@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,8 +1,9 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
|
|
3
3
|
import type { TestCase, TestRun } from './gate-schemas'
|
|
4
|
+
import { inTestSlot } from './test-slots'
|
|
4
5
|
|
|
5
|
-
import { clipOutput, runShell } from '../shell/run-command'
|
|
6
|
+
import { clipOutput, runShell, timedOutLine } from '../shell/run-command'
|
|
6
7
|
|
|
7
8
|
const decode = (text: string): string =>
|
|
8
9
|
text
|
|
@@ -25,7 +26,9 @@ const attribute = ({
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Reads bun's JUnit report: one `<testsuite file=..>` per file, a nested
|
|
28
|
-
* `<testsuite>` per describe, and a `<testcase>` per test.
|
|
29
|
+
* `<testsuite>` per describe, and a `<testcase>` per test. Each case keeps
|
|
30
|
+
* its `time`, in whole milliseconds, and its `assertions` count when the
|
|
31
|
+
* report has them (#518, #581).
|
|
29
32
|
*/
|
|
30
33
|
export const parseJunit = ({ xml }: { xml: string }): TestCase[] => {
|
|
31
34
|
const cases: TestCase[] = []
|
|
@@ -46,10 +49,18 @@ export const parseJunit = ({ xml }: { xml: string }): TestCase[] => {
|
|
|
46
49
|
continue
|
|
47
50
|
}
|
|
48
51
|
const name = attribute({ attrs, name: 'name' }) ?? ''
|
|
52
|
+
const seconds = Number(attribute({ attrs, name: 'time' }))
|
|
53
|
+
const assertions = Number(attribute({ attrs, name: 'assertions' }))
|
|
49
54
|
cases.push({
|
|
50
55
|
file: attribute({ attrs, name: 'file' }) ?? suites[0] ?? '',
|
|
51
56
|
full_name: [...suites.slice(1), name].join(' > '),
|
|
52
57
|
status: 'passed',
|
|
58
|
+
...(Number.isFinite(seconds)
|
|
59
|
+
? { duration_ms: Math.round(seconds * 1000) }
|
|
60
|
+
: {}),
|
|
61
|
+
...(Number.isInteger(assertions) && assertions >= 0
|
|
62
|
+
? { assertions }
|
|
63
|
+
: {}),
|
|
53
64
|
})
|
|
54
65
|
open = selfClosing ? -1 : cases.length - 1
|
|
55
66
|
} else if (open >= 0 && !closing) {
|
|
@@ -83,6 +94,13 @@ export const testFilesAmong = ({
|
|
|
83
94
|
|
|
84
95
|
const NO_TEST_FILES = /0 test files matching|No tests found!/
|
|
85
96
|
|
|
97
|
+
/**
|
|
98
|
+
* How to give a test command longer, for the timed-out message. Every test
|
|
99
|
+
* command says the same, whatever its kind (#542).
|
|
100
|
+
*/
|
|
101
|
+
export const TEST_TIMEOUT_HINT =
|
|
102
|
+
'To give it longer, raise `test_timeout_ms` in `.luca/config.json`.'
|
|
103
|
+
|
|
86
104
|
/**
|
|
87
105
|
* Runs the engine config's test command with bun's JUnit reporter, so each
|
|
88
106
|
* test case's outcome is known. The command must be a `bun test` command; the
|
|
@@ -90,22 +108,70 @@ const NO_TEST_FILES = /0 test files matching|No tests found!/
|
|
|
90
108
|
*
|
|
91
109
|
* A repo with no test files counts as a pass: `bun test` exits 1 there, but a
|
|
92
110
|
* baseline with nothing to break is fine.
|
|
111
|
+
*
|
|
112
|
+
* One that runs past `timeout_ms` (see `testTimeoutOf`) is stopped, with
|
|
113
|
+
* everything it started, and fails; its output starts by saying so, and how
|
|
114
|
+
* to give it longer (#542).
|
|
115
|
+
*
|
|
116
|
+
* With `test_concurrency` (#544), it first waits for a test slot (see
|
|
117
|
+
* `inTestSlot`): the run records that wait as `wait_ms`, and its time limit
|
|
118
|
+
* starts after it. An `exclusive` run (a lone rerun, #544) waits until no
|
|
119
|
+
* other test run is going, and holds every slot while it runs.
|
|
93
120
|
*/
|
|
94
121
|
export const runTests = async ({
|
|
95
122
|
cwd,
|
|
96
123
|
command,
|
|
97
124
|
test_files,
|
|
98
125
|
report_file,
|
|
126
|
+
timeout_ms,
|
|
127
|
+
test_concurrency,
|
|
128
|
+
exclusive,
|
|
99
129
|
}: {
|
|
100
130
|
cwd: string
|
|
101
131
|
command: string
|
|
102
132
|
/** The worktree's test files, to spot files that produced no results. */
|
|
103
133
|
test_files: string[]
|
|
104
134
|
report_file: string
|
|
135
|
+
timeout_ms: number
|
|
136
|
+
/** The config's `test_concurrency`; left out, no cap. */
|
|
137
|
+
test_concurrency?: number
|
|
138
|
+
/** Run alone, holding every test slot (#544). Left out, it doesn't. */
|
|
139
|
+
exclusive?: boolean
|
|
140
|
+
}): Promise<TestRun> =>
|
|
141
|
+
inTestSlot({
|
|
142
|
+
test_concurrency,
|
|
143
|
+
exclusive,
|
|
144
|
+
work: () =>
|
|
145
|
+
runTestsInHeldSlot({
|
|
146
|
+
cwd,
|
|
147
|
+
command,
|
|
148
|
+
test_files,
|
|
149
|
+
report_file,
|
|
150
|
+
timeout_ms,
|
|
151
|
+
}),
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* `runTests` for a caller that already holds a test slot (see `inTestSlot`):
|
|
156
|
+
* it runs at once, taking no slot of its own.
|
|
157
|
+
*/
|
|
158
|
+
export const runTestsInHeldSlot = async ({
|
|
159
|
+
cwd,
|
|
160
|
+
command,
|
|
161
|
+
test_files,
|
|
162
|
+
report_file,
|
|
163
|
+
timeout_ms,
|
|
164
|
+
}: {
|
|
165
|
+
cwd: string
|
|
166
|
+
command: string
|
|
167
|
+
test_files: string[]
|
|
168
|
+
report_file: string
|
|
169
|
+
timeout_ms: number
|
|
105
170
|
}): Promise<TestRun> => {
|
|
106
171
|
const shell = await runShell({
|
|
107
172
|
command: `${command} --reporter=junit --reporter-outfile='${report_file}'`,
|
|
108
173
|
cwd,
|
|
174
|
+
timeout_ms,
|
|
109
175
|
})
|
|
110
176
|
const output = `${shell.stdout}\n${shell.stderr}`
|
|
111
177
|
const noTestFiles = NO_TEST_FILES.test(output)
|
|
@@ -124,10 +190,31 @@ export const runTests = async ({
|
|
|
124
190
|
files_without_results: test_files.filter(
|
|
125
191
|
(file) => !withResults.has(file)
|
|
126
192
|
),
|
|
127
|
-
output: clipOutput({ text: output })
|
|
193
|
+
output: `${shell.timed_out ? timedOutLine({ timeout_ms, hint: TEST_TIMEOUT_HINT, survivors: shell.survivors }) : ''}${clipOutput({ text: output })}`,
|
|
128
194
|
}
|
|
129
195
|
}
|
|
130
196
|
|
|
197
|
+
/** A word quoted for `sh`, whatever it holds. */
|
|
198
|
+
const shellWord = (word: string): string => `'${word.replaceAll("'", `'\\''`)}'`
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* A bun test command that runs just these test files: the config's command
|
|
202
|
+
* with each file after it, as a `./` path so bun takes it as a path, not a
|
|
203
|
+
* name to match. A command that already names a folder runs that too.
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* onlyTestFiles({ command: 'bun test', files: ['src/sum.test.ts'] })
|
|
207
|
+
* // "bun test './src/sum.test.ts'"
|
|
208
|
+
*/
|
|
209
|
+
export const onlyTestFiles = ({
|
|
210
|
+
command,
|
|
211
|
+
files,
|
|
212
|
+
}: {
|
|
213
|
+
command: string
|
|
214
|
+
files: string[]
|
|
215
|
+
}): string =>
|
|
216
|
+
[command, ...files.map((file) => shellWord(`./${file}`))].join(' ')
|
|
217
|
+
|
|
131
218
|
/**
|
|
132
219
|
* The report file for the `index`th of several commands: the first keeps
|
|
133
220
|
* `report_file`, the others get a numbered one beside it.
|
|
@@ -155,18 +242,27 @@ export const reportFileFor = ({
|
|
|
155
242
|
* empty passing run.
|
|
156
243
|
*
|
|
157
244
|
* @example
|
|
158
|
-
* const run = await runBunTests({ cwd, commands: ['bun test'], test_files, report_file })
|
|
245
|
+
* const run = await runBunTests({ cwd, commands: ['bun test'], test_files, report_file, timeout_ms: testTimeoutOf({ config }) })
|
|
159
246
|
*/
|
|
160
247
|
export const runBunTests = async ({
|
|
161
248
|
cwd,
|
|
162
249
|
commands,
|
|
163
250
|
test_files,
|
|
164
251
|
report_file,
|
|
252
|
+
timeout_ms,
|
|
253
|
+
test_concurrency,
|
|
254
|
+
exclusive,
|
|
165
255
|
}: {
|
|
166
256
|
cwd: string
|
|
167
257
|
commands: string[]
|
|
168
258
|
test_files: string[]
|
|
169
259
|
report_file: string
|
|
260
|
+
/** Each command's time limit (see `runTests`). */
|
|
261
|
+
timeout_ms: number
|
|
262
|
+
/** The config's `test_concurrency`: each command takes a test slot. */
|
|
263
|
+
test_concurrency?: number
|
|
264
|
+
/** Each command runs alone, holding every test slot (#544). */
|
|
265
|
+
exclusive?: boolean
|
|
170
266
|
}): Promise<TestRun> => {
|
|
171
267
|
const runs: TestRun[] = []
|
|
172
268
|
for (const [index, command] of commands.entries()) {
|
|
@@ -176,9 +272,66 @@ export const runBunTests = async ({
|
|
|
176
272
|
command,
|
|
177
273
|
test_files,
|
|
178
274
|
report_file: reportFileFor({ report_file, index }),
|
|
275
|
+
timeout_ms,
|
|
276
|
+
test_concurrency,
|
|
277
|
+
exclusive,
|
|
179
278
|
})
|
|
180
279
|
)
|
|
181
280
|
}
|
|
281
|
+
return joinRuns({ runs, test_files })
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* A command that runs just these test files with plain `bun test` (#564):
|
|
286
|
+
* never the config's command plus the files, because Bun's
|
|
287
|
+
* `--path-ignore-patterns` leaves out even files named on the command line.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* fileTestsCommand({ files: ['src/a.e2e.test.ts'] }) // "bun test './src/a.e2e.test.ts'"
|
|
291
|
+
*/
|
|
292
|
+
export const fileTestsCommand = ({ files }: { files: string[] }): string =>
|
|
293
|
+
onlyTestFiles({ command: 'bun test', files })
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* A fast run with a ticket's own-files run (#564) joined on (see
|
|
297
|
+
* `joinRuns`). An own run that gave no results at all means every file in it
|
|
298
|
+
* still doesn't load, which the fast run already said: it changes nothing,
|
|
299
|
+
* so the fast run, its command, and its output stay exactly as they are.
|
|
300
|
+
* (A fast test file that can't load yet, the usual red state, is such a
|
|
301
|
+
* file: it is not run twice in the journal.)
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* const current = withOwnRun({ fast, own: await runTests({ ... }), test_files })
|
|
305
|
+
*/
|
|
306
|
+
export const withOwnRun = ({
|
|
307
|
+
fast,
|
|
308
|
+
own,
|
|
309
|
+
test_files,
|
|
310
|
+
}: {
|
|
311
|
+
fast: TestRun
|
|
312
|
+
own: TestRun
|
|
313
|
+
test_files: string[]
|
|
314
|
+
}): TestRun =>
|
|
315
|
+
own.cases.length === 0 ? fast : joinRuns({ runs: [fast, own], test_files })
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Several test runs as one: every case, `ok` only when all passed, and a test
|
|
319
|
+
* file without results only when no run gave it any. One run is itself. No
|
|
320
|
+
* run (a run of refactor tickets only, which the red check skips) gives an
|
|
321
|
+
* empty passing run. Used for the baseline and the red check
|
|
322
|
+
* (`runBunTests`) and for a ticket's own slow test files (#564). Their
|
|
323
|
+
* waits for a test slot add up to the joined run's `wait_ms` (#544).
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* const run = joinRuns({ runs: [fast, own], test_files })
|
|
327
|
+
*/
|
|
328
|
+
export const joinRuns = ({
|
|
329
|
+
runs,
|
|
330
|
+
test_files,
|
|
331
|
+
}: {
|
|
332
|
+
runs: TestRun[]
|
|
333
|
+
test_files: string[]
|
|
334
|
+
}): TestRun => {
|
|
182
335
|
const [first] = runs
|
|
183
336
|
if (first === undefined) {
|
|
184
337
|
return {
|
|
@@ -194,8 +347,11 @@ export const runBunTests = async ({
|
|
|
194
347
|
if (runs.length === 1) return first
|
|
195
348
|
const cases = runs.flatMap((run) => run.cases)
|
|
196
349
|
const withResults = new Set(cases.map(({ file }) => file))
|
|
350
|
+
const waits = runs.flatMap(({ wait_ms }) =>
|
|
351
|
+
wait_ms === undefined ? [] : [wait_ms]
|
|
352
|
+
)
|
|
197
353
|
return {
|
|
198
|
-
command:
|
|
354
|
+
command: runs.map(({ command }) => command).join(' && '),
|
|
199
355
|
ok: runs.every(({ ok }) => ok),
|
|
200
356
|
exit_code: runs.find(({ ok }) => !ok)?.exit_code ?? first.exit_code,
|
|
201
357
|
no_test_files: runs.every(({ no_test_files }) => no_test_files),
|
|
@@ -208,5 +364,8 @@ export const runBunTests = async ({
|
|
|
208
364
|
.map(({ command, output }) => `$ ${command}\n${output}`)
|
|
209
365
|
.join('\n\n'),
|
|
210
366
|
}),
|
|
367
|
+
...(waits.length === 0
|
|
368
|
+
? {}
|
|
369
|
+
: { wait_ms: waits.reduce((total, wait) => total + wait, 0) }),
|
|
211
370
|
}
|
|
212
371
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createSlots, type Slots } from './prepare-slots'
|
|
2
|
+
import { onSlotWait } from './slot-waits'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The engine's test slots (#544): one set for the whole engine process, so
|
|
6
|
+
* `test_concurrency` caps every test run at once, whichever step runs it.
|
|
7
|
+
*/
|
|
8
|
+
export const TEST_SLOTS: Slots = createSlots()
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Runs `work` in a test slot, at most `test_concurrency` at once, and adds
|
|
12
|
+
* how long it waited for one as `wait_ms`; the work's own time limit and
|
|
13
|
+
* timing start inside, after the wait. The step's slot-wait listener hears
|
|
14
|
+
* the wait while it lasts (see `slot-waits.ts`). An `exclusive` take waits for every
|
|
15
|
+
* test run to end, and holds off new ones until it settles.
|
|
16
|
+
*
|
|
17
|
+
* With `test_concurrency` left out there is no cap, but the work still holds
|
|
18
|
+
* a slot, so an exclusive take waits for it too. Nothing waits then unless an
|
|
19
|
+
* exclusive take is going, and the result is as `work` gave it (no
|
|
20
|
+
* `wait_ms`). Work that already holds a slot must not take another: an
|
|
21
|
+
* exclusive take queued between the two would wait forever.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const check = await inTestSlot({ test_concurrency: 1, work: () => shellCheck({ ... }) })
|
|
25
|
+
* check.wait_ms // 0, or how long another test run held the slot
|
|
26
|
+
*/
|
|
27
|
+
export const inTestSlot = async <T extends object>({
|
|
28
|
+
test_concurrency,
|
|
29
|
+
work,
|
|
30
|
+
exclusive = false,
|
|
31
|
+
}: {
|
|
32
|
+
test_concurrency: number | undefined
|
|
33
|
+
work: () => Promise<T>
|
|
34
|
+
exclusive?: boolean
|
|
35
|
+
}): Promise<T & { wait_ms?: number }> => {
|
|
36
|
+
const asked = performance.now()
|
|
37
|
+
const limit = test_concurrency ?? Infinity
|
|
38
|
+
return TEST_SLOTS.run({
|
|
39
|
+
limit,
|
|
40
|
+
exclusive,
|
|
41
|
+
on_wait: onSlotWait({ kind: 'test_slot', limit }),
|
|
42
|
+
work: async () => {
|
|
43
|
+
if (test_concurrency === undefined && !exclusive) return work()
|
|
44
|
+
const wait_ms = Math.round(performance.now() - asked)
|
|
45
|
+
return { ...(await work()), wait_ms }
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
}
|