diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 807d966..2b14eb9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: deno compile \ --allow-read \ --allow-write \ - --allow-net=0.0.0.0:3000 \ + --allow-net \ --allow-env=HOST,PORT,PERSIST,QUEUE_API_TOKEN,QUEUE_DEPTH_LIMIT,QUEUE_COUNT_LIMIT,RATE_LIMIT_REQUESTS \ main.ts - name: Run tests diff --git a/Dockerfile b/Dockerfile index 017a09d..7038059 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,10 @@ FROM denoland/deno:2.7.6 -ARG DENO_HOST=0.0.0.0 -ARG DENO_PORT=3000 - COPY . /queue WORKDIR /queue -RUN deno compile --allow-read --allow-write --allow-net=${DENO_HOST}:${DENO_PORT} --allow-env=HOST,PORT,PERSIST,QUEUE_API_TOKEN,QUEUE_DEPTH_LIMIT,QUEUE_COUNT_LIMIT,RATE_LIMIT_REQUESTS --allow-sys main.ts && cp ./queue /usr/bin/ +RUN deno compile --allow-read --allow-write --allow-net --allow-env=HOST,PORT,PERSIST,QUEUE_API_TOKEN,QUEUE_DEPTH_LIMIT,QUEUE_COUNT_LIMIT,RATE_LIMIT_REQUESTS --allow-sys main.ts && cp ./queue /usr/bin/ RUN chown -R deno:deno /queue /usr/bin/queue diff --git a/tests/compiled_binary_test.ts b/tests/compiled_binary_test.ts index fd4074c..6a7a995 100644 --- a/tests/compiled_binary_test.ts +++ b/tests/compiled_binary_test.ts @@ -10,126 +10,179 @@ import { assertEquals } from "jsr:@std/assert@1.0"; async function compile(permFlags: string[], outPath: string): Promise { - const cmd = new Deno.Command(Deno.execPath(), { - args: ["compile", ...permFlags, "-o", outPath, "main.ts"], - cwd: ".", - stdout: "piped", - stderr: "piped", - }); - const { code, stderr } = await cmd.output(); - if (code !== 0) { - throw new Error(`compile failed: ${new TextDecoder().decode(stderr)}`); - } + const cmd = new Deno.Command(Deno.execPath(), { + args: ["compile", ...permFlags, "-o", outPath, "main.ts"], + cwd: ".", + stdout: "piped", + stderr: "piped", + }); + const { code, stderr } = await cmd.output(); + if (code !== 0) { + throw new Error(`compile failed: ${new TextDecoder().decode(stderr)}`); + } } // Spawns a compiled binary and waits for it to either announce it's // listening, or exit/crash. Returns the outcome so callers can assert on it. async function probeStartup( - binPath: string, - args: string[], - env: Record, + binPath: string, + args: string[], + env: Record, ): Promise<{ started: boolean; output: string }> { - const decoder = new TextDecoder(); - const child = new Deno.Command(binPath, { - args, - env, - stdout: "piped", - stderr: "piped", - }).spawn(); + const decoder = new TextDecoder(); + const child = new Deno.Command(binPath, { + args, + env, + stdout: "piped", + stderr: "piped", + }).spawn(); - let buf = ""; - let started = false; + let buf = ""; + let started = false; - const stdoutReader = child.stdout.getReader(); - const stderrReader = child.stderr.getReader(); + const stdoutReader = child.stdout.getReader(); + const stderrReader = child.stderr.getReader(); - const readAll = async (reader: ReadableStreamDefaultReader) => { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - buf += decoder.decode(value, { stream: true }); - if (buf.includes("Listening on")) { - started = true; - return; - } - } - }; + const readAll = async (reader: ReadableStreamDefaultReader) => { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buf += decoder.decode(value, { stream: true }); + if (buf.includes("Listening on")) { + started = true; + return; + } + } + }; - const timeout = new Promise((resolve) => setTimeout(resolve, 3000)); + const timeout = new Promise((resolve) => setTimeout(resolve, 3000)); - await Promise.race([ - Promise.all([readAll(stdoutReader), readAll(stderrReader)]), - timeout, - ]); + await Promise.race([ + Promise.all([readAll(stdoutReader), readAll(stderrReader)]), + timeout, + ]); - try { stdoutReader.releaseLock(); } catch { /* ignore */ } - try { stderrReader.releaseLock(); } catch { /* ignore */ } - try { child.kill("SIGKILL"); } catch { /* ignore */ } - try { await child.status; } catch { /* ignore */ } + try { + stdoutReader.releaseLock(); + } catch { /* ignore */ } + try { + stderrReader.releaseLock(); + } catch { /* ignore */ } + try { + child.kill("SIGKILL"); + } catch { /* ignore */ } + try { + await child.status; + } catch { /* ignore */ } - return { started, output: buf }; + return { started, output: buf }; } Deno.test({ - name: "compiled binary: starts with a scoped --allow-env allowlist (#64)", - fn: async () => { - const tempDir = await Deno.makeTempDir({ prefix: "queue-compile-test-" }); - const outPath = `${tempDir}/queue`; - try { - await compile( - [ - "--allow-read", - "--allow-write", - "--allow-net", - "--allow-env=HOST,PORT,PERSIST,QUEUE_API_TOKEN,QUEUE_DEPTH_LIMIT,QUEUE_COUNT_LIMIT,RATE_LIMIT_REQUESTS", - "--allow-sys", - ], - outPath, - ); - const { started, output } = await probeStartup(outPath, [], { - QUEUE_API_TOKEN: "compile-test-token", - HOST: "127.0.0.1", - PORT: "0", - }); - assertEquals(started, true, `binary did not report listening. Output:\n${output}`); - } finally { - await Deno.remove(tempDir, { recursive: true }).catch(() => {}); - } - }, - // Compiling a 100MB+ binary is slow; this is an integration test, not a unit test. - sanitizeResources: false, - sanitizeOps: false, + name: "compiled binary: starts with a scoped --allow-env allowlist (#64)", + fn: async () => { + const tempDir = await Deno.makeTempDir({ prefix: "queue-compile-test-" }); + const outPath = `${tempDir}/queue`; + try { + await compile( + [ + "--allow-read", + "--allow-write", + "--allow-net", + "--allow-env=HOST,PORT,PERSIST,QUEUE_API_TOKEN,QUEUE_DEPTH_LIMIT,QUEUE_COUNT_LIMIT,RATE_LIMIT_REQUESTS", + "--allow-sys", + ], + outPath, + ); + const { started, output } = await probeStartup(outPath, [], { + QUEUE_API_TOKEN: "compile-test-token", + HOST: "127.0.0.1", + PORT: "0", + }); + assertEquals( + started, + true, + `binary did not report listening. Output:\n${output}`, + ); + } finally { + await Deno.remove(tempDir, { recursive: true }).catch(() => {}); + } + }, + // Compiling a 100MB+ binary is slow; this is an integration test, not a unit test. + sanitizeResources: false, + sanitizeOps: false, +}); + +Deno.test({ + name: + "compiled binary: starts and persists with an arbitrary --persist dir (#65)", + fn: async () => { + const tempDir = await Deno.makeTempDir({ prefix: "queue-compile-test-" }); + const outPath = `${tempDir}/queue`; + const persistDir = `${tempDir}/persist`; + try { + await compile( + [ + "--allow-read", + "--allow-write", + "--allow-net", + "--allow-env", + "--allow-sys", + ], + outPath, + ); + const { started, output } = await probeStartup(outPath, ["--persist"], { + QUEUE_API_TOKEN: "compile-test-token", + HOST: "127.0.0.1", + PORT: "0", + PERSIST: persistDir, + }); + assertEquals( + started, + true, + `binary did not report listening. Output:\n${output}`, + ); + } finally { + await Deno.remove(tempDir, { recursive: true }).catch(() => {}); + } + }, + // Compiling a 100MB+ binary is slow; this is an integration test, not a unit test. + sanitizeResources: false, + sanitizeOps: false, }); Deno.test({ - name: "compiled binary: starts and persists with an arbitrary --persist dir (#65)", - fn: async () => { - const tempDir = await Deno.makeTempDir({ prefix: "queue-compile-test-" }); - const outPath = `${tempDir}/queue`; - const persistDir = `${tempDir}/persist`; - try { - await compile( - [ - "--allow-read", - "--allow-write", - "--allow-net", - "--allow-env", - "--allow-sys", - ], - outPath, - ); - const { started, output } = await probeStartup(outPath, ["--persist"], { - QUEUE_API_TOKEN: "compile-test-token", - HOST: "127.0.0.1", - PORT: "0", - PERSIST: persistDir, - }); - assertEquals(started, true, `binary did not report listening. Output:\n${output}`); - } finally { - await Deno.remove(tempDir, { recursive: true }).catch(() => {}); - } - }, - // Compiling a 100MB+ binary is slow; this is an integration test, not a unit test. - sanitizeResources: false, - sanitizeOps: false, + name: + "compiled binary: binds to a runtime PORT/HOST different from build-time defaults (#68)", + fn: async () => { + const tempDir = await Deno.makeTempDir({ prefix: "queue-compile-test-" }); + const outPath = `${tempDir}/queue`; + try { + await compile( + [ + "--allow-read", + "--allow-write", + "--allow-net", + "--allow-env", + "--allow-sys", + ], + outPath, + ); + const { started, output } = await probeStartup(outPath, [], { + QUEUE_API_TOKEN: "compile-test-token", + HOST: "0.0.0.0", + PORT: "1991", + }); + assertEquals( + started, + true, + `binary did not report listening. Output:\n${output}`, + ); + } finally { + await Deno.remove(tempDir, { recursive: true }).catch(() => {}); + } + }, + // Compiling a 100MB+ binary is slow; this is an integration test, not a unit test. + sanitizeResources: false, + sanitizeOps: false, });