@alecsibilia/luca 14.0.0-alpha.2 → 14.0.0-alpha.21
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/CHANGELOG.md +76 -0
- package/README.md +7 -2
- package/board/client/board-look.ts +31 -1
- package/board/client/board-panel-parts.tsx +150 -0
- package/board/client/board-panel-stages.ts +99 -0
- package/board/client/board-panel-styles.ts +223 -0
- package/board/client/board-panel.tsx +77 -1275
- package/board/client/engine-life.ts +48 -0
- package/board/client/final-review-stack.tsx +144 -0
- package/board/client/heartbeat.tsx +61 -0
- package/board/client/helper-lines.tsx +69 -0
- package/board/client/live-feed.tsx +86 -0
- package/board/client/live-line.ts +73 -0
- package/board/client/model-line.tsx +178 -0
- package/board/client/needs-you-section.tsx +133 -0
- package/board/client/one-line.tsx +193 -0
- package/board/client/repo-wait-banner.tsx +69 -0
- package/board/client/run-footer.tsx +68 -0
- package/board/client/run-header.tsx +189 -0
- package/board/client/run-picker.tsx +98 -0
- package/board/client/status-bar.tsx +131 -0
- package/board/client/step-dots.tsx +127 -0
- package/board/client/stuck-actions.tsx +269 -0
- package/board/client/ticket-card-view.tsx +238 -0
- package/board/client/ticket-detail.tsx +87 -0
- package/board/client/ticket-stack.tsx +119 -0
- package/board/client/ticket-wait.ts +188 -0
- package/board/client/timeline-rows.tsx +41 -14
- package/board/client/usage-line.tsx +51 -0
- package/board/client/wait-line.tsx +39 -0
- package/board/index.client.tsx +1 -1
- package/board/index.server.ts +7 -1
- package/board/package.json +1 -1
- package/board/server/board-panel-state-helpers.ts +67 -0
- package/board/server/board-server-check.ts +294 -0
- package/board/server/board-server-engine.ts +129 -0
- package/board/server/board-server-events.ts +134 -0
- package/board/server/board-server-read.ts +190 -0
- package/board/server/board-server-resume.ts +164 -0
- package/board/server/board-server-start.ts +174 -0
- package/board/server/board-server-store.ts +281 -0
- package/board/server/board-server-types.ts +114 -0
- package/board/server/board-server-util.ts +4 -0
- package/board/server/board-server.ts +52 -943
- package/board/server/board-vocabulary-engine.ts +144 -0
- package/board/server/board-vocabulary-review.ts +115 -0
- package/board/server/board-vocabulary-run.ts +145 -0
- package/board/server/board-vocabulary-shapes.ts +153 -0
- package/board/server/board-vocabulary.ts +20 -375
- package/board/server/current-step-panel-helpers.ts +108 -0
- package/board/server/describe-record.ts +403 -0
- package/board/server/engine-launch.ts +20 -3
- package/board/server/engine-restart-helpers.ts +105 -0
- package/board/server/engine-watch.ts +45 -5
- package/board/server/journal-rebuild-helpers.ts +169 -0
- package/board/server/live-file.ts +130 -0
- package/board/server/luca-version.ts +1 -1
- package/board/server/make-rows.ts +14 -629
- package/board/server/record-texts.ts +350 -0
- package/board/server/reduce-board-agent-start.ts +224 -0
- package/board/server/reduce-board-agents.ts +160 -0
- package/board/server/reduce-board-counters.ts +138 -0
- package/board/server/reduce-board-create.ts +148 -0
- package/board/server/reduce-board-final-review.ts +174 -0
- package/board/server/reduce-board-needs-you.ts +283 -0
- package/board/server/reduce-board-reasons.ts +59 -0
- package/board/server/reduce-board-run.ts +255 -0
- package/board/server/reduce-board-settle.ts +88 -0
- package/board/server/reduce-board-steps.ts +226 -0
- package/board/server/reduce-board-text.ts +139 -0
- package/board/server/reduce-board-ticket-checks.ts +168 -0
- package/board/server/reduce-board-ticket-join.ts +144 -0
- package/board/server/reduce-board-ticket-start.ts +132 -0
- package/board/server/reduce-board-update.ts +117 -0
- package/board/server/reduce-board-usage.ts +247 -0
- package/board/server/reduce-board.ts +100 -1917
- package/board/server/reply-post.ts +407 -0
- package/board/server/run-registry.ts +8 -4
- package/board/server/ticket-stages-panel-helpers.ts +296 -0
- package/board/shared/board-rows.ts +9 -0
- package/board/shared/board-rpc.ts +42 -0
- package/board/shared/board-state-counts.ts +77 -0
- package/board/shared/board-state-live.ts +99 -0
- package/board/shared/board-state-review.ts +59 -0
- package/board/shared/board-state-run.ts +236 -0
- package/board/shared/board-state-ticket.ts +257 -0
- package/board/shared/board-state-usage.ts +107 -0
- package/board/shared/board-state.ts +97 -567
- package/board/shared/reply-actions.ts +207 -0
- package/engine/agents/agent-launcher.ts +27 -4
- package/engine/agents/claude-launcher-helpers.ts +233 -0
- package/engine/agents/claude-launcher.ts +28 -669
- package/engine/agents/claude-messages.ts +212 -0
- package/engine/agents/claude-options.ts +43 -8
- package/engine/agents/common-instructions.ts +138 -0
- package/engine/agents/input-channel.ts +36 -0
- package/engine/agents/launch-paths.ts +29 -0
- package/engine/agents/launch-session.ts +227 -0
- package/engine/agents/learner-instructions.ts +31 -0
- package/engine/agents/open-session.ts +210 -0
- package/engine/agents/prompt-sections.ts +95 -0
- package/engine/agents/rejoin-prompts.ts +135 -0
- package/engine/agents/reviewer-instructions.ts +99 -0
- package/engine/agents/role-instructions.ts +31 -244
- package/engine/agents/role-prompts.ts +39 -109
- package/engine/agents/role-results.ts +106 -7
- package/engine/agents/scripted-launcher.ts +21 -6
- package/engine/agents/session-pool.ts +124 -0
- package/engine/agents/turn-progress.ts +283 -0
- package/engine/agents/writer-instructions.ts +151 -0
- package/engine/cli/computer-adapters-real.ts +15 -3
- package/engine/cli/computer-adapters.ts +2 -0
- package/engine/cli/computer-checks.ts +87 -4
- package/engine/cli/config-reload-text.ts +39 -0
- package/engine/cli/decision-model-setup.ts +70 -0
- package/engine/cli/doctor-command.ts +1 -0
- package/engine/cli/doctor.ts +21 -3
- package/engine/cli/going-runs.ts +192 -36
- package/engine/cli/init-command.ts +1 -0
- package/engine/cli/init.ts +23 -1
- package/engine/cli/live-runs.ts +73 -0
- package/engine/cli/luca-run.ts +42 -7
- package/engine/cli/luca-skills.ts +183 -0
- package/engine/cli/luca.ts +8 -1
- package/engine/cli/models-command.ts +119 -0
- package/engine/cli/read-run.ts +81 -0
- package/engine/cli/run-args.ts +33 -6
- package/engine/cli/run-demo.ts +143 -0
- package/engine/cli/run-end.ts +184 -0
- package/engine/cli/run-modes-helpers.ts +81 -0
- package/engine/cli/run-modes.ts +62 -439
- package/engine/cli/setup.ts +5 -48
- package/engine/cli/unfinished-runs.ts +122 -0
- package/engine/cli/upgrade-command.ts +212 -30
- package/engine/cli/upgrade-finish.ts +149 -0
- package/engine/cli/upgrade.ts +112 -71
- package/engine/config/config-fields.ts +127 -0
- package/engine/config/config-reload.ts +250 -0
- package/engine/config/engine-config-schema.ts +235 -0
- package/engine/config/engine-config.ts +72 -142
- package/engine/config/test-commands.ts +102 -0
- package/engine/core/agent-guards-helpers.ts +84 -0
- package/engine/core/agent-turn.ts +395 -0
- package/engine/core/already-done.ts +44 -1
- package/engine/core/build-action.ts +214 -0
- package/engine/core/build-context.ts +135 -0
- package/engine/core/build-launch.ts +259 -0
- package/engine/core/commit-in.ts +282 -0
- package/engine/core/decide-build-early.ts +108 -0
- package/engine/core/decide-build-ending.ts +145 -0
- package/engine/core/decide-build-helpers.ts +87 -0
- package/engine/core/decide-build-tickets.ts +207 -0
- package/engine/core/decide-build-view.ts +103 -0
- package/engine/core/decide-build.ts +101 -1368
- package/engine/core/decide-final-fix.ts +203 -0
- package/engine/core/decide-final-review.ts +55 -310
- package/engine/core/decide-red-green.ts +398 -0
- package/engine/core/decide-rejoin.ts +259 -0
- package/engine/core/decide-repo-wait.ts +129 -0
- package/engine/core/decide-run-budget.ts +86 -12
- package/engine/core/decide-stuck-helpers.ts +127 -0
- package/engine/core/decide-stuck.ts +60 -8
- package/engine/core/decide-ticket-review.ts +95 -0
- package/engine/core/decide-ticket.ts +267 -0
- package/engine/core/decide-waits.ts +47 -0
- package/engine/core/decide.ts +74 -23
- package/engine/core/engine-chores.ts +163 -0
- package/engine/core/environment-rerun.ts +83 -0
- package/engine/core/evidence-text.ts +84 -0
- package/engine/core/execute-action.ts +303 -0
- package/engine/core/execute-already-done-check.ts +133 -0
- package/engine/core/execute-build.ts +68 -944
- package/engine/core/execute-changeset.ts +5 -7
- package/engine/core/execute-final-review.ts +125 -13
- package/engine/core/execute-gates.ts +182 -0
- package/engine/core/execute-intake.ts +130 -0
- package/engine/core/execute-join.ts +276 -0
- package/engine/core/execute-memory.ts +12 -0
- package/engine/core/execute-pull-request.ts +84 -0
- package/engine/core/execute-red-check.ts +219 -0
- package/engine/core/execute-repo-wait.ts +129 -0
- package/engine/core/execute-stuck.ts +25 -10
- package/engine/core/execute-waits.ts +158 -0
- package/engine/core/execute-with-jev.ts +118 -0
- package/engine/core/execute.ts +124 -831
- package/engine/core/final-failure-cap.ts +38 -0
- package/engine/core/final-review-action.ts +125 -0
- package/engine/core/final-review-text.ts +63 -17
- package/engine/core/final-stuck-text.ts +86 -0
- package/engine/core/fix-loop-text.ts +49 -2
- package/engine/core/left-out.ts +120 -0
- package/engine/core/live-file.ts +372 -0
- package/engine/core/loop-caps.ts +25 -2
- package/engine/core/not-in-run.ts +7 -3
- package/engine/core/pull-request-text.ts +255 -6
- package/engine/core/repo-wait-text.ts +81 -0
- package/engine/core/review-text.ts +55 -1
- package/engine/core/run-crash-recovery-helpers.ts +16 -0
- package/engine/core/run-session-close-helpers.ts +96 -0
- package/engine/core/run-start.ts +148 -0
- package/engine/core/run-stuck-text.ts +86 -0
- package/engine/core/schedule-rules.ts +193 -0
- package/engine/core/stuck-detail-text.ts +89 -0
- package/engine/core/stuck-format.ts +22 -0
- package/engine/core/stuck-text.ts +29 -332
- package/engine/core/ticket-stuck-text.ts +192 -0
- package/engine/core/worktree-changes.ts +106 -0
- package/engine/gates/already-done-check.ts +120 -0
- package/engine/gates/feature-map.ts +96 -0
- package/engine/gates/gate-runner.ts +108 -108
- package/engine/gates/gate-schemas.ts +61 -2
- package/engine/gates/leftover-scan.ts +154 -31
- package/engine/gates/new-tests.ts +45 -0
- package/engine/gates/own-files-check.ts +98 -0
- package/engine/gates/prepare-check.ts +60 -0
- package/engine/gates/prepare-slots.ts +99 -0
- package/engine/gates/red-check.ts +184 -16
- package/engine/gates/shell-check.ts +64 -0
- package/engine/gates/slot-waits.ts +55 -0
- package/engine/gates/slow-test-runner.ts +59 -0
- package/engine/gates/test-checks.ts +161 -0
- package/engine/gates/test-runner.ts +164 -5
- package/engine/gates/test-slots.ts +48 -0
- package/engine/git/git-adapter.ts +55 -396
- package/engine/git/git-commits.ts +132 -0
- package/engine/git/git-join.ts +139 -0
- package/engine/git/git-read.ts +123 -0
- package/engine/git/git-run.ts +32 -0
- package/engine/git/git-worktrees.ts +143 -0
- package/engine/git/outside-links.ts +55 -0
- package/engine/guards/after-turn-check.ts +31 -5
- package/engine/guards/guard-hook.ts +60 -12
- package/engine/guards/guard-roles.ts +68 -0
- package/engine/guards/helper-rules.ts +92 -0
- package/engine/guards/permission-rules.ts +161 -0
- package/engine/guards/role-rules-helpers.ts +48 -0
- package/engine/guards/role-rules.ts +34 -606
- package/engine/guards/shell-rules.ts +344 -0
- package/engine/guards/worktree-git.ts +208 -0
- package/engine/guards/worktree-snapshot.ts +412 -0
- package/engine/guards/worktree-state.ts +28 -825
- package/engine/guards/worktree-undo.ts +260 -0
- package/engine/guards/write-rules.ts +149 -0
- package/engine/index.ts +30 -9
- package/engine/intake/criteria-checks.ts +114 -0
- package/engine/intake/intake-checks.ts +164 -30
- package/engine/intake/intake-schemas.ts +37 -2
- package/engine/jev/clef-client.ts +259 -0
- package/engine/jev/decision-model-credentials.ts +212 -0
- package/engine/jev/jev-jobs.ts +144 -60
- package/engine/jev/jev-schemas.ts +86 -5
- package/engine/jev/jev-shadow-helpers.ts +218 -0
- package/engine/jev/jev-shadow.ts +109 -64
- package/engine/jev/model-pick.ts +275 -0
- package/engine/journal/agent-entries.ts +167 -0
- package/engine/journal/agent-message-entries.ts +72 -0
- package/engine/journal/agent-turn-replay.ts +203 -0
- package/engine/journal/already-done-entries.ts +55 -0
- package/engine/journal/commit-entries.ts +56 -0
- package/engine/journal/cross-ticket-replay.ts +222 -0
- package/engine/journal/decision-model-entries.ts +86 -0
- package/engine/journal/entry-fields.ts +10 -0
- package/engine/journal/final-review-entries.ts +128 -0
- package/engine/journal/final-review-replay.ts +324 -0
- package/engine/journal/final-review-round.ts +320 -0
- package/engine/journal/final-review-state.ts +180 -0
- package/engine/journal/gate-entries.ts +81 -0
- package/engine/journal/intake-entries.ts +87 -0
- package/engine/journal/join-entries.ts +126 -0
- package/engine/journal/journal-entry-schema.ts +218 -0
- package/engine/journal/journal-kinds.ts +97 -0
- package/engine/journal/journal-record-schema.ts +218 -0
- package/engine/journal/journal-record.ts +43 -1400
- package/engine/journal/journal.ts +6 -1
- package/engine/journal/limit-entries.ts +90 -0
- package/engine/journal/memory-entries.ts +124 -0
- package/engine/journal/memory-replay.ts +157 -0
- package/engine/journal/plan-replay.ts +139 -0
- package/engine/journal/pull-request-entries.ts +41 -0
- package/engine/journal/replay.ts +157 -2144
- package/engine/journal/reply-replay.ts +144 -0
- package/engine/journal/repo-wait-entries.ts +53 -0
- package/engine/journal/run-entries.ts +172 -0
- package/engine/journal/run-state.ts +264 -0
- package/engine/journal/shared-failure-replay.ts +133 -0
- package/engine/journal/stuck-entries.ts +193 -0
- package/engine/journal/ticket-progress.ts +392 -0
- package/engine/journal/ticket-replay.ts +411 -0
- package/engine/journal/ticket-result-replay.ts +364 -0
- package/engine/journal/ticket-retry-replay.ts +142 -0
- package/engine/journal/worktree-entries.ts +122 -0
- package/engine/models/model-score.ts +512 -0
- package/engine/shell/run-command.ts +187 -18
- package/engine/testing/agent-turn-fixtures.ts +287 -0
- package/engine/testing/already-done-fixtures.ts +97 -0
- package/engine/testing/build-fixtures.ts +68 -973
- package/engine/testing/checkouts-fixtures.ts +48 -0
- package/engine/testing/final-review-fixtures.ts +62 -1
- package/engine/testing/final-review-steps.ts +143 -0
- package/engine/testing/limit-fixtures.ts +85 -0
- package/engine/testing/live-file-fixtures.ts +57 -0
- package/engine/testing/practice-repo.ts +10 -4
- package/engine/testing/practice-run.ts +1 -1
- package/engine/testing/reply-fixtures.ts +88 -0
- package/engine/testing/run-budget-fixtures.ts +165 -0
- package/engine/testing/run-fixtures.ts +219 -0
- package/engine/testing/sdk-play.ts +177 -0
- package/engine/testing/setup-repo.ts +194 -0
- package/engine/testing/step-record-fixtures.ts +55 -0
- package/engine/testing/ticket-step-fixtures.ts +272 -0
- package/engine/testing/wait-scenarios.ts +186 -0
- package/engine/tracker/gh-retry.ts +217 -0
- package/engine/tracker/github-tracker.ts +310 -90
- package/engine/tracker/in-memory-tracker.ts +8 -4
- package/engine/tracker/tracker.ts +131 -6
- package/package.json +2 -1
- package/skills/luca-retro/SKILL.md +74 -0
- package/skills/luca-retro/scripts/retro-summary.ts +1356 -0
- package/skills/luca-unstick/SKILL.md +153 -0
- package/skills/luca-unstick/scripts/stuck-summary.ts +1063 -0
- package/engine/jev/jev-client.ts +0 -173
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { RedCheckResult, TestCase, TestRun } from './gate-schemas'
|
|
2
|
+
import { addedTestNames, testNamesIn } from './new-tests'
|
|
2
3
|
|
|
3
4
|
import type { CriterionTests, TestRef } from '../agents/role-results'
|
|
5
|
+
import { MAX_TESTS_PER_CRITERION } from '../core/loop-caps'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A test file the ticket changed, as it was at the ticket's base and as it
|
|
9
|
+
* is now (`null`: not there).
|
|
10
|
+
*/
|
|
11
|
+
export type ChangedTestSource = {
|
|
12
|
+
file: string
|
|
13
|
+
base: string | null
|
|
14
|
+
current: string | null
|
|
15
|
+
}
|
|
4
16
|
|
|
5
17
|
const lastPart = (name: string): string =>
|
|
6
18
|
(name.split(' > ').at(-1) ?? name).trim()
|
|
@@ -8,7 +20,44 @@ const lastPart = (name: string): string =>
|
|
|
8
20
|
/** Quote-insensitive text, so a test name found in source matches its printed name. */
|
|
9
21
|
const loose = (text: string): string => text.replace(/[\\'"`]/g, '')
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Said after a new unmapped test found in the text of a test file that does
|
|
25
|
+
* not load yet: with no results to check against, a `test(` inside a string
|
|
26
|
+
* or a comment counts as a test too (#581).
|
|
27
|
+
*/
|
|
28
|
+
const NOT_LOADED_HINT =
|
|
29
|
+
'. The file does not load yet, so every test( in its text counts, even one inside a string or a comment'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Whether a test name written in a file's text (`own`) is the test bun prints
|
|
33
|
+
* as `full_name`: equal to it, at its end after " > " (not split on the last
|
|
34
|
+
* " > ": a test name may hold one itself), or the same last part. One rule
|
|
35
|
+
* for a mapped name and for a name found in the text.
|
|
36
|
+
*/
|
|
37
|
+
const namesTest = ({
|
|
38
|
+
full_name,
|
|
39
|
+
own,
|
|
40
|
+
}: {
|
|
41
|
+
full_name: string
|
|
42
|
+
own: string
|
|
43
|
+
}): boolean => {
|
|
44
|
+
const full = full_name.trim()
|
|
45
|
+
return (
|
|
46
|
+
full === own ||
|
|
47
|
+
full.endsWith(` > ${own}`) ||
|
|
48
|
+
lastPart(full) === lastPart(own)
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The test case a named test is, in its file: the one whose full name
|
|
54
|
+
* matches, else the only one whose last part does. `undefined` if none (or
|
|
55
|
+
* more than one) does.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* findCase({ cases: run.cases, test: { file: 'src/sum.test.ts', name: 'sum > adds' } })
|
|
59
|
+
*/
|
|
60
|
+
export const findCase = ({
|
|
12
61
|
cases,
|
|
13
62
|
test,
|
|
14
63
|
}: {
|
|
@@ -67,16 +116,79 @@ const checkNewTest = ({
|
|
|
67
116
|
}
|
|
68
117
|
|
|
69
118
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
119
|
+
* Every old test (passed at the baseline) still passes, in its file or
|
|
120
|
+
* moved (#609): one missing from its file but now in another test file
|
|
121
|
+
* under the same full name, a case the baseline didn't have there. Each
|
|
122
|
+
* such case stands for one old test only, so a name several old tests had
|
|
123
|
+
* counts as moved once.
|
|
124
|
+
*/
|
|
125
|
+
const checkOldTests = ({
|
|
126
|
+
baseline,
|
|
127
|
+
current,
|
|
128
|
+
}: {
|
|
129
|
+
baseline: TestRun
|
|
130
|
+
current: TestRun
|
|
131
|
+
}): { problems: string[]; notes: string[] } => {
|
|
132
|
+
const problems: string[] = []
|
|
133
|
+
const notes: string[] = []
|
|
134
|
+
const same = (a: TestCase, b: TestCase) =>
|
|
135
|
+
a.file === b.file && a.full_name === b.full_name
|
|
136
|
+
const movable = current.cases.filter(
|
|
137
|
+
(now) => !baseline.cases.some((old) => same(old, now))
|
|
138
|
+
)
|
|
139
|
+
const claimed = new Set<TestCase>()
|
|
140
|
+
for (const old of baseline.cases.filter(
|
|
141
|
+
({ status }) => status === 'passed'
|
|
142
|
+
)) {
|
|
143
|
+
const label = `old test "${old.full_name}" in ${old.file}`
|
|
144
|
+
const now = current.cases.find((found) => same(found, old))
|
|
145
|
+
if (now !== undefined) {
|
|
146
|
+
if (now.status !== 'passed')
|
|
147
|
+
problems.push(`${label} no longer passes`)
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
const moved = movable.find(
|
|
151
|
+
(found) => !claimed.has(found) && found.full_name === old.full_name
|
|
152
|
+
)
|
|
153
|
+
if (moved === undefined) {
|
|
154
|
+
problems.push(`${label} is missing`)
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
claimed.add(moved)
|
|
158
|
+
const where = `old test "${old.full_name}" in ${moved.file} (moved from ${old.file})`
|
|
159
|
+
if (moved.status === 'passed')
|
|
160
|
+
notes.push(`${where}: still passes, as it should`)
|
|
161
|
+
else problems.push(`${where} no longer passes`)
|
|
162
|
+
}
|
|
163
|
+
return { problems, notes }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The red check. Pure: proves every criterion has a test, and at most
|
|
168
|
+
* `MAX_TESTS_PER_CRITERION`, every test the ticket added is mapped, every
|
|
169
|
+
* test file it created is in the feature map (when the repo has one), every
|
|
170
|
+
* new test fails, and every old test that passed before still passes. A
|
|
171
|
+
* test the ticket only moved to another test file, under the same full
|
|
172
|
+
* name, is an old test (#609): noted, not missing and not new.
|
|
72
173
|
*
|
|
73
174
|
* @param baseline - The test run before the test-writer started.
|
|
74
175
|
* @param current - The test run after the test-writer finished.
|
|
75
176
|
* @param test_files - The worktree's test files, per the engine config.
|
|
76
177
|
* @param sources - The text of each mapped test file, `null` if missing.
|
|
178
|
+
* @param changed_test_sources - Each test file the ticket changed, as it was
|
|
179
|
+
* at the ticket's base and as it is now: a new test is a name in the
|
|
180
|
+
* second the first doesn't have (#581), and no changed or deleted test
|
|
181
|
+
* file had at its base (a moved test, #609). For a file that has results in
|
|
182
|
+
* `current`, only a name of a case that ran counts, so a `test(` inside a
|
|
183
|
+
* string (a fixture) or a comment does not. A file that doesn't load yet
|
|
184
|
+
* has none, so its text alone decides, and every `test(` in it counts.
|
|
185
|
+
* @param feature_map - The repo's feature map, when the config names one
|
|
186
|
+
* (#583): its `path`, and `files`, the test files it lists (from
|
|
187
|
+
* `featureMapFiles`), `null` when the map file is missing. A test file the
|
|
188
|
+
* ticket created must be among them. Left out or `null`, nothing is checked.
|
|
77
189
|
*
|
|
78
190
|
* @example
|
|
79
|
-
* const result = checkRed({ criteria_ids: ['AC1'], mapping, baseline, current, test_files, sources })
|
|
191
|
+
* const result = checkRed({ criteria_ids: ['AC1'], mapping, baseline, current, test_files, sources, changed_test_sources })
|
|
80
192
|
* if (!result.ok) console.error(result.problems)
|
|
81
193
|
*/
|
|
82
194
|
export const checkRed = ({
|
|
@@ -86,6 +198,8 @@ export const checkRed = ({
|
|
|
86
198
|
current,
|
|
87
199
|
test_files,
|
|
88
200
|
sources,
|
|
201
|
+
changed_test_sources,
|
|
202
|
+
feature_map,
|
|
89
203
|
}: {
|
|
90
204
|
criteria_ids: string[]
|
|
91
205
|
mapping: CriterionTests[]
|
|
@@ -93,6 +207,8 @@ export const checkRed = ({
|
|
|
93
207
|
current: TestRun
|
|
94
208
|
test_files: string[]
|
|
95
209
|
sources: Record<string, string | null>
|
|
210
|
+
changed_test_sources: ChangedTestSource[]
|
|
211
|
+
feature_map?: { path: string; files: string[] | null } | null
|
|
96
212
|
}): RedCheckResult => {
|
|
97
213
|
const problems: string[] = []
|
|
98
214
|
const notes: string[] = []
|
|
@@ -107,6 +223,67 @@ export const checkRed = ({
|
|
|
107
223
|
notes.push(`unknown criterion id ${criterion_id}`)
|
|
108
224
|
}
|
|
109
225
|
}
|
|
226
|
+
for (const { criterion_id, tests } of mapping) {
|
|
227
|
+
if (tests.length > MAX_TESTS_PER_CRITERION) {
|
|
228
|
+
problems.push(
|
|
229
|
+
`${criterion_id} has ${tests.length} tests; write at most ${MAX_TESTS_PER_CRITERION} per criterion`
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// A name some changed (or deleted) test file had at the base is a moved
|
|
234
|
+
// test, not a new one, whichever file it is in now (#609).
|
|
235
|
+
const old_names = new Set(
|
|
236
|
+
changed_test_sources.flatMap(({ base }) =>
|
|
237
|
+
base === null ? [] : testNamesIn({ source: base })
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
for (const { file, base, current: text } of changed_test_sources) {
|
|
241
|
+
const mapped = mapping
|
|
242
|
+
.flatMap(({ tests }) => tests)
|
|
243
|
+
.filter((test) => test.file === file)
|
|
244
|
+
.map(({ name }) => name.trim())
|
|
245
|
+
// A file that loaded has results: a name in its text is a new test
|
|
246
|
+
// only if a case of that name ran, so a `test('x'` inside a string
|
|
247
|
+
// (a fixture) or a comment is not one. A file with none (it doesn't
|
|
248
|
+
// load yet) is read from its text alone.
|
|
249
|
+
const loaded = !current.files_without_results.includes(file)
|
|
250
|
+
const ran = current.cases.filter((found) => found.file === file)
|
|
251
|
+
for (const name of addedTestNames({ base, current: text })) {
|
|
252
|
+
if (old_names.has(name)) continue
|
|
253
|
+
const own = name.trim()
|
|
254
|
+
if (
|
|
255
|
+
loaded &&
|
|
256
|
+
!ran.some(({ full_name }) => namesTest({ full_name, own }))
|
|
257
|
+
) {
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
if (!mapped.some((full) => namesTest({ full_name: full, own }))) {
|
|
261
|
+
problems.push(
|
|
262
|
+
`"${name}" in ${file}: a new test the map doesn't name (map it to a criterion, or remove it)${loaded ? '' : NOT_LOADED_HINT}`
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (feature_map !== undefined && feature_map !== null) {
|
|
268
|
+
// A test file the ticket created (not one it edited) must be listed
|
|
269
|
+
// under a feature, so tests grow inside their feature's files (#583).
|
|
270
|
+
const created = changed_test_sources.filter(
|
|
271
|
+
({ base, current: text }) => base === null && text !== null
|
|
272
|
+
)
|
|
273
|
+
if (created.length > 0 && feature_map.files === null) {
|
|
274
|
+
problems.push(
|
|
275
|
+
`The feature map ${feature_map.path} is missing: restore it, or add it with the new test file listed`
|
|
276
|
+
)
|
|
277
|
+
} else {
|
|
278
|
+
for (const { file } of created) {
|
|
279
|
+
if (!(feature_map.files ?? []).includes(file)) {
|
|
280
|
+
problems.push(
|
|
281
|
+
`${file}: a new test file the feature map doesn't list (add it under a feature in ${feature_map.path})`
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
110
287
|
const testFiles = new Set(test_files)
|
|
111
288
|
for (const test of mapping.flatMap(({ tests }) => tests)) {
|
|
112
289
|
const outcome = checkNewTest({
|
|
@@ -118,17 +295,8 @@ export const checkRed = ({
|
|
|
118
295
|
if ('problem' in outcome) problems.push(outcome.problem)
|
|
119
296
|
else notes.push(outcome.note)
|
|
120
297
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
const now = current.cases.find(
|
|
125
|
-
({ file, full_name }) =>
|
|
126
|
-
file === old.file && full_name === old.full_name
|
|
127
|
-
)
|
|
128
|
-
const label = `old test "${old.full_name}" in ${old.file}`
|
|
129
|
-
if (now === undefined) problems.push(`${label} is missing`)
|
|
130
|
-
else if (now.status !== 'passed')
|
|
131
|
-
problems.push(`${label} no longer passes`)
|
|
132
|
-
}
|
|
298
|
+
const old = checkOldTests({ baseline, current })
|
|
299
|
+
problems.push(...old.problems)
|
|
300
|
+
notes.push(...old.notes)
|
|
133
301
|
return { ok: problems.length === 0, problems, notes }
|
|
134
302
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one shell command as a named gate check (`shellCheck`), and the small
|
|
3
|
+
* clock the checks share (`msSince`). The install, prepare, types, lint, and
|
|
4
|
+
* `pass_fail` test checks all run through it.
|
|
5
|
+
*/
|
|
6
|
+
import type { GateCheck, GateName } from './gate-schemas'
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
clipOutput,
|
|
10
|
+
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
11
|
+
runShell,
|
|
12
|
+
timedOutLine,
|
|
13
|
+
} from '../shell/run-command'
|
|
14
|
+
|
|
15
|
+
/** Milliseconds since `started` (from `performance.now()`), whole (#518). */
|
|
16
|
+
export const msSince = (started: number): number =>
|
|
17
|
+
Math.round(performance.now() - started)
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Runs one shell command as a named check: `ok` on exit 0, with the clipped
|
|
21
|
+
* output only when it failed. One that runs past `timeout_ms` is stopped,
|
|
22
|
+
* with everything it started, and its output starts by saying so, then
|
|
23
|
+
* `timeout_hint`, if any. The check says how long the command ran (#518).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* const install = await shellCheck({ name: 'install', command: 'bun install', cwd })
|
|
27
|
+
*/
|
|
28
|
+
export const shellCheck = async ({
|
|
29
|
+
name,
|
|
30
|
+
command,
|
|
31
|
+
cwd,
|
|
32
|
+
timeout_ms,
|
|
33
|
+
timeout_hint,
|
|
34
|
+
}: {
|
|
35
|
+
name: GateName
|
|
36
|
+
command: string
|
|
37
|
+
cwd: string
|
|
38
|
+
/** Defaults to `DEFAULT_COMMAND_TIMEOUT_MS`. */
|
|
39
|
+
timeout_ms?: number
|
|
40
|
+
/** How to give the command longer, for the timed-out message. */
|
|
41
|
+
timeout_hint?: string
|
|
42
|
+
}): Promise<GateCheck> => {
|
|
43
|
+
const limit = timeout_ms ?? DEFAULT_COMMAND_TIMEOUT_MS
|
|
44
|
+
const started = performance.now()
|
|
45
|
+
const result = await runShell({ command, cwd, timeout_ms: limit })
|
|
46
|
+
const ok = result.exit_code === 0
|
|
47
|
+
const timedOut = result.timed_out
|
|
48
|
+
? timedOutLine({
|
|
49
|
+
timeout_ms: limit,
|
|
50
|
+
hint: timeout_hint,
|
|
51
|
+
survivors: result.survivors,
|
|
52
|
+
})
|
|
53
|
+
: ''
|
|
54
|
+
return {
|
|
55
|
+
name,
|
|
56
|
+
command,
|
|
57
|
+
ok,
|
|
58
|
+
exit_code: result.exit_code,
|
|
59
|
+
output: ok
|
|
60
|
+
? ''
|
|
61
|
+
: `${timedOut}${clipOutput({ text: `${result.stdout}\n${result.stderr}` })}`,
|
|
62
|
+
duration_ms: msSince(started),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waits inside a step (#632): a check that must wait for a prepare slot or
|
|
3
|
+
* a test slot says so to whoever listens for the step it runs in, such as
|
|
4
|
+
* the run's live file. The listener rides along with the step's work (an
|
|
5
|
+
* `AsyncLocalStorage`), so the checks deep in the gate runner need no new
|
|
6
|
+
* argument. Nothing is journaled: the wait's length is already `wait_ms`.
|
|
7
|
+
*/
|
|
8
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
9
|
+
|
|
10
|
+
/** What a check waits for. */
|
|
11
|
+
export type SlotKind = 'prepare_slot' | 'test_slot'
|
|
12
|
+
|
|
13
|
+
/** A wait for a slot, as it starts: `in_use` of the `slots` are taken. */
|
|
14
|
+
export type SlotWait = { for: SlotKind; in_use: number; slots: number }
|
|
15
|
+
|
|
16
|
+
/** Hears a wait start; the function it gives back hears the slot taken. */
|
|
17
|
+
export type SlotWaitListener = (wait: SlotWait) => () => void
|
|
18
|
+
|
|
19
|
+
const LISTENER = new AsyncLocalStorage<SlotWaitListener>()
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runs `run` with `listener` hearing every slot wait inside it.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* await hearSlotWaits({ listener: live.slotWaits({ ticket: 11 }), run: () => runGates(...) })
|
|
26
|
+
*/
|
|
27
|
+
export const hearSlotWaits = <T>({
|
|
28
|
+
listener,
|
|
29
|
+
run,
|
|
30
|
+
}: {
|
|
31
|
+
listener: SlotWaitListener
|
|
32
|
+
run: () => Promise<T>
|
|
33
|
+
}): Promise<T> => LISTENER.run(listener, run)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The `on_wait` for a take of `kind` slots, at most `limit` at once, or
|
|
37
|
+
* `undefined` when nobody listens. With no cap (`Infinity`), the slots are
|
|
38
|
+
* the ones in use.
|
|
39
|
+
*/
|
|
40
|
+
export const onSlotWait = ({
|
|
41
|
+
kind,
|
|
42
|
+
limit,
|
|
43
|
+
}: {
|
|
44
|
+
kind: SlotKind
|
|
45
|
+
limit: number
|
|
46
|
+
}): ((in_use: number) => () => void) | undefined => {
|
|
47
|
+
const listener = LISTENER.getStore()
|
|
48
|
+
if (listener === undefined) return undefined
|
|
49
|
+
return (in_use) =>
|
|
50
|
+
listener({
|
|
51
|
+
for: kind,
|
|
52
|
+
in_use,
|
|
53
|
+
slots: Number.isFinite(limit) ? limit : in_use,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The final review's slow tests (#563): the prepare command, then each slow
|
|
3
|
+
* test command, as `slow_test` checks.
|
|
4
|
+
*/
|
|
5
|
+
import type { GateCheck } from './gate-schemas'
|
|
6
|
+
import { prepareCheck } from './prepare-check'
|
|
7
|
+
import { testChecks } from './test-checks'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
slowTestCommands,
|
|
11
|
+
testTimeoutOf,
|
|
12
|
+
type EngineConfig,
|
|
13
|
+
} from '../config/engine-config'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The final review's slow tests (#563), once round 1's lenses are in: the
|
|
17
|
+
* prepare command, if any (a failed one stops there), then each slow
|
|
18
|
+
* test command, in order, as `slow_test` checks with `test_timeout_ms`.
|
|
19
|
+
* `around_prepare`, if given, runs the prepare check, as in `runGates`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* const { ok, checks } = await runSlowTests({ cwd, config, test_files, report_file })
|
|
23
|
+
*/
|
|
24
|
+
export const runSlowTests = async ({
|
|
25
|
+
cwd,
|
|
26
|
+
config,
|
|
27
|
+
test_files,
|
|
28
|
+
report_file,
|
|
29
|
+
around_prepare,
|
|
30
|
+
}: {
|
|
31
|
+
cwd: string
|
|
32
|
+
config: EngineConfig
|
|
33
|
+
test_files: string[]
|
|
34
|
+
report_file: string
|
|
35
|
+
/** Wraps the prepare run. Left out, it runs as is. */
|
|
36
|
+
around_prepare?: (
|
|
37
|
+
prepare: () => Promise<GateCheck | null>
|
|
38
|
+
) => Promise<GateCheck | null>
|
|
39
|
+
}): Promise<{ ok: boolean; checks: GateCheck[] }> => {
|
|
40
|
+
const prepare = () => prepareCheck({ config, cwd })
|
|
41
|
+
const prepared = await (around_prepare?.(prepare) ?? prepare())
|
|
42
|
+
if (prepared !== null && !prepared.ok) {
|
|
43
|
+
return { ok: false, checks: [prepared] }
|
|
44
|
+
}
|
|
45
|
+
const { checks } = await testChecks({
|
|
46
|
+
commands: slowTestCommands({ config }),
|
|
47
|
+
name: 'slow_test',
|
|
48
|
+
cwd,
|
|
49
|
+
test_files,
|
|
50
|
+
report_file,
|
|
51
|
+
first_index: 0,
|
|
52
|
+
timeout_ms: testTimeoutOf({ config }),
|
|
53
|
+
limit: null,
|
|
54
|
+
test_concurrency: config.test_concurrency,
|
|
55
|
+
exclusive: false,
|
|
56
|
+
})
|
|
57
|
+
const all = prepared === null ? checks : [prepared, ...checks]
|
|
58
|
+
return { ok: all.every(({ ok }) => ok), checks: all }
|
|
59
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The test commands as gate checks (`testChecks`): `pass_fail` commands judged
|
|
3
|
+
* by their exit code, `bun` commands run with bun's JUnit reporter, each
|
|
4
|
+
* waiting for a test slot first. Also the fast test speed limit (#580).
|
|
5
|
+
*/
|
|
6
|
+
import type { GateCheck, TestRun } from './gate-schemas'
|
|
7
|
+
import { msSince, shellCheck } from './shell-check'
|
|
8
|
+
import {
|
|
9
|
+
reportFileFor,
|
|
10
|
+
runTestsInHeldSlot,
|
|
11
|
+
TEST_TIMEOUT_HINT,
|
|
12
|
+
} from './test-runner'
|
|
13
|
+
import { inTestSlot } from './test-slots'
|
|
14
|
+
|
|
15
|
+
import type { TestCommand } from '../config/engine-config'
|
|
16
|
+
|
|
17
|
+
/** What to do about a fast test over the speed limit (#580). */
|
|
18
|
+
export const FAST_TEST_HINT = ({ limit_ms }: { limit_ms: number }): string =>
|
|
19
|
+
`A fast test may take at most ${limit_ms} ms (\`test_case_timeout_ms\`). Make it faster, or move it to the slow tests.`
|
|
20
|
+
|
|
21
|
+
/** The fast test speed limit (#580), and the files measured against it. */
|
|
22
|
+
export type SpeedLimit = { limit_ms: number; measured_files: string[] }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A `bun` test command's run as a gate check named `name`: it fails on a
|
|
26
|
+
* failed run, or on a passing test over the fast test speed limit (see
|
|
27
|
+
* `testChecks`), and it ran from `started` until now.
|
|
28
|
+
*/
|
|
29
|
+
const bunCheck = ({
|
|
30
|
+
name,
|
|
31
|
+
run,
|
|
32
|
+
limit,
|
|
33
|
+
started,
|
|
34
|
+
}: {
|
|
35
|
+
name: 'test' | 'slow_test'
|
|
36
|
+
run: TestRun
|
|
37
|
+
limit: SpeedLimit | null
|
|
38
|
+
started: number
|
|
39
|
+
}): GateCheck => {
|
|
40
|
+
const tooSlow =
|
|
41
|
+
limit === null
|
|
42
|
+
? []
|
|
43
|
+
: run.cases.filter(
|
|
44
|
+
({ file, status, duration_ms }) =>
|
|
45
|
+
status === 'passed' &&
|
|
46
|
+
limit.measured_files.includes(file) &&
|
|
47
|
+
(duration_ms ?? 0) > limit.limit_ms
|
|
48
|
+
)
|
|
49
|
+
const timedOut = /timed out after \d+ms/.test(run.output)
|
|
50
|
+
const hint =
|
|
51
|
+
limit === null || (tooSlow.length === 0 && !timedOut)
|
|
52
|
+
? ''
|
|
53
|
+
: `${FAST_TEST_HINT({ limit_ms: limit.limit_ms })}\n`
|
|
54
|
+
const slowLines = tooSlow
|
|
55
|
+
.map(
|
|
56
|
+
({ file, full_name, duration_ms }) =>
|
|
57
|
+
`- ${file} > ${full_name}: ${duration_ms} ms`
|
|
58
|
+
)
|
|
59
|
+
.join('\n')
|
|
60
|
+
const ok = run.ok && tooSlow.length === 0
|
|
61
|
+
return {
|
|
62
|
+
name,
|
|
63
|
+
command: run.command,
|
|
64
|
+
ok,
|
|
65
|
+
exit_code: run.exit_code,
|
|
66
|
+
output: ok
|
|
67
|
+
? ''
|
|
68
|
+
: `${hint}${slowLines === '' ? '' : `Too slow for a fast test:\n${slowLines}\n`}${run.output}`,
|
|
69
|
+
duration_ms: msSince(started),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Runs test commands as gate checks named `name`, in order (see `runGates`).
|
|
75
|
+
* With a `limit` (#580, the fast call only), each `bun` command gets
|
|
76
|
+
* `--timeout=<limit_ms>`, and a passing test in one of `measured_files` that
|
|
77
|
+
* took longer than that fails the check: a test's own timeout option
|
|
78
|
+
* overrides `--timeout`, so only its measured time tells.
|
|
79
|
+
*
|
|
80
|
+
* With `test_concurrency` (#544), each command first waits for a test slot
|
|
81
|
+
* (see `inTestSlot`); its check records the wait as `wait_ms`, and its time
|
|
82
|
+
* limit and `duration_ms` start after it. With `exclusive` (a lone rerun,
|
|
83
|
+
* #544), each command waits until no other test run is going, and holds
|
|
84
|
+
* every slot while it runs.
|
|
85
|
+
*/
|
|
86
|
+
export const testChecks = async ({
|
|
87
|
+
commands,
|
|
88
|
+
name,
|
|
89
|
+
cwd,
|
|
90
|
+
test_files,
|
|
91
|
+
report_file,
|
|
92
|
+
first_index,
|
|
93
|
+
timeout_ms,
|
|
94
|
+
limit,
|
|
95
|
+
test_concurrency,
|
|
96
|
+
exclusive,
|
|
97
|
+
}: {
|
|
98
|
+
commands: TestCommand[]
|
|
99
|
+
name: 'test' | 'slow_test'
|
|
100
|
+
cwd: string
|
|
101
|
+
test_files: string[]
|
|
102
|
+
report_file: string
|
|
103
|
+
/** The first command's index, so each command gets its own report file. */
|
|
104
|
+
first_index: number
|
|
105
|
+
timeout_ms: number
|
|
106
|
+
/**
|
|
107
|
+
* The fast test speed limit (#580), or `null` for none. Only the fast
|
|
108
|
+
* call has one; slow tests never do.
|
|
109
|
+
*/
|
|
110
|
+
limit: SpeedLimit | null
|
|
111
|
+
/** The config's `test_concurrency`; left out, no cap. */
|
|
112
|
+
test_concurrency: number | undefined
|
|
113
|
+
exclusive: boolean
|
|
114
|
+
}): Promise<{ checks: GateCheck[]; runs: TestRun[] }> => {
|
|
115
|
+
const checks: GateCheck[] = []
|
|
116
|
+
const runs: TestRun[] = []
|
|
117
|
+
for (const [offset, { run: command, results }] of commands.entries()) {
|
|
118
|
+
if (results === 'pass_fail') {
|
|
119
|
+
checks.push(
|
|
120
|
+
await inTestSlot({
|
|
121
|
+
test_concurrency,
|
|
122
|
+
exclusive,
|
|
123
|
+
work: () =>
|
|
124
|
+
shellCheck({
|
|
125
|
+
name,
|
|
126
|
+
command,
|
|
127
|
+
cwd,
|
|
128
|
+
timeout_ms,
|
|
129
|
+
timeout_hint: TEST_TIMEOUT_HINT,
|
|
130
|
+
}),
|
|
131
|
+
})
|
|
132
|
+
)
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
checks.push(
|
|
136
|
+
await inTestSlot({
|
|
137
|
+
test_concurrency,
|
|
138
|
+
exclusive,
|
|
139
|
+
work: async () => {
|
|
140
|
+
const started = performance.now()
|
|
141
|
+
const run = await runTestsInHeldSlot({
|
|
142
|
+
cwd,
|
|
143
|
+
command:
|
|
144
|
+
limit === null
|
|
145
|
+
? command
|
|
146
|
+
: `${command} --timeout=${limit.limit_ms}`,
|
|
147
|
+
test_files,
|
|
148
|
+
report_file: reportFileFor({
|
|
149
|
+
report_file,
|
|
150
|
+
index: first_index + offset,
|
|
151
|
+
}),
|
|
152
|
+
timeout_ms,
|
|
153
|
+
})
|
|
154
|
+
runs.push(run)
|
|
155
|
+
return bunCheck({ name, run, limit, started })
|
|
156
|
+
},
|
|
157
|
+
})
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
return { checks, runs }
|
|
161
|
+
}
|