Repository navigation
Prove the tests catch real mistakes, and tell the admin stories in plain English #2011
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 46 commits
7fd1e90
6a6fe10
707a48e
1b2d2a1
b57c9b4
21ea8a8
b5c2565
dbd1025
52910d3
9528978
596ce08
dd448a9
32c2aed
93c6dba
b70bd07
2ec5a06
606d218
27c7203
db8219e
c985d99
1813dcf
d669a9f
1f58f63
c0cc322
c4506a1
cb80ec6
8c5702f
5ee84cc
adf3ace
85a9d20
da36269
8463395
5a33bdf
9f15574
1e915f2
89e0a47
2e81e83
9b5e2b8
3b82cfe
730c254
d8a843b
1748578
79bbcfd
e7a6912
e1fdb04
c63fa7d
4fdf504
28c2a21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,9 @@ | ||
| #!/usr/bin/env -S deno run --allow-all | ||
|
|
||
| import { fromFileUrl, relative, resolve } from "@std/path"; | ||
| import { splitFlagValues } from "#scripts/flag-values.ts"; | ||
| import { auditEquivalentMutants } from "#scripts/mutation/equivalent-audit.ts"; | ||
| import { | ||
| EQUIVALENT_MUTANTS_FILE, | ||
| EQUIVALENT_MUTANTS_PATH, | ||
| } from "#scripts/mutation/ignore.ts"; | ||
| import { listRegistryFiles } from "#scripts/mutation/ignore.ts"; | ||
| import { runInSnapshot } from "#scripts/mutation/isolation.ts"; | ||
| import { | ||
| isSnapshotChild, | ||
|
|
@@ -21,28 +20,42 @@ const usage = `Usage: deno task mutation:audit-equivalents [--write] | |
| Runs lint and type-check against every recorded equivalent mutant without | ||
| running tests. Pass --write to remove entries that static checks now kill.`; | ||
|
|
||
| const parseOptions = (args: string[]): { write: boolean } => { | ||
| if (args.length === 0) return { write: false }; | ||
| if (args.length === 1 && args[0] === "--write") { | ||
| return { write: true }; | ||
| } | ||
| if (args.length === 1 && ["-h", "--help"].includes(args[0]!)) { | ||
| /** The audit's options. The registry list is internal plumbing: the parent | ||
| * enumerates the shards once and hands the same list to its snapshot child, | ||
| * so the audited files and the copied-back files can never differ. */ | ||
| interface AuditOptions { | ||
| registry: string[]; | ||
| write: boolean; | ||
| } | ||
|
|
||
| const parseOptions = (args: string[]): AuditOptions => { | ||
| const { rest, values } = splitFlagValues(args, "--registry"); | ||
| const registry = values.map((path) => { | ||
| if (!path || path.startsWith("--")) { | ||
| throw new Error(`--registry needs a path\n\n${usage}`); | ||
| } | ||
| return path; | ||
| }); | ||
| if (rest.length === 1 && ["-h", "--help"].includes(rest[0]!)) { | ||
| console.log(usage); | ||
| Deno.exit(0); | ||
| } | ||
| throw new Error(`Unknown arguments: ${args.join(" ")}\n\n${usage}`); | ||
| if (rest.length > 1 || (rest.length === 1 && rest[0] !== "--write")) { | ||
| throw new Error(`Unknown arguments: ${rest.join(" ")}\n\n${usage}`); | ||
| } | ||
| return { registry, write: rest[0] === "--write" }; | ||
| }; | ||
|
|
||
| /** Audit inside this checkout, which the snapshot child owns outright. */ | ||
| const runAudit = async (options: { write: boolean }): Promise<number> => { | ||
| const runAudit = async (options: AuditOptions): Promise<number> => { | ||
| const controller = new AbortController(); | ||
| const onSignal = (): void => controller.abort(); | ||
| onTerminationSignals(onSignal); | ||
| const result = await auditEquivalentMutants({ | ||
| ...options, | ||
| ignoreFile: EQUIVALENT_MUTANTS_FILE, | ||
| ignoreFiles: options.registry.map((path) => resolve(projectRoot, path)), | ||
| root: projectRoot, | ||
| signal: controller.signal, | ||
| write: options.write, | ||
| }) | ||
| .catch((error) => { | ||
| if (controller.signal.aborted) Deno.exit(130); | ||
|
|
@@ -60,17 +73,33 @@ const runAudit = async (options: { write: boolean }): Promise<number> => { | |
| return !options.write && result.retained !== result.checked ? 1 : 0; | ||
| }; | ||
|
|
||
| /** Project-relative registry paths, listed before snapshotting so a --write | ||
| * audit can carry each pruned file back to the live checkout. */ | ||
| const registryCopyBackPaths = async (): Promise<string[]> => | ||
| (await listRegistryFiles()).map((file) => | ||
| relative(projectRoot, typeof file === "string" ? file : fromFileUrl(file)), | ||
| ); | ||
|
|
||
| if (import.meta.main) { | ||
| // Parsed here too, so a bad flag or --help answers without copying anything. | ||
| const options = parseOptions(Deno.args); | ||
| if (isSnapshotChild()) { | ||
| Deno.exit(await runSnapshotChild(() => runAudit(options))); | ||
| } | ||
| // The manifest is the parent's to build: a hand-passed file would be | ||
| // audited in the snapshot yet never copied back, so refuse it outright. | ||
| if (options.registry.length > 0) { | ||
| throw new Error(`--registry is internal to the audit\n\n${usage}`); | ||
| } | ||
| // One shard list serves the audit and the copy-back alike, so a shard added | ||
| // while the snapshot is being made can never be pruned and then lost. | ||
| const registry = await registryCopyBackPaths(); | ||
| Deno.exit( | ||
| isSnapshotChild() | ||
| ? await runSnapshotChild(() => runAudit(options)) | ||
| : await runInSnapshot({ | ||
| args: Deno.args, | ||
| // Only a --write audit rewrites the list, so only it keeps a file. | ||
| copyBack: options.write ? [EQUIVALENT_MUTANTS_PATH] : [], | ||
| entryScript: "scripts/audit-equivalent-mutants.ts", | ||
| }), | ||
| await runInSnapshot({ | ||
| args: [...Deno.args, ...registry.flatMap((path) => ["--registry", path])], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the task is invoked with an extra AGENTS.md reference: AGENTS.md:L980-L984 Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 730c254. The parent now refuses a command-line Generated by Claude Code |
||
| // Only a --write audit rewrites the registry, so only it keeps files. | ||
| copyBack: options.write ? registry : [], | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When AGENTS.md reference: AGENTS.md:L98-L98 Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in 4fdf504, taking the second option you offered: the child's shard prune inside the snapshot now writes through the same whole-or-nothing helper, so no truncated shard can ever exist in the work copy for the copy-back to publish. What a crashed Generated by Claude Code |
||
| entryScript: "scripts/audit-equivalent-mutants.ts", | ||
| }), | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| /** | ||
| * Splitting one repeated flag and its values out of a command line. | ||
| */ | ||
|
|
||
| /** Split argv into the given flag's values and everything else. A flag at the | ||
| * end with nothing after it yields undefined, for the caller to refuse with | ||
| * its own words. */ | ||
| export const splitFlagValues = ( | ||
| args: string[], | ||
| flag: string, | ||
| ): { rest: string[]; values: (string | undefined)[] } => { | ||
| const rest: string[] = []; | ||
| const values: (string | undefined)[] = []; | ||
| const remaining = [...args]; | ||
| while (remaining.length > 0) { | ||
| const value = remaining.shift()!; | ||
| if (value === flag) values.push(remaining.shift()); | ||
| else rest.push(value); | ||
| } | ||
| return { rest, values }; | ||
| }; | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
Uh oh!
There was an error while loading. Please reload this page.