KyleJune
KyleJune6d ago

Coverage for child processes

Does coverage not work for spawned child processes? I was trying to write an end to end test by spawning my server as a child process, but it seems like any code touched in that child process doesn't count towards coverage. Is there any way to get it to count? In the following test case, I have it spawn my hono server, then wait for Deno.serve to start listening before it tries making a fetch request to the hono server. The test passes, showing that it was able to use the endpoint that returns "Hello, World!" text, but the coverage shows it as untouched since it is in a child process.
describe("serves application when running main", () => {
it("should serve the application", async () => {
const command = new Deno.Command(Deno.execPath(), {
args: ["run", "-A", resolve(import.meta.dirname!, "./main.ts")],
stdout: "piped",
stderr: "piped",
});
await using child = command.spawn();
const stdout = mergeReadableStreams(child.stdout, child.stderr)
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());

// Wait for server to start by monitoring stdout
for await (const line of stdout) {
if (line.includes("Listening on")) {
break;
}
}

const res = await fetch("http://localhost:8000/");
assertEquals(res.status, 200);
assertEquals(await res.text(), "Hello, World!");
});
});
describe("serves application when running main", () => {
it("should serve the application", async () => {
const command = new Deno.Command(Deno.execPath(), {
args: ["run", "-A", resolve(import.meta.dirname!, "./main.ts")],
stdout: "piped",
stderr: "piped",
});
await using child = command.spawn();
const stdout = mergeReadableStreams(child.stdout, child.stderr)
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());

// Wait for server to start by monitoring stdout
for await (const line of stdout) {
if (line.includes("Listening on")) {
break;
}
}

const res = await fetch("http://localhost:8000/");
assertEquals(res.status, 200);
assertEquals(await res.text(), "Hello, World!");
});
});
No description
No description
6 Replies
bennyp
bennyp3d ago
have you opened an issue for this?
KyleJune
KyleJuneOP3d ago
No
bennyp
bennyp3d ago
https://github.com/denoland/deno/issues/7648 this seems to indicate that coverage should work
GitHub
A cover command and cross process code coverage · Issue #7648 · d...
I've mentioned a couple of times in Discord that I'm leaning towards a cover command so time to elaborate a bit on what I mean before slinging patches. A really common test scenario involve...
bennyp
bennyp3d ago
2.3 turns on coverage for the deno test command, maybe they don't also propagate that env var? @kt3k is it possible that your PR adding automatic coverage doesn't propagate the necessary env to child processes?
kt3k
kt3k3d ago
That feature is not implemented yet. It's tracked in this issue https://github.com/denoland/deno/issues/16440
GitHub
FR: Consider adding deno run --coverage · Issue #16440 · denola...
This would help measure code coverage of running Deno program by running tests against it. e.g. Running some API tests against a running Deno server. Should hopefully be not too contrived since V8 ...
KyleJune
KyleJuneOP2d ago
Oh nice. Good to see there is an issue for it. I was mainly looking to do it to cover code paths that are only triggered when files conditionally do work depending on import.meta.main being true. Otherwise those lines will never get touched by tests. The latest update does add a way to ignore those lines, but it would be nice to be able to cover them. It would probably be useful for testing cli tools and their output. With that flag, people would be able to spawn child processes for the cli script with different args to test different code paths. Without it, you would need to export the functions used in the cli script to be able to test their behavior. I've done that before by having the cli code in an exported function like export function run(args) { ... } then at the end add if (import.meta.main) { run(Deno.args) } so that that function is automatically called with deno args if the script is called directly. Then in tests you call it with fake arguments. If anyone else comes across my screenshot example and tries using it, I found one flaw with it. For some reason, no additional requests would get handled after the first if you were to try to make multiple requests to the server that was started. I found it was related to how the stdout stream was getting cancelled. If you change the for loop to use stdout.values({ preventCancel: true }) instead of stdout, the child process will keep handling requests until it is done. Also if you want to spawn a server for use in multiple tests, put it in a beforeAll and assign it to a variable in the parent describe, then child.kill(); await server.status; in the afterAll to get rid of it. My example in this help thread was using explicit resource management, so it was automatically killing the process.

Did you find this page helpful?