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.
Files changed (77) hide show
  1. package/dist/cli.js +2161 -34
  2. package/package.json +13 -4
  3. package/src/AGENTS.md +156 -0
  4. package/src/adapters/docker/docker.ts +117 -0
  5. package/src/api/declarations.ts +279 -0
  6. package/src/api/fragments.ts +30 -0
  7. package/src/api/parameter-names.d.ts +16 -0
  8. package/src/cli/commands/build/build.docker.test.ts +173 -0
  9. package/src/cli/commands/build/build.test.ts +234 -0
  10. package/src/cli/commands/build/build.ts +115 -0
  11. package/src/cli/commands/declarations.typecheck.test.ts +173 -0
  12. package/src/cli/commands/executions/executions.test.ts +84 -0
  13. package/src/cli/commands/executions/executions.ts +31 -0
  14. package/src/cli/commands/format.ts +33 -0
  15. package/src/cli/commands/generate/generate.test.ts +101 -0
  16. package/src/cli/commands/generate/generate.ts +18 -0
  17. package/src/cli/commands/generate/parameter-names.ts +131 -0
  18. package/src/cli/commands/init/init.test.ts +123 -0
  19. package/src/cli/commands/init/init.ts +189 -0
  20. package/src/cli/commands/options.ts +17 -0
  21. package/src/cli/commands/plan/plan.test.ts +168 -0
  22. package/src/cli/commands/plan/plan.ts +72 -0
  23. package/src/cli/commands/remeasure/remeasure.test.ts +536 -0
  24. package/src/cli/commands/remeasure/remeasure.ts +194 -0
  25. package/src/cli/commands/run/run.test.ts +622 -0
  26. package/src/cli/commands/run/run.ts +203 -0
  27. package/src/cli/commands/show/show.test.ts +288 -0
  28. package/src/cli/commands/show/show.ts +252 -0
  29. package/src/cli/commands/validation.test.ts +200 -0
  30. package/src/cli/index.test.ts +47 -0
  31. package/src/cli/index.ts +9 -0
  32. package/src/cli/program.ts +29 -0
  33. package/src/images/prerun-builds.ts +108 -0
  34. package/src/images/prerun.test.ts +243 -0
  35. package/src/images/prerun.ts +272 -0
  36. package/src/images/repository.ts +72 -0
  37. package/src/index.ts +24 -0
  38. package/src/instruments/instruments.test.ts +240 -0
  39. package/src/instruments/instruments.ts +205 -0
  40. package/src/matrix/execution.ts +122 -0
  41. package/src/matrix/hashing.test.ts +298 -0
  42. package/src/matrix/hashing.ts +131 -0
  43. package/src/matrix/permutations.test.ts +58 -0
  44. package/src/matrix/permutations.ts +50 -0
  45. package/src/matrix/resolve.ts +63 -0
  46. package/src/matrix/tags.test.ts +15 -0
  47. package/src/matrix/tags.ts +22 -0
  48. package/src/matrix/ulid.test.ts +28 -0
  49. package/src/matrix/ulid.ts +22 -0
  50. package/src/measuring/aggregate.test.ts +188 -0
  51. package/src/measuring/aggregate.ts +154 -0
  52. package/src/measuring/measure.test.ts +105 -0
  53. package/src/measuring/measure.ts +62 -0
  54. package/src/measuring/remeasure-templates/v1/isPrime.test.template.txt +4 -0
  55. package/src/measuring/remeasure-templates/v2/isPrime.test.template.txt +5 -0
  56. package/src/measuring/remeasure.test.ts +352 -0
  57. package/src/measuring/remeasure.ts +113 -0
  58. package/src/platform/fs.test.ts +89 -0
  59. package/src/platform/fs.ts +123 -0
  60. package/src/platform/path.test.ts +38 -0
  61. package/src/platform/path.ts +72 -0
  62. package/src/platform/signals.test.ts +25 -0
  63. package/src/platform/signals.ts +68 -0
  64. package/src/project/config.test.ts +68 -0
  65. package/src/project/config.ts +297 -0
  66. package/src/running/runner.test.ts +73 -0
  67. package/src/running/runner.ts +252 -0
  68. package/src/store/run-data-store.test.ts +232 -0
  69. package/src/store/run-data-store.ts +254 -0
  70. package/src/testing/cli.ts +65 -0
  71. package/src/testing/fake-docker.test.ts +197 -0
  72. package/src/testing/fake-docker.ts +104 -0
  73. package/src/testing/fixture-project.ts +177 -0
  74. package/src/testing/measurement-context.ts +5 -0
  75. package/src/testing/seed-repo.test.ts +10 -0
  76. package/src/testing/seed-repo.ts +83 -0
  77. package/src/testing/test-setup.ts +10 -0
@@ -0,0 +1,194 @@
1
+ // `thunderjar remeasure <test case | execution>` — spec/100-cli-reference.md#remeasure. Applies
2
+ // today's measurements to past container runs without rerunning the agent, printing what it did
3
+ // (spec/110-cli-report-visualization.md#remeasure). An execution prints each of its task's
4
+ // current measurements, up to date or new / changed, each name deleted from the task with how
5
+ // many measures of it are kept, then each measure it took. A test case prints every execution of
6
+ // it, not only its latest as `show` does, and which of them have nothing to do, then how many
7
+ // runs it measured.
8
+ //
9
+ // Each run takes only its missing or stale measures (spec/085-test-case-results.md#backfilling-measurements),
10
+ // each from a context over its postrun image, as `run` opens them, and they're merged into its
11
+ // stored Result, so every other measure, a deleted one among them, keeps its value and
12
+ // `measuredAt`. Then every aggregation result of each execution it was given is recomputed and
13
+ // stored from the Results now stored, even with nothing to measure, so an edited `aggregate` or
14
+ // a deleted measurement is picked up (delivery story 2400).
15
+ //
16
+ // The argument is tried as an execution ID, then a test case name, as `show` resolves it; a
17
+ // postrun tag isn't a target. Each execution is planned, measured and aggregated against its
18
+ // stored task's measurements, not today's test case declaration. `--data-dir` reads and writes a
19
+ // store kept outside the project.
20
+
21
+ import { Command } from "commander";
22
+ import { dockerCli } from "../../../adapters/docker/docker.ts";
23
+ import { LOCAL_REPOSITORY } from "../../../images/prerun.ts";
24
+ import { abbreviateHash } from "../../../matrix/hashing.ts";
25
+ import { recomputeAggregationResult } from "../../../measuring/aggregate.ts";
26
+ import { measureContainerRun } from "../../../measuring/measure.ts";
27
+ import { planRemeasure, type ExecutionSummary, type RemeasurePlan } from "../../../measuring/remeasure.ts";
28
+ import { findThunderjarRoot, loadMeasurements } from "../../../project/config.ts";
29
+ import { createDockerMeasurementOpener, type MeasurementOpener } from "../../../running/runner.ts";
30
+ import {
31
+ jsonFileStore,
32
+ runDataStorePath,
33
+ type ContainerRunRecord,
34
+ type ExecutionSummary as RecordedExecution,
35
+ type Result,
36
+ type RunDataStore,
37
+ } from "../../../store/run-data-store.ts";
38
+ import { count, showOutcome } from "../format.ts";
39
+ import { dataDirOption } from "../options.ts";
40
+
41
+ /** A run that was measured, and the measures it took. */
42
+ interface Measured {
43
+ record: ContainerRunRecord;
44
+ result: Result;
45
+ }
46
+
47
+ /** How every report ends: what was updated and recomputed, and that no agent ran. */
48
+ const footer = (updated: number, recomputed: number) => [
49
+ `Updated ${count(updated, "container run")}. Recomputed ${count(recomputed, "aggregation result")}.`,
50
+ "The agent was not rerun.",
51
+ ];
52
+
53
+ const NOTHING_TO_DO = "Already current, nothing to do.";
54
+
55
+ /** Each measure taken, in run order: `8ffdda6e i00 isPrimeTemplateTest {passed: 3, failed: 2}`. */
56
+ function measuredLines(measured: Measured[]) {
57
+ const taken = measured.flatMap(({ record, result }) => Object.entries(result).map((entry) => ({ record, entry })));
58
+ const width = Math.max(...taken.map(({ entry: [name] }) => name.length));
59
+ return taken.map(
60
+ ({ record: { permutationHash, iteration }, entry: [name, { outcome }] }) =>
61
+ ` ${abbreviateHash(permutationHash)} i${String(iteration).padStart(2, "0")} ${name.padEnd(width)} ${showOutcome(outcome)}`,
62
+ );
63
+ }
64
+
65
+ /** An execution's plan: its header, a row per measurement name, current ones first, in one column; then what it took. */
66
+ function executionReport(
67
+ task: string,
68
+ containerRuns: number,
69
+ { executionId, measurements, deleted }: ExecutionSummary,
70
+ measured: Measured[],
71
+ recomputed: number,
72
+ ) {
73
+ const rows: [name: string, status: string][] = [
74
+ ...measurements.map(({ name, contentHash, status }): [string, string] => [
75
+ name,
76
+ status === "current" ? "up to date" : `new / changed (${abbreviateHash(contentHash)})`,
77
+ ]),
78
+ ...deleted.map(({ name, storedMeasures }): [string, string] => [
79
+ name,
80
+ `deleted from task; ${count(storedMeasures, "stored measure")} kept, excluded from aggregates`,
81
+ ]),
82
+ ];
83
+ const width = Math.max(...rows.map(([name]) => name.length));
84
+ return [
85
+ `Execution ${executionId} (${count(containerRuns, "container run")})`,
86
+ `Current measurements for task ${task}:`,
87
+ ...rows.map(([name, status]) => ` ${name.padEnd(width)} ${status}`),
88
+ "",
89
+ ...(measured.length === 0
90
+ ? [NOTHING_TO_DO]
91
+ : ["Measuring from each postrun image:", ...measuredLines(measured), ""]),
92
+ ...footer(measured.length, recomputed),
93
+ ].join("\n");
94
+ }
95
+
96
+ /** A test case's executions, as listed: a row each with its runs and day, marking those with nothing to take; then how many runs it measured. */
97
+ function testCaseReport(
98
+ testCase: string,
99
+ executions: (RecordedExecution & { plan: RemeasurePlan })[],
100
+ updated: number,
101
+ recomputed: number,
102
+ ) {
103
+ const rows = executions.map(({ executionId, containerRuns, ranAt, plan }) => ({
104
+ executionId,
105
+ runs: count(containerRuns, "run"),
106
+ day: ranAt.slice(0, 10),
107
+ current: plan.runs.length === 0,
108
+ }));
109
+ const width = Math.max(...rows.map(({ runs }) => runs.length));
110
+ const total = executions.reduce((sum, { containerRuns }) => sum + containerRuns, 0);
111
+ return [
112
+ `${testCase} has ${count(executions.length, "execution")} (${count(total, "container run")})`,
113
+ ...rows.map(
114
+ ({ executionId, runs, day, current }) =>
115
+ ` ${executionId} ${runs.padEnd(width)} ${day}${current ? " (already current, nothing to do)" : ""}`,
116
+ ),
117
+ "",
118
+ updated === 0 ? NOTHING_TO_DO : `Measuring ${count(updated, "container run")}... done`,
119
+ ...footer(updated, recomputed),
120
+ ].join("\n");
121
+ }
122
+
123
+ /**
124
+ * Plans a recorded execution against the current measurements of the task it was run with, takes
125
+ * each planned run's measurements through `opener` and merges them into its stored Result, then
126
+ * recomputes and stores each of its permutations' aggregation results from the Results now stored.
127
+ * Gives its records, its plan, the runs measured, and how many aggregation results were recomputed.
128
+ */
129
+ async function remeasureExecution(cwd: string, store: RunDataStore, opener: MeasurementOpener, executionId: string) {
130
+ const records = await store.getExecution(executionId);
131
+ const measurements = await loadMeasurements(findThunderjarRoot(cwd), records[0]!.task);
132
+ const plan = planRemeasure(records, measurements);
133
+
134
+ const measured: Measured[] = [];
135
+ for (const { postrunImage, measurementNames } of plan.runs) {
136
+ const record = records.find((r) => r.postrunImage === postrunImage)!;
137
+ const planned = measurementNames.map((name) => measurements.find((m) => m.name === name)!);
138
+ const result = await measureContainerRun(planned, () => opener.openMeasurementContext(record.postrunImage));
139
+ await store.recordMeasures(record.postrunImage, result);
140
+ measured.push({ record, result });
141
+ }
142
+
143
+ const permutationHashes = new Set(records.map(({ permutationHash }) => permutationHash));
144
+ for (const permutationHash of permutationHashes) {
145
+ await recomputeAggregationResult(store, executionId, permutationHash, measurements);
146
+ }
147
+ return { records, plan, measured, recomputed: permutationHashes.size };
148
+ }
149
+
150
+ export async function remeasure(
151
+ target: string,
152
+ { cwd = process.cwd(), dataDir }: { cwd?: string; dataDir?: string } = {},
153
+ ): Promise<string> {
154
+ const store = jsonFileStore(runDataStorePath(cwd, dataDir));
155
+ // As `run` opens them: a scratch copy of each postrun image's files, measured on the host.
156
+ const opener = createDockerMeasurementOpener(dockerCli(), LOCAL_REPOSITORY);
157
+
158
+ if ((await store.getExecution(target)).length > 0) {
159
+ const { records, plan, measured, recomputed } = await remeasureExecution(cwd, store, opener, target);
160
+ return executionReport(records[0]!.task, records.length, plan.executions[0]!, measured, recomputed);
161
+ }
162
+
163
+ // Every execution of the test case, newest first, where `show` takes only the latest.
164
+ const executions = await store.listExecutions(target);
165
+ if (executions.length > 0) {
166
+ const planned = [];
167
+ let updated = 0;
168
+ let recomputed = 0;
169
+ for (const execution of executions) {
170
+ const { plan, measured, recomputed: ofExecution } = await remeasureExecution(
171
+ cwd,
172
+ store,
173
+ opener,
174
+ execution.executionId,
175
+ );
176
+ planned.push({ ...execution, plan });
177
+ updated += measured.length;
178
+ recomputed += ofExecution;
179
+ }
180
+ return testCaseReport(target, planned, updated, recomputed);
181
+ }
182
+
183
+ throw new Error(`${target} is not a recorded execution or test case`);
184
+ }
185
+
186
+ export function createRemeasureCommand(): Command {
187
+ return new Command("remeasure")
188
+ .description("Apply the current measurements to past runs.")
189
+ .argument("<target>", "an execution ID, or a test case, meaning every execution of it")
190
+ .addOption(dataDirOption())
191
+ .action(async (target: string, options: { dataDir?: string }) => {
192
+ console.log(await remeasure(target, { dataDir: options.dataDir }));
193
+ });
194
+ }