Skip to content

Commit 32fe345

Browse files
authored
Merge pull request #7752 from sagemathinc/api-exec-await-project
extract project/backend aspects of api-exec-await
2 parents d4e2291 + 0bcfbe5 commit 32fe345

File tree

24 files changed

+616
-232
lines changed

24 files changed

+616
-232
lines changed

src/packages/backend/execute-code.test.ts

+143
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55

66
process.env.COCALC_PROJECT_MONITOR_INTERVAL_S = "1";
7+
// default is much lower, might fail if you have more procs than the default
8+
process.env.COCALC_PROJECT_INFO_PROC_LIMIT = "10000";
79

810
import { executeCode } from "./execute-code";
911

@@ -300,6 +302,147 @@ describe("async", () => {
300302
);
301303
});
302304

305+
// the await case is essentially like the async case above, but it will block for a bit
306+
describe("await", () => {
307+
const check = (s) => {
308+
expect(s.type).toEqual("async");
309+
if (s.type !== "async") return;
310+
expect(s.status).toEqual("completed");
311+
expect(s.elapsed_s).toBeGreaterThan(1);
312+
expect(s.elapsed_s).toBeLessThan(3);
313+
expect(s.exit_code).toBe(0);
314+
expect(s.pid).toBeGreaterThan(1);
315+
expect(s.stdout).toEqual("foo\n");
316+
expect(s.stderr).toEqual("");
317+
};
318+
319+
it("returns when a job finishes", async () => {
320+
const c = await executeCode({
321+
command: "sleep 2; echo 'foo'",
322+
bash: true,
323+
err_on_exit: false,
324+
async_call: true,
325+
});
326+
expect(c.type).toEqual("async");
327+
if (c.type !== "async") return;
328+
const { status, job_id, pid } = c;
329+
expect(status).toEqual("running");
330+
expect(pid).toBeGreaterThan(1);
331+
const t0 = Date.now();
332+
const s = await executeCode({
333+
async_await: true,
334+
async_get: job_id,
335+
async_stats: true,
336+
});
337+
const t1 = Date.now();
338+
// This is the main test: it really waited for at least a second until the job completed
339+
expect((t1 - t0) / 1000).toBeGreaterThan(1);
340+
check(s);
341+
if (s.type !== "async") return;
342+
expect(Array.isArray(s.stats)).toBeTruthy();
343+
});
344+
345+
it("returns immediately if already done", async () => {
346+
const c = await executeCode({
347+
command: "sleep 1.1; echo 'foo'",
348+
bash: true,
349+
err_on_exit: false,
350+
async_call: true,
351+
});
352+
expect(c.type).toEqual("async");
353+
if (c.type !== "async") return;
354+
const { status, job_id, pid } = c;
355+
expect(status).toEqual("running");
356+
expect(pid).toBeGreaterThan(1);
357+
await new Promise((done) => setTimeout(done, 2000));
358+
const s = await executeCode({
359+
async_await: true,
360+
async_get: job_id,
361+
async_stats: true,
362+
});
363+
check(s);
364+
if (s.type !== "async") return;
365+
expect(s.elapsed_s).toBeLessThan(1.5);
366+
});
367+
368+
it("deal with unknown executables", async () => {
369+
const c = await executeCode({
370+
command: "random123unknown99",
371+
err_on_exit: false,
372+
async_call: true,
373+
});
374+
expect(c.type).toEqual("async");
375+
if (c.type !== "async") return;
376+
const { job_id, pid } = c;
377+
expect(pid).toBeUndefined();
378+
const s = await executeCode({
379+
async_await: true,
380+
async_get: job_id,
381+
async_stats: true,
382+
});
383+
expect(s.type).toEqual("async");
384+
if (s.type !== "async") return;
385+
expect(s.exit_code).toBe(1);
386+
expect(s.stderr).toContain("ENOENT");
387+
expect(s.status).toBe("error");
388+
});
389+
390+
it("returns an error", async () => {
391+
const c = await executeCode({
392+
command: "sleep .1; >&2 echo baz; exit 3",
393+
bash: true,
394+
err_on_exit: false,
395+
async_call: true,
396+
});
397+
expect(c.type).toEqual("async");
398+
if (c.type !== "async") return;
399+
const { status, job_id, pid } = c;
400+
expect(status).toEqual("running");
401+
expect(pid).toBeGreaterThan(1);
402+
const t0 = Date.now();
403+
const s = await executeCode({
404+
async_await: true,
405+
async_get: job_id,
406+
async_stats: true,
407+
});
408+
expect((Date.now() - t0) / 1000).toBeGreaterThan(0.05);
409+
expect(s.type).toEqual("async");
410+
if (s.type !== "async") return;
411+
expect(s.stderr).toEqual("baz\n");
412+
expect(s.exit_code).toEqual(3);
413+
expect(s.status).toEqual("completed");
414+
});
415+
416+
it("react to a killed process", async () => {
417+
const c = await executeCode({
418+
command: "sh",
419+
args: ["-c", `echo foo; sleep 1; echo bar;`],
420+
bash: false,
421+
err_on_exit: false,
422+
async_call: true,
423+
});
424+
expect(c.type).toEqual("async");
425+
if (c.type !== "async") return;
426+
const { job_id, pid } = c;
427+
await new Promise((done) => setTimeout(done, 100));
428+
await executeCode({
429+
command: `kill -9 -${pid}`,
430+
bash: true,
431+
});
432+
const s = await executeCode({
433+
async_await: true,
434+
async_get: job_id,
435+
async_stats: true,
436+
});
437+
expect(s.type).toEqual("async");
438+
if (s.type !== "async") return;
439+
expect(s.stderr).toEqual("");
440+
expect(s.stdout).toEqual("foo\n");
441+
expect(s.exit_code).toEqual(0);
442+
expect(s.status).toEqual("completed");
443+
});
444+
});
445+
303446
// we burn a bit of CPU to get the cpu_pct and cpu_secs up
304447
const CPU_PY = `
305448
from time import time

0 commit comments

Comments
 (0)