thunderjar 0.1.2 → 0.1.3-preview-eee7160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2161 -34
- package/package.json +13 -4
- package/src/AGENTS.md +156 -0
- package/src/adapters/docker/docker.ts +117 -0
- package/src/api/declarations.ts +279 -0
- package/src/api/fragments.ts +30 -0
- package/src/api/parameter-names.d.ts +16 -0
- package/src/cli/commands/build/build.docker.test.ts +173 -0
- package/src/cli/commands/build/build.test.ts +234 -0
- package/src/cli/commands/build/build.ts +115 -0
- package/src/cli/commands/declarations.typecheck.test.ts +173 -0
- package/src/cli/commands/executions/executions.test.ts +84 -0
- package/src/cli/commands/executions/executions.ts +31 -0
- package/src/cli/commands/format.ts +33 -0
- package/src/cli/commands/generate/generate.test.ts +101 -0
- package/src/cli/commands/generate/generate.ts +18 -0
- package/src/cli/commands/generate/parameter-names.ts +131 -0
- package/src/cli/commands/init/init.test.ts +123 -0
- package/src/cli/commands/init/init.ts +189 -0
- package/src/cli/commands/options.ts +17 -0
- package/src/cli/commands/plan/plan.test.ts +168 -0
- package/src/cli/commands/plan/plan.ts +72 -0
- package/src/cli/commands/remeasure/remeasure.test.ts +536 -0
- package/src/cli/commands/remeasure/remeasure.ts +194 -0
- package/src/cli/commands/run/run.test.ts +622 -0
- package/src/cli/commands/run/run.ts +203 -0
- package/src/cli/commands/show/show.test.ts +288 -0
- package/src/cli/commands/show/show.ts +252 -0
- package/src/cli/commands/validation.test.ts +200 -0
- package/src/cli/index.test.ts +47 -0
- package/src/cli/index.ts +9 -0
- package/src/cli/program.ts +29 -0
- package/src/images/prerun-builds.ts +108 -0
- package/src/images/prerun.test.ts +243 -0
- package/src/images/prerun.ts +272 -0
- package/src/images/repository.ts +72 -0
- package/src/index.ts +24 -0
- package/src/instruments/instruments.test.ts +240 -0
- package/src/instruments/instruments.ts +205 -0
- package/src/matrix/execution.ts +122 -0
- package/src/matrix/hashing.test.ts +298 -0
- package/src/matrix/hashing.ts +131 -0
- package/src/matrix/permutations.test.ts +58 -0
- package/src/matrix/permutations.ts +50 -0
- package/src/matrix/resolve.ts +63 -0
- package/src/matrix/tags.test.ts +15 -0
- package/src/matrix/tags.ts +22 -0
- package/src/matrix/ulid.test.ts +28 -0
- package/src/matrix/ulid.ts +22 -0
- package/src/measuring/aggregate.test.ts +188 -0
- package/src/measuring/aggregate.ts +154 -0
- package/src/measuring/measure.test.ts +105 -0
- package/src/measuring/measure.ts +62 -0
- package/src/measuring/remeasure-templates/v1/isPrime.test.template.txt +4 -0
- package/src/measuring/remeasure-templates/v2/isPrime.test.template.txt +5 -0
- package/src/measuring/remeasure.test.ts +352 -0
- package/src/measuring/remeasure.ts +113 -0
- package/src/platform/fs.test.ts +89 -0
- package/src/platform/fs.ts +123 -0
- package/src/platform/path.test.ts +38 -0
- package/src/platform/path.ts +72 -0
- package/src/platform/signals.test.ts +25 -0
- package/src/platform/signals.ts +68 -0
- package/src/project/config.test.ts +68 -0
- package/src/project/config.ts +297 -0
- package/src/running/runner.test.ts +73 -0
- package/src/running/runner.ts +252 -0
- package/src/store/run-data-store.test.ts +232 -0
- package/src/store/run-data-store.ts +254 -0
- package/src/testing/cli.ts +65 -0
- package/src/testing/fake-docker.test.ts +197 -0
- package/src/testing/fake-docker.ts +104 -0
- package/src/testing/fixture-project.ts +177 -0
- package/src/testing/measurement-context.ts +5 -0
- package/src/testing/seed-repo.test.ts +10 -0
- package/src/testing/seed-repo.ts +83 -0
- package/src/testing/test-setup.ts +10 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
// `thunderjar remeasure`, through the CLI, in place in fixtureTests/scenario_8_recorded_executions,
|
|
2
|
+
// which starts with a run data store of four executions. An execution prints its plan: each
|
|
3
|
+
// current measurement's status, and each deleted name with its stored measures. A test case
|
|
4
|
+
// lists every execution of it, and which have nothing to do. Then each run's missing or stale
|
|
5
|
+
// measures are taken from its postrun image, through the recording fake Docker, which is given
|
|
6
|
+
// the scenario's two saved postrun workspaces and copies them out; nothing runs in a container.
|
|
7
|
+
// Every aggregation result of each execution given is recomputed from the Results then stored,
|
|
8
|
+
// even with nothing to measure. A successful remeasure writes to a disposable copy of the store,
|
|
9
|
+
// given with `--data-dir`; the checked-in store and saved workspaces are left as they were. Each
|
|
10
|
+
// asserts on stdout, stderr and the exit code. Any of these can be run by hand, with an absolute
|
|
11
|
+
// `--data-dir` holding a copy of the scenario's `.thunderjar/.data/runs.json`, though by hand the
|
|
12
|
+
// postrun images must exist in the real Docker:
|
|
13
|
+
//
|
|
14
|
+
// bun run fixture 8 remeasure 01k4x9j2e8mqz3abcdefghjkmn --data-dir <dir>
|
|
15
|
+
// bun run fixture 8 remeasure fails --data-dir <dir>
|
|
16
|
+
|
|
17
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
18
|
+
import { copy, existsSync, listEntries, makeTempDir, readTextSync, remove, tmpdir } from "../../../platform/fs.ts";
|
|
19
|
+
import { join } from "../../../platform/path.ts";
|
|
20
|
+
import { fakeDocker, scenario, thunderjar } from "../../../testing/cli.ts";
|
|
21
|
+
import type { AggregationResult, ContainerRunRecord, RecordedMeasure, Result } from "../../../store/run-data-store.ts";
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
/** Recorded before aggregation results were stored; one run exited 3 and was never measured. */
|
|
25
|
+
const OLDEST = "01k4tq3h8r0000000000000000";
|
|
26
|
+
/** readmeExists, since deleted, errored in each permutation's first iteration; one isPrimeTemplateTest is stale. */
|
|
27
|
+
const ERRORED = "01k4x9j2e8mqz3abcdefghjkmn";
|
|
28
|
+
/** The latest execution of `fails`, after readmeExists was deleted. */
|
|
29
|
+
const LATEST = "01k4xc0000000000000000000a";
|
|
30
|
+
/** The only execution of `times-out`, whose task has no measurements; each run exited 124. */
|
|
31
|
+
const TIMED_OUT = "01k4xb7q0c2tnvabcdefghjkmn";
|
|
32
|
+
|
|
33
|
+
/** The failing agent's permutation of `fails`, and the behaving agent's. */
|
|
34
|
+
const FAILING = "42440c48b1cee8eeabea4c8ac45fb03f66331039bb3b57b9ecca96bc83a4fb03";
|
|
35
|
+
const BEHAVING = "8ffdda6e5c8221796af0671d22f10f9e35edab65e0c056a829946f54c90753e7";
|
|
36
|
+
const WAITING = "389190d41af852c3b80e2d399804c682de3810d2b936de26db7de0ac273faca6";
|
|
37
|
+
|
|
38
|
+
/** ERRORED's behaving run measured with the older template, so its isPrimeTemplateTest is stale. */
|
|
39
|
+
const STALE_RUN = `postrun-e${ERRORED}-h8ffdda6e-i00`;
|
|
40
|
+
/** OLDEST's behaving run that exited 3 and was never measured. */
|
|
41
|
+
const UNMEASURED_RUN = `postrun-e${OLDEST}-h8ffdda6e-i01`;
|
|
42
|
+
|
|
43
|
+
/** The current measurements' full content hashes. */
|
|
44
|
+
const HASH = {
|
|
45
|
+
isPrimeTemplateTest: "e4cf4e5c3ec40d0d5373b9a92c23e7a90d30500caef60d7e1df608258cd2a6cd",
|
|
46
|
+
isPrimeTsExists: "e514c7262f9be694b4f4eb151381431b9e99c39b660739f5238f3a1637be63ed",
|
|
47
|
+
tsIgnoreCount: "383b2dc3ebbb7aed32c3f0cb6cd13ee824eae5aec189deaa66b7cb30e9b1b492",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const FOOTER = "The agent was not rerun.";
|
|
51
|
+
|
|
52
|
+
/** Every container run takes 0.04s in this scenario. */
|
|
53
|
+
const DURATION = { mean: 40, max: 40 };
|
|
54
|
+
|
|
55
|
+
/** The failing agent's aggregation result in each execution of `fails`: it never writes isPrime.ts. */
|
|
56
|
+
const failingAggregate = (executionId: string): AggregationResult => ({
|
|
57
|
+
executionId,
|
|
58
|
+
permutationHash: FAILING,
|
|
59
|
+
iterations: 2,
|
|
60
|
+
measures: {
|
|
61
|
+
isPrimeTemplateTest: { measured: 0 },
|
|
62
|
+
isPrimeTsExists: { measured: 2, rate: 0 },
|
|
63
|
+
tsIgnoreCount: { measured: 0 },
|
|
64
|
+
},
|
|
65
|
+
duration: DURATION,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** The behaving agent's aggregation result with both iterations measured, without the deleted readmeExists. */
|
|
69
|
+
const behavingAggregate = (executionId: string): AggregationResult => ({
|
|
70
|
+
executionId,
|
|
71
|
+
permutationHash: BEHAVING,
|
|
72
|
+
iterations: 2,
|
|
73
|
+
measures: {
|
|
74
|
+
isPrimeTemplateTest: { measured: 2, mean: { passed: 3, failed: 2 }, sum: { passed: 6, failed: 4 } },
|
|
75
|
+
isPrimeTsExists: { measured: 2, rate: 1 },
|
|
76
|
+
tsIgnoreCount: { measured: 2, median: 1, min: 1, sum: 2 },
|
|
77
|
+
},
|
|
78
|
+
duration: DURATION,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/** ERRORED's aggregation results recomputed: as stored, without the deleted readmeExists. */
|
|
82
|
+
const ERRORED_RECOMPUTED = [failingAggregate(ERRORED), behavingAggregate(ERRORED)];
|
|
83
|
+
|
|
84
|
+
/** OLDEST's aggregation results, which it had none of, once its behaving agent's second run is measured. */
|
|
85
|
+
const OLDEST_BACKFILLED = [failingAggregate(OLDEST), behavingAggregate(OLDEST)];
|
|
86
|
+
|
|
87
|
+
interface StoreFile {
|
|
88
|
+
containerRuns: ContainerRunRecord[];
|
|
89
|
+
aggregationResults?: AggregationResult[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const read = (path: string) => JSON.parse(readTextSync(path)) as StoreFile;
|
|
93
|
+
|
|
94
|
+
/** `executionId`'s stored aggregation results, by permutation hash. */
|
|
95
|
+
const aggregatesOf = (file: StoreFile, executionId: string) =>
|
|
96
|
+
(file.aggregationResults ?? [])
|
|
97
|
+
.filter((r) => r.executionId === executionId)
|
|
98
|
+
.toSorted((a, b) => (a.permutationHash < b.permutationHash ? -1 : 1));
|
|
99
|
+
|
|
100
|
+
/** A measure taken today, of today's measurement `name`; its `measuredAt` is checked by `takenBetween`. */
|
|
101
|
+
const taken = (name: keyof typeof HASH, instrument: string, data: unknown): RecordedMeasure => ({
|
|
102
|
+
instrument,
|
|
103
|
+
contentHash: HASH[name],
|
|
104
|
+
measuredAt: expect.any(String) as unknown as string,
|
|
105
|
+
outcome: { outcome: "measured", data },
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/** `records`, with `measures` merged into the Result of each run named, as a backfill stores them. */
|
|
109
|
+
const withMeasures = (records: ContainerRunRecord[], measures: Record<string, Result>) =>
|
|
110
|
+
records.map((record) =>
|
|
111
|
+
measures[record.postrunImage] ? { ...record, result: { ...record.result, ...measures[record.postrunImage] } } : record,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
/** Asserts each of `names` in `postrunImage`'s stored Result was dated, as a valid ISO time, between `from` and `to`. */
|
|
115
|
+
function takenBetween(file: StoreFile, postrunImage: string, names: string[], from: number, to: number) {
|
|
116
|
+
const { result } = file.containerRuns.find((r) => r.postrunImage === postrunImage)!;
|
|
117
|
+
for (const name of names) {
|
|
118
|
+
const { measuredAt } = result[name]!;
|
|
119
|
+
expect(new Date(measuredAt).toISOString()).toBe(measuredAt);
|
|
120
|
+
expect(Date.parse(measuredAt)).toBeGreaterThanOrEqual(from);
|
|
121
|
+
expect(Date.parse(measuredAt)).toBeLessThanOrEqual(to);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The fake Docker's commands, each measurement's container and scratch folder numbered in the
|
|
127
|
+
* order first seen, since their names are random.
|
|
128
|
+
*/
|
|
129
|
+
function protocol(commands: string[]) {
|
|
130
|
+
const seen = { container: [] as string[], scratch: [] as string[] };
|
|
131
|
+
const number = (kind: keyof typeof seen) => (match: string) => {
|
|
132
|
+
if (!seen[kind].includes(match)) seen[kind].push(match);
|
|
133
|
+
return `<${kind} ${seen[kind].indexOf(match) + 1}>`;
|
|
134
|
+
};
|
|
135
|
+
return commands.map((line) =>
|
|
136
|
+
line
|
|
137
|
+
.replaceAll(new RegExp(`${tmpdir().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/thunderjar-measure-[^/ ]+`, "g"), number("scratch"))
|
|
138
|
+
.replaceAll(/thunderjar-measure-[0-9a-f]{8}/g, number("container")),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** What opening the `n`th measurement's context of `tag` asks the fake Docker, then closing it. */
|
|
143
|
+
const opened = (n: number, tag: string) => [
|
|
144
|
+
`docker create --name <container ${n}> --entrypoint sh thunderjar:${tag}`,
|
|
145
|
+
`docker cp <container ${n}>:/workspace/. <scratch ${n}>/workspace`,
|
|
146
|
+
`docker cp <container ${n}>:/thunderjar/result.json <scratch ${n}>/result.json`,
|
|
147
|
+
`docker rm --force <container ${n}>`,
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/** Every file's bytes under `dir`, by relative path. */
|
|
151
|
+
const snapshot = (dir: string) =>
|
|
152
|
+
Object.fromEntries(
|
|
153
|
+
listEntries(dir, { recursive: true })
|
|
154
|
+
.filter((entry) => !entry.isDirectory)
|
|
155
|
+
.map(({ path }) => [path, readTextSync(join(dir, path))]),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
describe("scenario_8_recorded_executions", () => {
|
|
159
|
+
const fixtures = scenario("scenario_8_recorded_executions");
|
|
160
|
+
const dataDir = join(fixtures, ".thunderjar", ".data");
|
|
161
|
+
const store = join(dataDir, "runs.json");
|
|
162
|
+
const checkedIn = readTextSync(store);
|
|
163
|
+
const starting = read(store);
|
|
164
|
+
const saved = join(fixtures, "..", "postrun-files");
|
|
165
|
+
const savedFiles = snapshot(saved);
|
|
166
|
+
|
|
167
|
+
const disposable: string[] = [];
|
|
168
|
+
/** A disposable data directory, holding an exact copy of the checked-in store. */
|
|
169
|
+
const seededDataDir = async () => {
|
|
170
|
+
const dir = await makeTempDir("thunderjar-remeasure-");
|
|
171
|
+
disposable.push(dir);
|
|
172
|
+
await copy(store, join(dir, "runs.json"));
|
|
173
|
+
return { dir, store: join(dir, "runs.json") };
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const fakes: Awaited<ReturnType<typeof fakeDocker>>[] = [];
|
|
177
|
+
/** A disposable fake Docker in which both runs with saved files are postrun images. */
|
|
178
|
+
const savedPostruns = async () => {
|
|
179
|
+
const docker = await fakeDocker([], [], {
|
|
180
|
+
[`thunderjar:${STALE_RUN}`]: join(saved, ERRORED, STALE_RUN),
|
|
181
|
+
[`thunderjar:${UNMEASURED_RUN}`]: join(saved, OLDEST, UNMEASURED_RUN),
|
|
182
|
+
});
|
|
183
|
+
fakes.push(docker);
|
|
184
|
+
return docker;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
afterEach(async () => {
|
|
188
|
+
expect(readTextSync(store)).toBe(checkedIn);
|
|
189
|
+
expect(snapshot(saved)).toEqual(savedFiles);
|
|
190
|
+
for (const docker of fakes.splice(0)) {
|
|
191
|
+
// Every measurement container is removed once its files are copied out.
|
|
192
|
+
expect(docker.containers()).toEqual([]);
|
|
193
|
+
await docker.remove();
|
|
194
|
+
}
|
|
195
|
+
for (const dir of disposable.splice(0)) await remove(dir);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("remeasure <execution> retakes only its stale isPrimeTemplateTest, keeps the deleted readmeExists, and recomputes both its aggregates without readmeExists", async () => {
|
|
199
|
+
const expected = {
|
|
200
|
+
stdout: `Execution ${ERRORED} (4 container runs)
|
|
201
|
+
Current measurements for task is-prime:
|
|
202
|
+
isPrimeTemplateTest new / changed (e4cf4e5c)
|
|
203
|
+
isPrimeTsExists up to date
|
|
204
|
+
tsIgnoreCount up to date
|
|
205
|
+
readmeExists deleted from task; 4 stored measures kept, excluded from aggregates
|
|
206
|
+
|
|
207
|
+
Measuring from each postrun image:
|
|
208
|
+
8ffdda6e i00 isPrimeTemplateTest {passed: 3, failed: 2}
|
|
209
|
+
|
|
210
|
+
Updated 1 container run. Recomputed 2 aggregation results.
|
|
211
|
+
${FOOTER}
|
|
212
|
+
`,
|
|
213
|
+
stderr: "",
|
|
214
|
+
exitCode: 0,
|
|
215
|
+
};
|
|
216
|
+
for (const order of [
|
|
217
|
+
(dir: string) => ["remeasure", ERRORED, "--data-dir", dir],
|
|
218
|
+
(dir: string) => ["remeasure", "--data-dir", dir, ERRORED],
|
|
219
|
+
]) {
|
|
220
|
+
const seeded = await seededDataDir();
|
|
221
|
+
const docker = await savedPostruns();
|
|
222
|
+
const args = order(seeded.dir);
|
|
223
|
+
const from = Date.now();
|
|
224
|
+
expect({ args, ...(await thunderjar(fixtures, args, docker.env)) }).toEqual({ args, ...expected });
|
|
225
|
+
const to = Date.now();
|
|
226
|
+
|
|
227
|
+
const after = read(seeded.store);
|
|
228
|
+
takenBetween(after, STALE_RUN, ["isPrimeTemplateTest"], from, to);
|
|
229
|
+
// Every other measure, the deleted readmeExists among them, and every run's metadata are as they were.
|
|
230
|
+
expect(after.containerRuns).toEqual(
|
|
231
|
+
withMeasures(starting.containerRuns, {
|
|
232
|
+
[STALE_RUN]: { isPrimeTemplateTest: taken("isPrimeTemplateTest", "templateTest", { passed: 3, failed: 2 }) },
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
expect(aggregatesOf(after, ERRORED)).toEqual(ERRORED_RECOMPUTED);
|
|
236
|
+
for (const other of [OLDEST, TIMED_OUT, LATEST]) {
|
|
237
|
+
expect(aggregatesOf(after, other)).toEqual(aggregatesOf(starting, other));
|
|
238
|
+
}
|
|
239
|
+
const errored = after.containerRuns.filter((r) => r.executionId === ERRORED);
|
|
240
|
+
expect(errored.map((r) => r.result.readmeExists?.outcome.outcome)).toEqual([
|
|
241
|
+
"erroredWhileMeasuring",
|
|
242
|
+
"measured",
|
|
243
|
+
"erroredWhileMeasuring",
|
|
244
|
+
"measured",
|
|
245
|
+
]);
|
|
246
|
+
expect(protocol(docker.commands())).toEqual(opened(1, STALE_RUN));
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("show against a store remeasure <execution> updated marks neither the retaken value nor its aggregate stale, and has no readmeExists aggregate", async () => {
|
|
251
|
+
const seeded = await seededDataDir();
|
|
252
|
+
const docker = await savedPostruns();
|
|
253
|
+
expect((await thunderjar(fixtures, ["remeasure", ERRORED, "--data-dir", seeded.dir], docker.env)).exitCode).toBe(0);
|
|
254
|
+
|
|
255
|
+
expect(await thunderjar(fixtures, ["show", ERRORED, "--data-dir", seeded.dir], docker.env)).toEqual({
|
|
256
|
+
stdout: `Execution ${ERRORED}
|
|
257
|
+
Test case fails
|
|
258
|
+
Task is-prime
|
|
259
|
+
Date 2026-09-29 14:30
|
|
260
|
+
|
|
261
|
+
Permutation 42440c48 (2 iterations)
|
|
262
|
+
node20 | baseline | snerk | fake-harness-alpha@1.0.1/fake-model-small | plain
|
|
263
|
+
|
|
264
|
+
MEASURE MEASURED AGGREGATE
|
|
265
|
+
isPrimeTemplateTest 0/2 —
|
|
266
|
+
isPrimeTsExists 2/2 rate 0.00
|
|
267
|
+
tsIgnoreCount 0/2 —
|
|
268
|
+
|
|
269
|
+
exit 2/2 exited 3
|
|
270
|
+
duration mean 0.04s, max 0.04s
|
|
271
|
+
|
|
272
|
+
Permutation 8ffdda6e (2 iterations)
|
|
273
|
+
node20 | baseline | snerk | fake-harness-alpha@1.0.0/fake-model-small | plain
|
|
274
|
+
|
|
275
|
+
MEASURE MEASURED AGGREGATE
|
|
276
|
+
isPrimeTemplateTest 2/2 mean {passed: 3.0, failed: 2.0}, sum {passed: 6, failed: 4}
|
|
277
|
+
isPrimeTsExists 2/2 rate 1.00
|
|
278
|
+
tsIgnoreCount 2/2 median 1.0, min 1, sum 2
|
|
279
|
+
|
|
280
|
+
exit 2/2 exited 0
|
|
281
|
+
duration mean 0.04s, max 0.04s
|
|
282
|
+
`,
|
|
283
|
+
stderr: "",
|
|
284
|
+
exitCode: 0,
|
|
285
|
+
});
|
|
286
|
+
expect(await thunderjar(fixtures, ["show", STALE_RUN, "--data-dir", seeded.dir], docker.env)).toEqual({
|
|
287
|
+
stdout: `Container run ${STALE_RUN}
|
|
288
|
+
Execution ${ERRORED} (fails)
|
|
289
|
+
Permutation 8ffdda6e, iteration 1
|
|
290
|
+
Ran at 2026-09-29 14:30
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
baseImage node20 26d9
|
|
294
|
+
codeState baseline bb15
|
|
295
|
+
promptSet snerk 827a
|
|
296
|
+
harnessModel fake-harness-alpha@1.0.0/fake-model-small da28
|
|
297
|
+
initialPrompt plain 9e30
|
|
298
|
+
|
|
299
|
+
Measures
|
|
300
|
+
isPrimeTemplateTest {passed: 3, failed: 2}
|
|
301
|
+
isPrimeTsExists true
|
|
302
|
+
readmeExists error (disk on fire)
|
|
303
|
+
tsIgnoreCount 1
|
|
304
|
+
|
|
305
|
+
Duration 0.04s exit code 0
|
|
306
|
+
`,
|
|
307
|
+
stderr: "",
|
|
308
|
+
exitCode: 0,
|
|
309
|
+
});
|
|
310
|
+
// `show` only reads.
|
|
311
|
+
expect(protocol(docker.commands())).toEqual(opened(1, STALE_RUN));
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("remeasure <execution> takes each of a run's missing measures from its postrun image, one context each, in name order", async () => {
|
|
315
|
+
const seeded = await seededDataDir();
|
|
316
|
+
const docker = await savedPostruns();
|
|
317
|
+
const from = Date.now();
|
|
318
|
+
expect(await thunderjar(fixtures, ["remeasure", OLDEST, "--data-dir", seeded.dir], docker.env)).toEqual({
|
|
319
|
+
stdout: `Execution ${OLDEST} (4 container runs)
|
|
320
|
+
Current measurements for task is-prime:
|
|
321
|
+
isPrimeTemplateTest new / changed (e4cf4e5c)
|
|
322
|
+
isPrimeTsExists new / changed (e514c726)
|
|
323
|
+
tsIgnoreCount new / changed (383b2dc3)
|
|
324
|
+
readmeExists deleted from task; 3 stored measures kept, excluded from aggregates
|
|
325
|
+
|
|
326
|
+
Measuring from each postrun image:
|
|
327
|
+
8ffdda6e i01 isPrimeTemplateTest {passed: 3, failed: 2}
|
|
328
|
+
8ffdda6e i01 isPrimeTsExists true
|
|
329
|
+
8ffdda6e i01 tsIgnoreCount 1
|
|
330
|
+
|
|
331
|
+
Updated 1 container run. Recomputed 2 aggregation results.
|
|
332
|
+
${FOOTER}
|
|
333
|
+
`,
|
|
334
|
+
stderr: "",
|
|
335
|
+
exitCode: 0,
|
|
336
|
+
});
|
|
337
|
+
const to = Date.now();
|
|
338
|
+
const after = read(seeded.store);
|
|
339
|
+
takenBetween(after, UNMEASURED_RUN, Object.keys(HASH), from, to);
|
|
340
|
+
expect(aggregatesOf(after, OLDEST)).toEqual(OLDEST_BACKFILLED);
|
|
341
|
+
expect(protocol(docker.commands())).toEqual([1, 2, 3].flatMap((n) => opened(n, UNMEASURED_RUN)));
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("remeasure <execution> with every measure current reports each up to date and nothing to do, opens no context, and still recomputes both its aggregates", async () => {
|
|
345
|
+
const seeded = await seededDataDir();
|
|
346
|
+
const docker = await savedPostruns();
|
|
347
|
+
expect(await thunderjar(fixtures, ["remeasure", LATEST, "--data-dir", seeded.dir], docker.env)).toEqual({
|
|
348
|
+
stdout: `Execution ${LATEST} (4 container runs)
|
|
349
|
+
Current measurements for task is-prime:
|
|
350
|
+
isPrimeTemplateTest up to date
|
|
351
|
+
isPrimeTsExists up to date
|
|
352
|
+
tsIgnoreCount up to date
|
|
353
|
+
|
|
354
|
+
Already current, nothing to do.
|
|
355
|
+
Updated 0 container runs. Recomputed 2 aggregation results.
|
|
356
|
+
${FOOTER}
|
|
357
|
+
`,
|
|
358
|
+
stderr: "",
|
|
359
|
+
exitCode: 0,
|
|
360
|
+
});
|
|
361
|
+
// Recomputed from the same Results by the same measurements, so they're as stored.
|
|
362
|
+
expect(read(seeded.store)).toEqual(starting);
|
|
363
|
+
expect(docker.commands()).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("remeasure <test case> backfills every execution of it, the run that exited 3 among them, and recomputes both permutations of each", async () => {
|
|
367
|
+
const seeded = await seededDataDir();
|
|
368
|
+
const docker = await savedPostruns();
|
|
369
|
+
expect(aggregatesOf(starting, OLDEST)).toEqual([]);
|
|
370
|
+
const from = Date.now();
|
|
371
|
+
expect(await thunderjar(fixtures, ["remeasure", "fails", "--data-dir", seeded.dir], docker.env)).toEqual({
|
|
372
|
+
stdout: `fails has 3 executions (12 container runs)
|
|
373
|
+
${LATEST} 4 runs 2026-09-30 (already current, nothing to do)
|
|
374
|
+
${ERRORED} 4 runs 2026-09-29
|
|
375
|
+
${OLDEST} 4 runs 2026-09-28
|
|
376
|
+
|
|
377
|
+
Measuring 2 container runs... done
|
|
378
|
+
Updated 2 container runs. Recomputed 6 aggregation results.
|
|
379
|
+
${FOOTER}
|
|
380
|
+
`,
|
|
381
|
+
stderr: "",
|
|
382
|
+
exitCode: 0,
|
|
383
|
+
});
|
|
384
|
+
const to = Date.now();
|
|
385
|
+
|
|
386
|
+
const after = read(seeded.store);
|
|
387
|
+
takenBetween(after, STALE_RUN, ["isPrimeTemplateTest"], from, to);
|
|
388
|
+
takenBetween(after, UNMEASURED_RUN, Object.keys(HASH), from, to);
|
|
389
|
+
// The latest execution's records, and every untouched measure and run's metadata, are as they were.
|
|
390
|
+
expect(after.containerRuns).toEqual(
|
|
391
|
+
withMeasures(starting.containerRuns, {
|
|
392
|
+
[STALE_RUN]: { isPrimeTemplateTest: taken("isPrimeTemplateTest", "templateTest", { passed: 3, failed: 2 }) },
|
|
393
|
+
[UNMEASURED_RUN]: {
|
|
394
|
+
isPrimeTemplateTest: taken("isPrimeTemplateTest", "templateTest", { passed: 3, failed: 2 }),
|
|
395
|
+
isPrimeTsExists: taken("isPrimeTsExists", "fileCreated", true),
|
|
396
|
+
tsIgnoreCount: taken("tsIgnoreCount", "grep", 1),
|
|
397
|
+
},
|
|
398
|
+
}),
|
|
399
|
+
);
|
|
400
|
+
expect(after.containerRuns.find((r) => r.postrunImage === UNMEASURED_RUN)!.execution.exitCode).toBe(3);
|
|
401
|
+
expect(aggregatesOf(after, OLDEST)).toEqual(OLDEST_BACKFILLED);
|
|
402
|
+
expect(aggregatesOf(after, ERRORED)).toEqual(ERRORED_RECOMPUTED);
|
|
403
|
+
expect(aggregatesOf(after, LATEST)).toEqual(aggregatesOf(starting, LATEST));
|
|
404
|
+
expect(aggregatesOf(after, TIMED_OUT)).toEqual(aggregatesOf(starting, TIMED_OUT));
|
|
405
|
+
// Executions newest first, each run's measurements in name order.
|
|
406
|
+
expect(protocol(docker.commands())).toEqual([
|
|
407
|
+
...opened(1, STALE_RUN),
|
|
408
|
+
...opened(2, UNMEASURED_RUN),
|
|
409
|
+
...opened(3, UNMEASURED_RUN),
|
|
410
|
+
...opened(4, UNMEASURED_RUN),
|
|
411
|
+
]);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("remeasure again against the store it updated has nothing to do, opens no context, and leaves the store byte for byte as it was", async () => {
|
|
415
|
+
for (const [target, again] of [
|
|
416
|
+
[
|
|
417
|
+
ERRORED,
|
|
418
|
+
`Execution ${ERRORED} (4 container runs)
|
|
419
|
+
Current measurements for task is-prime:
|
|
420
|
+
isPrimeTemplateTest up to date
|
|
421
|
+
isPrimeTsExists up to date
|
|
422
|
+
tsIgnoreCount up to date
|
|
423
|
+
readmeExists deleted from task; 4 stored measures kept, excluded from aggregates
|
|
424
|
+
|
|
425
|
+
Already current, nothing to do.
|
|
426
|
+
Updated 0 container runs. Recomputed 2 aggregation results.
|
|
427
|
+
${FOOTER}
|
|
428
|
+
`,
|
|
429
|
+
],
|
|
430
|
+
[
|
|
431
|
+
"fails",
|
|
432
|
+
`fails has 3 executions (12 container runs)
|
|
433
|
+
${LATEST} 4 runs 2026-09-30 (already current, nothing to do)
|
|
434
|
+
${ERRORED} 4 runs 2026-09-29 (already current, nothing to do)
|
|
435
|
+
${OLDEST} 4 runs 2026-09-28 (already current, nothing to do)
|
|
436
|
+
|
|
437
|
+
Already current, nothing to do.
|
|
438
|
+
Updated 0 container runs. Recomputed 6 aggregation results.
|
|
439
|
+
${FOOTER}
|
|
440
|
+
`,
|
|
441
|
+
],
|
|
442
|
+
] as const) {
|
|
443
|
+
const seeded = await seededDataDir();
|
|
444
|
+
const docker = await savedPostruns();
|
|
445
|
+
const args = ["remeasure", target, "--data-dir", seeded.dir];
|
|
446
|
+
expect((await thunderjar(fixtures, args, docker.env)).exitCode).toBe(0);
|
|
447
|
+
const once = readTextSync(seeded.store);
|
|
448
|
+
const commands = docker.commands();
|
|
449
|
+
|
|
450
|
+
expect({ target, ...(await thunderjar(fixtures, args, docker.env)) }).toEqual({
|
|
451
|
+
target,
|
|
452
|
+
stdout: again,
|
|
453
|
+
stderr: "",
|
|
454
|
+
exitCode: 0,
|
|
455
|
+
});
|
|
456
|
+
expect(readTextSync(seeded.store)).toBe(once);
|
|
457
|
+
expect(docker.commands()).toEqual(commands);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
test("remeasure <test case> keeps runs whose agent exited non-zero: times-out's has nothing to measure, opens no context, and its duration is still aggregated", async () => {
|
|
462
|
+
const seeded = await seededDataDir();
|
|
463
|
+
const docker = await savedPostruns();
|
|
464
|
+
const expected = {
|
|
465
|
+
stdout: `times-out has 1 execution (2 container runs)
|
|
466
|
+
${TIMED_OUT} 2 runs 2026-09-29 (already current, nothing to do)
|
|
467
|
+
|
|
468
|
+
Already current, nothing to do.
|
|
469
|
+
Updated 0 container runs. Recomputed 1 aggregation result.
|
|
470
|
+
${FOOTER}
|
|
471
|
+
`,
|
|
472
|
+
stderr: "",
|
|
473
|
+
exitCode: 0,
|
|
474
|
+
};
|
|
475
|
+
const args = ["remeasure", "times-out", "--data-dir", seeded.dir];
|
|
476
|
+
expect(await thunderjar(fixtures, args, docker.env)).toEqual(expected);
|
|
477
|
+
const once = readTextSync(seeded.store);
|
|
478
|
+
const after = read(seeded.store);
|
|
479
|
+
expect(aggregatesOf(after, TIMED_OUT)).toEqual([
|
|
480
|
+
{ executionId: TIMED_OUT, permutationHash: WAITING, iterations: 2, measures: {}, duration: DURATION },
|
|
481
|
+
]);
|
|
482
|
+
expect(after.containerRuns).toEqual(starting.containerRuns);
|
|
483
|
+
|
|
484
|
+
expect(await thunderjar(fixtures, args, docker.env)).toEqual(expected);
|
|
485
|
+
expect(readTextSync(seeded.store)).toBe(once);
|
|
486
|
+
expect(docker.commands()).toEqual([]);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
test("fails for a target that is neither a recorded execution nor a test case, a postrun tag among them", async () => {
|
|
490
|
+
const docker = await savedPostruns();
|
|
491
|
+
for (const target of ["is-even", "postrun-e01k4x9j2e8mqz3abcdefghjkmn-h8ffdda6e-i00"]) {
|
|
492
|
+
expect(await thunderjar(fixtures, ["remeasure", target], docker.env)).toEqual({
|
|
493
|
+
stdout: "",
|
|
494
|
+
stderr: `${target} is not a recorded execution or test case\n`,
|
|
495
|
+
exitCode: 1,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
expect(docker.commands()).toEqual([]);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("remeasure --data-dir reads only the store there: an empty one has no executions, and isn't written", async () => {
|
|
502
|
+
const empty = await makeTempDir("thunderjar-remeasure-");
|
|
503
|
+
disposable.push(empty);
|
|
504
|
+
const docker = await savedPostruns();
|
|
505
|
+
expect(await thunderjar(fixtures, ["remeasure", ERRORED, "--data-dir", empty], docker.env)).toEqual({
|
|
506
|
+
stdout: "",
|
|
507
|
+
stderr: `${ERRORED} is not a recorded execution or test case\n`,
|
|
508
|
+
exitCode: 1,
|
|
509
|
+
});
|
|
510
|
+
expect(existsSync(join(empty, "runs.json"))).toBe(false);
|
|
511
|
+
expect(docker.commands()).toEqual([]);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("says what's wrong, then its usage, for a missing target, extra arguments, an unknown flag, or a bad --data-dir", async () => {
|
|
515
|
+
const seeded = await seededDataDir();
|
|
516
|
+
const before = readTextSync(seeded.store);
|
|
517
|
+
const docker = await savedPostruns();
|
|
518
|
+
for (const [args, error] of [
|
|
519
|
+
[["remeasure"], "error: missing required argument 'target'"],
|
|
520
|
+
[["remeasure", ERRORED, "fails"], "error: too many arguments for 'remeasure'"],
|
|
521
|
+
[["remeasure", ERRORED, "--force"], "error: unknown option '--force'"],
|
|
522
|
+
[["remeasure", "--json", ERRORED], "error: unknown option '--json'"],
|
|
523
|
+
[["remeasure", ERRORED, "--data-dir"], "error: option '--data-dir <dir>' argument missing"],
|
|
524
|
+
[["remeasure", "--data-dir", ERRORED], "error: missing required argument 'target'"],
|
|
525
|
+
[["remeasure", ERRORED, "--data-dir", dataDir, "--data-dir", dataDir], "It may only be given once."],
|
|
526
|
+
[["remeasure", ERRORED, "--data-dir", seeded.dir, "--force"], "error: unknown option '--force'"],
|
|
527
|
+
] as const) {
|
|
528
|
+
const result = await thunderjar(fixtures, [...args], docker.env);
|
|
529
|
+
expect({ args, stdout: result.stdout, exitCode: result.exitCode }).toEqual({ args, stdout: "", exitCode: 1 });
|
|
530
|
+
expect(result.stderr).toContain(error);
|
|
531
|
+
expect(result.stderr).toContain("Usage: thunderjar remeasure [options] <target>");
|
|
532
|
+
}
|
|
533
|
+
expect(readTextSync(seeded.store)).toBe(before);
|
|
534
|
+
expect(docker.commands()).toEqual([]);
|
|
535
|
+
});
|
|
536
|
+
});
|