@quenty/nevermore-cli 4.41.1 → 4.42.0
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 +6 -0
- package/dist/commands/batch-command/batch-test-command.d.ts.map +1 -1
- package/dist/commands/batch-command/batch-test-command.js +18 -2
- package/dist/commands/batch-command/batch-test-command.js.map +1 -1
- package/dist/commands/test-command/test-command.d.ts.map +1 -1
- package/dist/commands/test-command/test-command.js +6 -4
- package/dist/commands/test-command/test-command.js.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.d.ts.map +1 -1
- package/dist/utils/open-cloud/open-cloud-client.js +4 -1
- package/dist/utils/open-cloud/open-cloud-client.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.d.ts +13 -5
- package/dist/utils/testing/parsers/batch-log-parser.d.ts.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.js +114 -68
- package/dist/utils/testing/parsers/batch-log-parser.js.map +1 -1
- package/dist/utils/testing/parsers/batch-log-parser.test.js +57 -5
- package/dist/utils/testing/parsers/batch-log-parser.test.js.map +1 -1
- package/dist/utils/testing/parsers/traceback-parser.d.ts +31 -0
- package/dist/utils/testing/parsers/traceback-parser.d.ts.map +1 -0
- package/dist/utils/testing/parsers/traceback-parser.js +91 -0
- package/dist/utils/testing/parsers/traceback-parser.js.map +1 -0
- package/dist/utils/testing/parsers/traceback-parser.test.d.ts +2 -0
- package/dist/utils/testing/parsers/traceback-parser.test.d.ts.map +1 -0
- package/dist/utils/testing/parsers/traceback-parser.test.js +45 -0
- package/dist/utils/testing/parsers/traceback-parser.test.js.map +1 -0
- package/dist/utils/testing/runner/test-runner.d.ts +8 -0
- package/dist/utils/testing/runner/test-runner.d.ts.map +1 -1
- package/dist/utils/testing/runner/test-runner.js +28 -3
- package/dist/utils/testing/runner/test-runner.js.map +1 -1
- package/dist/utils/testing/runner/test-runner.test.d.ts +2 -0
- package/dist/utils/testing/runner/test-runner.test.d.ts.map +1 -0
- package/dist/utils/testing/runner/test-runner.test.js +18 -0
- package/dist/utils/testing/runner/test-runner.test.js.map +1 -0
- package/dist/utils/testing/test-log-parser.d.ts +32 -1
- package/dist/utils/testing/test-log-parser.d.ts.map +1 -1
- package/dist/utils/testing/test-log-parser.js +53 -9
- package/dist/utils/testing/test-log-parser.js.map +1 -1
- package/dist/utils/testing/test-log-parser.test.d.ts +2 -0
- package/dist/utils/testing/test-log-parser.test.d.ts.map +1 -0
- package/dist/utils/testing/test-log-parser.test.js +56 -0
- package/dist/utils/testing/test-log-parser.test.js.map +1 -0
- package/package.json +2 -2
- package/src/commands/batch-command/batch-test-command.ts +78 -58
- package/src/commands/test-command/test-command.ts +7 -4
- package/src/utils/open-cloud/open-cloud-client.ts +4 -1
- package/src/utils/testing/parsers/batch-log-parser.test.ts +70 -5
- package/src/utils/testing/parsers/batch-log-parser.ts +141 -80
- package/src/utils/testing/parsers/traceback-parser.test.ts +64 -0
- package/src/utils/testing/parsers/traceback-parser.ts +122 -0
- package/src/utils/testing/runner/test-runner.test.ts +29 -0
- package/src/utils/testing/runner/test-runner.ts +35 -3
- package/src/utils/testing/test-log-parser.test.ts +70 -0
- package/src/utils/testing/test-log-parser.ts +80 -10
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { evaluateTestOutcome, hasTestReport } from './test-log-parser.js';
|
|
3
|
+
describe('evaluateTestOutcome', () => {
|
|
4
|
+
it('passes a clean jest report', () => {
|
|
5
|
+
const outcome = evaluateTestOutcome('Tests: 275 passed, 275 total', {
|
|
6
|
+
requireTestReport: true,
|
|
7
|
+
});
|
|
8
|
+
expect(outcome.success).toBe(true);
|
|
9
|
+
expect(outcome.failureReasons).toEqual([]);
|
|
10
|
+
});
|
|
11
|
+
it('refuses to pass output with no jest report in it', () => {
|
|
12
|
+
// Absence of failure was previously read as success, so an empty log passed.
|
|
13
|
+
const outcome = evaluateTestOutcome('some unrelated chatter', {
|
|
14
|
+
requireTestReport: true,
|
|
15
|
+
});
|
|
16
|
+
expect(outcome.success).toBe(false);
|
|
17
|
+
expect(outcome.failureReasons.join()).toContain('no jest report');
|
|
18
|
+
});
|
|
19
|
+
it('exempts probe runs, which have no jest in them at all', () => {
|
|
20
|
+
const outcome = evaluateTestOutcome('hello from a probe script');
|
|
21
|
+
expect(outcome.success).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it('fails on a traceback even when every test passed', () => {
|
|
24
|
+
// Jest cannot see a deferred-callback crash: it fires outside any test.
|
|
25
|
+
const logs = [
|
|
26
|
+
'Tests: 155 passed, 155 total',
|
|
27
|
+
'attempt to index nil',
|
|
28
|
+
'Stack Begin',
|
|
29
|
+
"Script 'ServerScriptService.egghunt2026.Foo', Line 12",
|
|
30
|
+
'Stack End',
|
|
31
|
+
].join('\n');
|
|
32
|
+
const outcome = evaluateTestOutcome(logs, { requireTestReport: true });
|
|
33
|
+
expect(outcome.success).toBe(false);
|
|
34
|
+
expect(outcome.failureReasons.join()).toContain('traceback');
|
|
35
|
+
});
|
|
36
|
+
it('reports every reason a run failed, not just the first', () => {
|
|
37
|
+
const logs = [
|
|
38
|
+
'Tests: 19 failed, 470 passed, 516 total',
|
|
39
|
+
'Stack Begin',
|
|
40
|
+
"Script 'ServerScriptService.egghunt2026.Foo', Line 12",
|
|
41
|
+
'Stack End',
|
|
42
|
+
].join('\n');
|
|
43
|
+
const outcome = evaluateTestOutcome(logs, { requireTestReport: true });
|
|
44
|
+
expect(outcome.failureReasons).toHaveLength(2);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('hasTestReport', () => {
|
|
48
|
+
it('accepts a runner that ran and found nothing', () => {
|
|
49
|
+
// Packages without specs are legitimate; passWithNoTests depends on this.
|
|
50
|
+
expect(hasTestReport('No tests found, exiting with code 0')).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it('rejects silence', () => {
|
|
53
|
+
expect(hasTestReport('')).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=test-log-parser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-log-parser.test.js","sourceRoot":"","sources":["../../../src/utils/testing/test-log-parser.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1E,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,OAAO,GAAG,mBAAmB,CAAC,8BAA8B,EAAE;YAClE,iBAAiB,EAAE,IAAI;SACxB,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,6EAA6E;QAC7E,MAAM,OAAO,GAAG,mBAAmB,CAAC,wBAAwB,EAAE;YAC5D,iBAAiB,EAAE,IAAI;SACxB,CAAC,CAAC;QAEH,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,OAAO,GAAG,mBAAmB,CAAC,2BAA2B,CAAC,CAAC;QAEjE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,wEAAwE;QACxE,MAAM,IAAI,GAAG;YACX,8BAA8B;YAC9B,sBAAsB;YACtB,aAAa;YACb,uDAAuD;YACvD,WAAW;SACZ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpC,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,IAAI,GAAG;YACX,yCAAyC;YACzC,aAAa;YACb,uDAAuD;YACvD,WAAW;SACZ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEb,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvE,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,0EAA0E;QAC1E,MAAM,CAAC,aAAa,CAAC,qCAAqC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/nevermore-cli",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.42.0",
|
|
4
4
|
"description": "CLI interface for Nevermore",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -57,5 +57,5 @@
|
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=16"
|
|
59
59
|
},
|
|
60
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "481392a58dedd5ee476d66c90c14e2f429e23df8"
|
|
61
61
|
}
|
|
@@ -60,64 +60,84 @@ export const batchTestCommand: CommandModule<
|
|
|
60
60
|
command: 'test',
|
|
61
61
|
describe: 'Run tests for changed packages with test targets',
|
|
62
62
|
builder: (yargs) => {
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
63
|
+
return (
|
|
64
|
+
yargs
|
|
65
|
+
.option('cloud', {
|
|
66
|
+
describe: 'Run tests via Open Cloud instead of locally',
|
|
67
|
+
type: 'boolean',
|
|
68
|
+
default: false,
|
|
69
|
+
})
|
|
70
|
+
.option('api-key', {
|
|
71
|
+
describe: 'Roblox Open Cloud API key (--cloud only)',
|
|
72
|
+
type: 'string',
|
|
73
|
+
})
|
|
74
|
+
.option('all', {
|
|
75
|
+
describe: 'Test all packages with test targets, not just changed',
|
|
76
|
+
type: 'boolean',
|
|
77
|
+
default: false,
|
|
78
|
+
})
|
|
79
|
+
.option('base', {
|
|
80
|
+
describe: 'Git ref to diff against for change detection',
|
|
81
|
+
type: 'string',
|
|
82
|
+
default: 'origin/main',
|
|
83
|
+
})
|
|
84
|
+
.option('concurrency', {
|
|
85
|
+
describe: 'Max parallel tests (0 = unlimited, default: unlimited)',
|
|
86
|
+
type: 'number',
|
|
87
|
+
})
|
|
88
|
+
.option('output', {
|
|
89
|
+
describe: 'Write JSON results to this file',
|
|
90
|
+
type: 'string',
|
|
91
|
+
})
|
|
92
|
+
.option('limit', {
|
|
93
|
+
describe: 'Max number of packages to test (for local debugging)',
|
|
94
|
+
type: 'number',
|
|
95
|
+
})
|
|
96
|
+
.option('logs', {
|
|
97
|
+
describe: 'Show execution logs for all packages (not just failures)',
|
|
98
|
+
type: 'boolean',
|
|
99
|
+
default: false,
|
|
100
|
+
})
|
|
101
|
+
.option('aggregated', {
|
|
102
|
+
describe:
|
|
103
|
+
'Build all packages into a single place and execute one batch script',
|
|
104
|
+
type: 'boolean',
|
|
105
|
+
default: true,
|
|
106
|
+
})
|
|
107
|
+
.option('batch-place-id', {
|
|
108
|
+
describe:
|
|
109
|
+
'Override placeId for the aggregated batch upload (--aggregated only)',
|
|
110
|
+
type: 'number',
|
|
111
|
+
})
|
|
112
|
+
.option('batch-universe-id', {
|
|
113
|
+
describe:
|
|
114
|
+
'Override universeId for the aggregated batch upload (--aggregated only)',
|
|
115
|
+
type: 'number',
|
|
116
|
+
})
|
|
117
|
+
.option('timeout', {
|
|
118
|
+
describe:
|
|
119
|
+
'Max script execution time in seconds for the whole batch. Sent to the Open Cloud API so Roblox cancels server-side on overrun. Max 300s (API limit). (default: 300)',
|
|
120
|
+
type: 'number',
|
|
121
|
+
})
|
|
122
|
+
// Accepted only to fail with a useful message. A batch runs one shared
|
|
123
|
+
// script across packages, so there is nothing for arbitrary Luau to mean
|
|
124
|
+
// here — but yargs strict mode would otherwise reject it with a bare
|
|
125
|
+
// "Unknown argument" that does not say where to go instead.
|
|
126
|
+
.option('script-text', {
|
|
127
|
+
hidden: true,
|
|
128
|
+
type: 'string',
|
|
129
|
+
})
|
|
130
|
+
.check((args) => {
|
|
131
|
+
if (args.scriptText !== undefined) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
'--script-text is not supported by "batch test", which runs one shared script for all packages.\n' +
|
|
134
|
+
'Run it against a single package instead:\n' +
|
|
135
|
+
' cd <package> && nevermore test --cloud --script-text \'print("hi")\''
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
})
|
|
140
|
+
);
|
|
121
141
|
},
|
|
122
142
|
handler: async (args) => {
|
|
123
143
|
try {
|
|
@@ -56,9 +56,9 @@ export class TestProjectCommand<T>
|
|
|
56
56
|
type: 'string',
|
|
57
57
|
});
|
|
58
58
|
args.option('logs', {
|
|
59
|
-
describe:
|
|
59
|
+
describe:
|
|
60
|
+
'Show execution logs. Defaults on with --script-text, whose output is the whole point of the run',
|
|
60
61
|
type: 'boolean',
|
|
61
|
-
default: false,
|
|
62
62
|
});
|
|
63
63
|
args.option('universe-id', {
|
|
64
64
|
describe:
|
|
@@ -84,7 +84,7 @@ export class TestProjectCommand<T>
|
|
|
84
84
|
});
|
|
85
85
|
args.option('timeout', {
|
|
86
86
|
describe:
|
|
87
|
-
'Max script execution time in seconds. Sent to the Open Cloud API so Roblox cancels server-side on overrun (default:
|
|
87
|
+
'Max script execution time in seconds. Sent to the Open Cloud API so Roblox cancels server-side on overrun. Max 300s (API limit). (default: 300)',
|
|
88
88
|
type: 'number',
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -94,7 +94,10 @@ export class TestProjectCommand<T>
|
|
|
94
94
|
public handler = async (args: TestProjectArgs) => {
|
|
95
95
|
const cwd = process.cwd();
|
|
96
96
|
const packageName = (await readPackageNameAsync(cwd)) ?? path.basename(cwd);
|
|
97
|
-
|
|
97
|
+
// A probe run exists to show its output. Hiding it behind an opt-in flag
|
|
98
|
+
// made --script-text look broken enough that a workaround was written into
|
|
99
|
+
// the docs instead of a bug report.
|
|
100
|
+
const showLogs = args.logs ?? args.scriptText !== undefined;
|
|
98
101
|
const useSpinner = process.stdout.isTTY && !args.verbose;
|
|
99
102
|
|
|
100
103
|
const reporter = new CompositeReporter(
|
|
@@ -339,6 +339,7 @@ export class OpenCloudClient {
|
|
|
339
339
|
const structured: { message: string; createTime: string }[] = [];
|
|
340
340
|
let pageToken: string | undefined;
|
|
341
341
|
let pagesFetched = 0;
|
|
342
|
+
let entriesFetched = 0;
|
|
342
343
|
|
|
343
344
|
do {
|
|
344
345
|
const url = new URL(`https://apis.roblox.com/cloud/v2/${taskPath}/logs`);
|
|
@@ -386,6 +387,7 @@ export class OpenCloudClient {
|
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
|
|
390
|
+
entriesFetched += (data.luauExecutionSessionTaskLogs ?? []).length;
|
|
389
391
|
pagesFetched++;
|
|
390
392
|
pageToken = data.nextPageToken || undefined;
|
|
391
393
|
} while (pageToken);
|
|
@@ -402,7 +404,8 @@ export class OpenCloudClient {
|
|
|
402
404
|
// the task and the parser, and the parser cannot tell a short run from a
|
|
403
405
|
// truncated fetch. Record what arrived so a real run can settle it.
|
|
404
406
|
OutputHelper.verbose(
|
|
405
|
-
`[open-cloud] Task logs: ${pagesFetched} page(s), ${
|
|
407
|
+
`[open-cloud] Task logs: ${pagesFetched} page(s), ${entriesFetched} entries, ` +
|
|
408
|
+
`${messages.length} messages, ${text.length} chars`
|
|
406
409
|
);
|
|
407
410
|
|
|
408
411
|
return text;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
countTracebacks,
|
|
5
|
+
findSummaryEntries,
|
|
6
|
+
parseBatchTestLogs,
|
|
7
|
+
} from './batch-log-parser.js';
|
|
4
8
|
|
|
5
9
|
const SLUG_MAP = new Map([['egghunt2026', 'egghunt2026']]);
|
|
6
10
|
|
|
@@ -23,10 +27,11 @@ describe('parseBatchTestLogs', () => {
|
|
|
23
27
|
expect(result?.testCounts).toEqual({ passed: 275, failed: 0, total: 275 });
|
|
24
28
|
});
|
|
25
29
|
|
|
26
|
-
it('
|
|
27
|
-
//
|
|
28
|
-
//
|
|
30
|
+
it('attributes a section closed by END when its BEGIN was dropped', () => {
|
|
31
|
+
// Open Cloud keeps only the tail of a long run's log, so BEGIN — printed
|
|
32
|
+
// first — is the first casualty while END and the summary survive.
|
|
29
33
|
const logs = [
|
|
34
|
+
' ✓ some test that survived (3 ms)',
|
|
30
35
|
'Tests: 275 passed, 275 total',
|
|
31
36
|
'===BATCH_TEST_END egghunt2026 PASS 1000===',
|
|
32
37
|
'===BATCH_TEST_SUMMARY===',
|
|
@@ -35,8 +40,48 @@ describe('parseBatchTestLogs', () => {
|
|
|
35
40
|
|
|
36
41
|
const result = parseBatchTestLogs(logs, SLUG_MAP).get('egghunt2026');
|
|
37
42
|
|
|
43
|
+
expect(result?.success).toBe(true);
|
|
44
|
+
expect(result?.testCounts).toEqual({ passed: 275, failed: 0, total: 275 });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('does not let an out-of-order END steal another package’s output', () => {
|
|
48
|
+
// The API does not deliver messages in order, so a stray END can arrive
|
|
49
|
+
// mid-section. Only the first section can have lost its head; past that the
|
|
50
|
+
// log is well-formed and a BEGIN-less END must not claim anything.
|
|
51
|
+
const twoPackages = new Map([
|
|
52
|
+
['alpha', 'alpha'],
|
|
53
|
+
['beta', 'beta'],
|
|
54
|
+
]);
|
|
55
|
+
const logs = [
|
|
56
|
+
'===BATCH_TEST_BEGIN alpha===',
|
|
57
|
+
'Tests: 10 passed, 10 total',
|
|
58
|
+
'===BATCH_TEST_END beta PASS 5===',
|
|
59
|
+
'===BATCH_TEST_END alpha PASS 10===',
|
|
60
|
+
'===BATCH_TEST_SUMMARY===',
|
|
61
|
+
'[{"slug":"alpha","success":true},{"slug":"beta","success":true}]',
|
|
62
|
+
].join('\n');
|
|
63
|
+
|
|
64
|
+
const results = parseBatchTestLogs(logs, twoPackages);
|
|
65
|
+
|
|
66
|
+
expect(results.get('alpha')?.testCounts?.total).toBe(10);
|
|
67
|
+
// beta never had a section of its own; it must not inherit alpha's.
|
|
68
|
+
expect(results.get('beta')?.testCounts).toBeUndefined();
|
|
69
|
+
expect(results.get('beta')?.success).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('fails a pass it cannot read, rather than trusting the pcall', () => {
|
|
73
|
+
// No END marker either, so there is no boundary to attribute output with.
|
|
74
|
+
// The pcall said success, but nothing here saw a test run.
|
|
75
|
+
const logs = [
|
|
76
|
+
'Tests: 275 passed, 275 total',
|
|
77
|
+
'===BATCH_TEST_SUMMARY===',
|
|
78
|
+
'[{"slug":"egghunt2026","success":true,"durationMs":1000}]',
|
|
79
|
+
].join('\n');
|
|
80
|
+
|
|
81
|
+
const result = parseBatchTestLogs(logs, SLUG_MAP).get('egghunt2026');
|
|
82
|
+
|
|
38
83
|
expect(result?.success).toBe(false);
|
|
39
|
-
expect(result?.error).toContain('
|
|
84
|
+
expect(result?.error).toContain('no output could be attributed');
|
|
40
85
|
expect(result?.testCounts).toBeUndefined();
|
|
41
86
|
});
|
|
42
87
|
|
|
@@ -72,6 +117,26 @@ describe('parseBatchTestLogs', () => {
|
|
|
72
117
|
});
|
|
73
118
|
});
|
|
74
119
|
|
|
120
|
+
describe('findSummaryEntries', () => {
|
|
121
|
+
it('finds the summary even when output landed after it', () => {
|
|
122
|
+
// Messages are not delivered in order, so a stray line can follow the
|
|
123
|
+
// summary. Treating the whole remainder as one JSON blob made the same
|
|
124
|
+
// batch parse on one run and fail on the next.
|
|
125
|
+
const lines = [
|
|
126
|
+
'[{"slug":"alpha","success":true}]',
|
|
127
|
+
' ✓ a stray line that arrived late',
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
expect(findSummaryEntries(lines, 0)).toEqual([
|
|
131
|
+
{ slug: 'alpha', success: true },
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns undefined when there is no array to find', () => {
|
|
136
|
+
expect(findSummaryEntries(['not json at all'], 0)).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
75
140
|
describe('countTracebacks', () => {
|
|
76
141
|
it('counts each traceback separately', () => {
|
|
77
142
|
expect(countTracebacks('Stack Begin\nfoo\nStack End\nStack Begin\n')).toBe(
|