@quenty/nevermore-cli 4.50.0 → 4.51.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 +12 -0
- package/dist/commands/batch-command/batch-bundle-command.d.ts +12 -0
- package/dist/commands/batch-command/batch-bundle-command.d.ts.map +1 -0
- package/dist/commands/batch-command/batch-bundle-command.js +96 -0
- package/dist/commands/batch-command/batch-bundle-command.js.map +1 -0
- package/dist/commands/batch-command/batch-test-command.d.ts +1 -0
- package/dist/commands/batch-command/batch-test-command.d.ts.map +1 -1
- package/dist/commands/batch-command/batch-test-command.js +103 -15
- package/dist/commands/batch-command/batch-test-command.js.map +1 -1
- package/dist/commands/batch-command/index.d.ts.map +1 -1
- package/dist/commands/batch-command/index.js +2 -0
- package/dist/commands/batch-command/index.js.map +1 -1
- package/dist/commands/tools-command/ci-post-test-results.d.ts +1 -0
- package/dist/commands/tools-command/ci-post-test-results.d.ts.map +1 -1
- package/dist/commands/tools-command/ci-post-test-results.js +13 -4
- package/dist/commands/tools-command/ci-post-test-results.js.map +1 -1
- package/dist/utils/batch/changed-packages-utils.d.ts +8 -0
- package/dist/utils/batch/changed-packages-utils.d.ts.map +1 -1
- package/dist/utils/batch/changed-packages-utils.js +9 -11
- package/dist/utils/batch/changed-packages-utils.js.map +1 -1
- package/dist/utils/job-context/batch-script-job-context.d.ts +12 -0
- package/dist/utils/job-context/batch-script-job-context.d.ts.map +1 -1
- package/dist/utils/job-context/batch-script-job-context.js +19 -0
- package/dist/utils/job-context/batch-script-job-context.js.map +1 -1
- package/dist/utils/sourcemap/sourcemap-loader.d.ts +3 -3
- package/dist/utils/sourcemap/sourcemap-loader.d.ts.map +1 -1
- package/dist/utils/sourcemap/sourcemap-loader.js +6 -7
- package/dist/utils/sourcemap/sourcemap-loader.js.map +1 -1
- package/dist/utils/testing/batch-run.e2e.test.js +36 -0
- package/dist/utils/testing/batch-run.e2e.test.js.map +1 -1
- package/dist/utils/testing/parsers/annotation-workspace.d.ts +17 -0
- package/dist/utils/testing/parsers/annotation-workspace.d.ts.map +1 -0
- package/dist/utils/testing/parsers/annotation-workspace.js +47 -0
- package/dist/utils/testing/parsers/annotation-workspace.js.map +1 -0
- package/dist/utils/testing/parsers/index.d.ts +0 -1
- package/dist/utils/testing/parsers/index.d.ts.map +1 -1
- package/dist/utils/testing/parsers/index.js +0 -1
- package/dist/utils/testing/parsers/index.js.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts +14 -29
- package/dist/utils/testing/parsers/jest-lua-parser.d.ts.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.js +85 -100
- package/dist/utils/testing/parsers/jest-lua-parser.js.map +1 -1
- package/dist/utils/testing/parsers/jest-lua-parser.test.js +359 -56
- package/dist/utils/testing/parsers/jest-lua-parser.test.js.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts +23 -17
- package/dist/utils/testing/parsers/roblox-path-resolver.d.ts.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.js +89 -54
- package/dist/utils/testing/parsers/roblox-path-resolver.js.map +1 -1
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js +76 -36
- package/dist/utils/testing/parsers/roblox-path-resolver.test.js.map +1 -1
- package/dist/utils/testing/runner/combined-project-generator.d.ts.map +1 -1
- package/dist/utils/testing/runner/combined-project-generator.js +5 -6
- package/dist/utils/testing/runner/combined-project-generator.js.map +1 -1
- package/dist/utils/testing/runner/prebuilt-batch-bundle.d.ts +26 -0
- package/dist/utils/testing/runner/prebuilt-batch-bundle.d.ts.map +1 -0
- package/dist/utils/testing/runner/prebuilt-batch-bundle.js +200 -0
- package/dist/utils/testing/runner/prebuilt-batch-bundle.js.map +1 -0
- package/dist/utils/testing/runner/prebuilt-batch-bundle.test.d.ts +2 -0
- package/dist/utils/testing/runner/prebuilt-batch-bundle.test.d.ts.map +1 -0
- package/dist/utils/testing/runner/prebuilt-batch-bundle.test.js +181 -0
- package/dist/utils/testing/runner/prebuilt-batch-bundle.test.js.map +1 -0
- package/dist/utils/testing/runner/test-project-slug.d.ts +11 -0
- package/dist/utils/testing/runner/test-project-slug.d.ts.map +1 -0
- package/dist/utils/testing/runner/test-project-slug.js +11 -0
- package/dist/utils/testing/runner/test-project-slug.js.map +1 -0
- package/dist/utils/testing/runner/test-runner.d.ts +8 -1
- package/dist/utils/testing/runner/test-runner.d.ts.map +1 -1
- package/dist/utils/testing/runner/test-runner.js +19 -6
- package/dist/utils/testing/runner/test-runner.js.map +1 -1
- package/package.json +7 -6
- package/src/commands/batch-command/batch-bundle-command.ts +140 -0
- package/src/commands/batch-command/batch-test-command.ts +156 -16
- package/src/commands/batch-command/index.ts +2 -0
- package/src/commands/tools-command/ci-post-test-results.ts +18 -3
- package/src/utils/batch/changed-packages-utils.ts +21 -12
- package/src/utils/job-context/batch-script-job-context.ts +34 -3
- package/src/utils/sourcemap/sourcemap-loader.ts +7 -9
- package/src/utils/testing/batch-run.e2e.test.ts +42 -0
- package/src/utils/testing/parsers/annotation-workspace.ts +74 -0
- package/src/utils/testing/parsers/index.ts +0 -1
- package/src/utils/testing/parsers/jest-lua-parser.test.ts +433 -65
- package/src/utils/testing/parsers/jest-lua-parser.ts +131 -109
- package/src/utils/testing/parsers/roblox-path-resolver.test.ts +144 -61
- package/src/utils/testing/parsers/roblox-path-resolver.ts +117 -56
- package/src/utils/testing/runner/combined-project-generator.ts +9 -13
- package/src/utils/testing/runner/prebuilt-batch-bundle.test.ts +252 -0
- package/src/utils/testing/runner/prebuilt-batch-bundle.ts +267 -0
- package/src/utils/testing/runner/test-project-slug.ts +18 -0
- package/src/utils/testing/runner/test-runner.ts +43 -13
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import { createReadStream, createWriteStream } from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as tar from 'tar';
|
|
5
|
+
import { BuildContext } from '@quenty/nevermore-template-helpers';
|
|
6
|
+
import { type CombinedPlaceChunk } from './combined-project-generator.js';
|
|
7
|
+
import { PLACE_UPLOAD_MAX_BYTES } from '../../open-cloud/open-cloud-client.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Places built by `nevermore batch bundle`, unpacked from a
|
|
11
|
+
* `*.nevermore.tests.tar` and ready for `batch test --bundle` to run.
|
|
12
|
+
*/
|
|
13
|
+
export interface PrebuiltBatchBundle {
|
|
14
|
+
chunks: CombinedPlaceChunk[];
|
|
15
|
+
packages: PrebuiltBatchPackage[];
|
|
16
|
+
/** Holds the unpacked places; whoever runs them owns its cleanup. */
|
|
17
|
+
buildContext: BuildContext;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface PrebuiltBatchPackage {
|
|
21
|
+
name: string;
|
|
22
|
+
hasJestConfig: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ManifestFile {
|
|
26
|
+
formatVersion: number;
|
|
27
|
+
chunks: Array<{
|
|
28
|
+
file: string;
|
|
29
|
+
packages: Array<{ name: string; slug: string; hasJestConfig: boolean }>;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Suggested file suffix, following `deploy.nevermore.json`. */
|
|
34
|
+
export const BUNDLE_FILE_SUFFIX = '.nevermore.tests.tar';
|
|
35
|
+
|
|
36
|
+
const MANIFEST_FILE_NAME = 'manifest.json';
|
|
37
|
+
const FORMAT_VERSION = 1;
|
|
38
|
+
const MAX_CHUNKS = 32;
|
|
39
|
+
const MAX_PACKAGES = 1000;
|
|
40
|
+
const MAX_MANIFEST_BYTES = 1024 * 1024;
|
|
41
|
+
const CHUNK_FILE_PATTERN = /^place-\d{1,3}\.rbxl$/;
|
|
42
|
+
// Names reach the bot's PR comment, so a fork's bundle gets no Markdown links,
|
|
43
|
+
// images, URLs, tables or @mentions: only a leading scope `@`, and plain text
|
|
44
|
+
// for a multi-place target's place name ("pkg - Hub (Test)").
|
|
45
|
+
const PACKAGE_NAME_PATTERN = /^@?[\p{L}\p{N} /()+.,'_~-]{1,200}$/u;
|
|
46
|
+
const SLUG_PATTERN = /^[A-Za-z0-9_.-]{1,100}$/;
|
|
47
|
+
|
|
48
|
+
export async function writePrebuiltBatchBundleAsync(
|
|
49
|
+
file: string,
|
|
50
|
+
chunks: CombinedPlaceChunk[],
|
|
51
|
+
hasJestConfig: Map<string, boolean>
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const stage = await BuildContext.createAsync({ prefix: 'test-bundle-' });
|
|
54
|
+
const stageDir = stage.buildDir;
|
|
55
|
+
try {
|
|
56
|
+
const manifest: ManifestFile = {
|
|
57
|
+
formatVersion: FORMAT_VERSION,
|
|
58
|
+
chunks: [],
|
|
59
|
+
};
|
|
60
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
61
|
+
const placeFile = `place-${index + 1}.rbxl`;
|
|
62
|
+
await fs.copyFile(chunk.rbxlPath, path.join(stageDir, placeFile));
|
|
63
|
+
manifest.chunks.push({
|
|
64
|
+
file: placeFile,
|
|
65
|
+
packages: [...chunk.slugMap].map(([name, slug]) => ({
|
|
66
|
+
name,
|
|
67
|
+
slug,
|
|
68
|
+
hasJestConfig: hasJestConfig.get(name) ?? false,
|
|
69
|
+
})),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
await fs.writeFile(
|
|
73
|
+
path.join(stageDir, MANIFEST_FILE_NAME),
|
|
74
|
+
JSON.stringify(manifest, null, 2)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
await fs.mkdir(path.dirname(path.resolve(file)), { recursive: true });
|
|
78
|
+
await tar.c({ file, cwd: stageDir, portable: true }, [
|
|
79
|
+
MANIFEST_FILE_NAME,
|
|
80
|
+
...manifest.chunks.map((c) => c.file),
|
|
81
|
+
]);
|
|
82
|
+
} finally {
|
|
83
|
+
await stage.cleanupAsync();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read a bundle file. It is untrusted: entries are read from the archive, never
|
|
89
|
+
* extracted by it, and only `manifest.json` and `place-N.rbxl` regular files
|
|
90
|
+
* are accepted.
|
|
91
|
+
*/
|
|
92
|
+
export async function readPrebuiltBatchBundleAsync(
|
|
93
|
+
file: string
|
|
94
|
+
): Promise<PrebuiltBatchBundle> {
|
|
95
|
+
let stat;
|
|
96
|
+
try {
|
|
97
|
+
stat = await fs.lstat(file);
|
|
98
|
+
} catch {
|
|
99
|
+
throw new Error(`Bundle file not found: ${file}`);
|
|
100
|
+
}
|
|
101
|
+
if (!stat.isFile()) {
|
|
102
|
+
throw new Error(`Bundle ${file} is not a regular file`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const buildContext = await BuildContext.createAsync({
|
|
106
|
+
prefix: 'test-bundle-',
|
|
107
|
+
});
|
|
108
|
+
const dir = buildContext.buildDir;
|
|
109
|
+
try {
|
|
110
|
+
const { manifestText, placeSizes } = await _unpackAsync(file, dir);
|
|
111
|
+
return { ..._parseManifest(manifestText, dir, placeSizes), buildContext };
|
|
112
|
+
} catch (err) {
|
|
113
|
+
await buildContext.cleanupAsync();
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function _unpackAsync(
|
|
119
|
+
file: string,
|
|
120
|
+
dir: string
|
|
121
|
+
): Promise<{ manifestText: string; placeSizes: Map<string, number> }> {
|
|
122
|
+
let manifestText: string | undefined;
|
|
123
|
+
const placeSizes = new Map<string, number>();
|
|
124
|
+
const writes: Promise<void>[] = [];
|
|
125
|
+
let failure: Error | undefined;
|
|
126
|
+
|
|
127
|
+
const parser = new tar.Parser({
|
|
128
|
+
strict: true,
|
|
129
|
+
onReadEntry: (entry) => {
|
|
130
|
+
const name = entry.path;
|
|
131
|
+
const reject = (message: string) => {
|
|
132
|
+
failure ??= new Error(message);
|
|
133
|
+
entry.resume();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (entry.type !== 'File' && entry.type !== 'OldFile') {
|
|
137
|
+
return reject(`Bundle entry ${name} is not a regular file`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (name === MANIFEST_FILE_NAME) {
|
|
141
|
+
if (manifestText !== undefined || entry.size > MAX_MANIFEST_BYTES) {
|
|
142
|
+
return reject('Bundle has more than one manifest, or one too large');
|
|
143
|
+
}
|
|
144
|
+
const parts: Buffer[] = [];
|
|
145
|
+
entry.on('data', (part: Buffer) => parts.push(part));
|
|
146
|
+
entry.on('end', () => {
|
|
147
|
+
manifestText = Buffer.concat(parts).toString('utf-8');
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!CHUNK_FILE_PATTERN.test(name) || placeSizes.has(name)) {
|
|
153
|
+
return reject(`Bundle has an unexpected entry: ${name}`);
|
|
154
|
+
}
|
|
155
|
+
if (placeSizes.size >= MAX_CHUNKS) {
|
|
156
|
+
return reject(`Bundle must hold at most ${MAX_CHUNKS} places`);
|
|
157
|
+
}
|
|
158
|
+
if (entry.size > PLACE_UPLOAD_MAX_BYTES) {
|
|
159
|
+
return reject(`${name} is over the place upload limit`);
|
|
160
|
+
}
|
|
161
|
+
placeSizes.set(name, entry.size);
|
|
162
|
+
writes.push(
|
|
163
|
+
new Promise<void>((resolve, rejectWrite) => {
|
|
164
|
+
const out = createWriteStream(path.join(dir, name));
|
|
165
|
+
out.on('finish', () => resolve());
|
|
166
|
+
out.on('error', rejectWrite);
|
|
167
|
+
entry.on('error', rejectWrite);
|
|
168
|
+
entry.pipe(out);
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await new Promise<void>((resolve, rejectRead) => {
|
|
175
|
+
const input = createReadStream(file);
|
|
176
|
+
input.on('error', rejectRead);
|
|
177
|
+
parser.on('error', (err: Error) =>
|
|
178
|
+
rejectRead(new Error(`Bundle is not a valid tar file: ${err.message}`))
|
|
179
|
+
);
|
|
180
|
+
parser.on('end', () => resolve());
|
|
181
|
+
input.pipe(parser);
|
|
182
|
+
});
|
|
183
|
+
await Promise.all(writes);
|
|
184
|
+
|
|
185
|
+
if (failure) {
|
|
186
|
+
throw failure;
|
|
187
|
+
}
|
|
188
|
+
if (manifestText === undefined) {
|
|
189
|
+
throw new Error(`Bundle is missing ${MANIFEST_FILE_NAME}`);
|
|
190
|
+
}
|
|
191
|
+
return { manifestText, placeSizes };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function _parseManifest(
|
|
195
|
+
manifestText: string,
|
|
196
|
+
dir: string,
|
|
197
|
+
placeSizes: Map<string, number>
|
|
198
|
+
): { chunks: CombinedPlaceChunk[]; packages: PrebuiltBatchPackage[] } {
|
|
199
|
+
let parsed: unknown;
|
|
200
|
+
try {
|
|
201
|
+
parsed = JSON.parse(manifestText);
|
|
202
|
+
} catch {
|
|
203
|
+
throw new Error('Bundle manifest is not valid JSON');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const manifest = parsed as Partial<ManifestFile>;
|
|
207
|
+
if (manifest?.formatVersion !== FORMAT_VERSION) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`Unsupported bundle format ${String(manifest?.formatVersion)} ` +
|
|
210
|
+
`(expected ${FORMAT_VERSION})`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (!Array.isArray(manifest.chunks) || manifest.chunks.length > MAX_CHUNKS) {
|
|
214
|
+
throw new Error(`Bundle must list at most ${MAX_CHUNKS} places`);
|
|
215
|
+
}
|
|
216
|
+
if (manifest.chunks.length !== placeSizes.size) {
|
|
217
|
+
throw new Error('Bundle manifest does not list every place it holds');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const seenNames = new Set<string>();
|
|
221
|
+
const seenSlugs = new Set<string>();
|
|
222
|
+
const seenFiles = new Set<string>();
|
|
223
|
+
const chunks: CombinedPlaceChunk[] = [];
|
|
224
|
+
const packages: PrebuiltBatchPackage[] = [];
|
|
225
|
+
|
|
226
|
+
for (const entry of manifest.chunks) {
|
|
227
|
+
const file = entry?.file;
|
|
228
|
+
const sizeBytes =
|
|
229
|
+
typeof file === 'string' ? placeSizes.get(file) : undefined;
|
|
230
|
+
if (sizeBytes === undefined || seenFiles.has(file!)) {
|
|
231
|
+
throw new Error(`Bundle lists an invalid place file: ${String(file)}`);
|
|
232
|
+
}
|
|
233
|
+
seenFiles.add(file!);
|
|
234
|
+
|
|
235
|
+
if (!Array.isArray(entry.packages) || entry.packages.length === 0) {
|
|
236
|
+
throw new Error(`${file} lists no packages`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const slugMap = new Map<string, string>();
|
|
240
|
+
for (const pkg of entry.packages) {
|
|
241
|
+
const { name, slug, hasJestConfig } = pkg ?? {};
|
|
242
|
+
if (typeof name !== 'string' || !PACKAGE_NAME_PATTERN.test(name)) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Bundle lists an invalid package name: ${String(name)}`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
if (typeof slug !== 'string' || !SLUG_PATTERN.test(slug)) {
|
|
248
|
+
throw new Error(`Bundle lists an invalid slug for ${name}`);
|
|
249
|
+
}
|
|
250
|
+
if (seenNames.has(name) || seenSlugs.has(slug)) {
|
|
251
|
+
throw new Error(`Bundle lists ${name} (${slug}) more than once`);
|
|
252
|
+
}
|
|
253
|
+
seenNames.add(name);
|
|
254
|
+
seenSlugs.add(slug);
|
|
255
|
+
slugMap.set(name, slug);
|
|
256
|
+
packages.push({ name, hasJestConfig: hasJestConfig === true });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
chunks.push({ rbxlPath: path.join(dir, file!), slugMap, sizeBytes });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (packages.length > MAX_PACKAGES) {
|
|
263
|
+
throw new Error(`Bundle lists more than ${MAX_PACKAGES} packages`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { chunks, packages };
|
|
267
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface TestRojoProject {
|
|
2
|
+
tree?: { ServerScriptService?: Record<string, unknown> };
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A package's instance name in its test place: the first non-`$`,
|
|
7
|
+
* non-`Script` key under ServerScriptService.
|
|
8
|
+
*/
|
|
9
|
+
export function findTestProjectSlug(
|
|
10
|
+
project: TestRojoProject
|
|
11
|
+
): string | undefined {
|
|
12
|
+
const serverScriptService = project.tree?.ServerScriptService;
|
|
13
|
+
if (!serverScriptService) return undefined;
|
|
14
|
+
|
|
15
|
+
return Object.keys(serverScriptService).find(
|
|
16
|
+
(key) => !key.startsWith('$') && key !== 'Script'
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -2,7 +2,10 @@ import * as fs from 'fs/promises';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { randomUUID } from 'crypto';
|
|
4
4
|
import { type DeployTarget } from '@quenty/nevermore-deploy';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type BuiltPlace,
|
|
7
|
+
type JobContext,
|
|
8
|
+
} from '../../job-context/job-context.js';
|
|
6
9
|
import { type DeployOverrides } from '../../build/build.js';
|
|
7
10
|
import {
|
|
8
11
|
type ParsedTestCounts,
|
|
@@ -88,6 +91,12 @@ export interface SingleTestOptions {
|
|
|
88
91
|
suppressCountsProvenance?: boolean;
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
export interface PreparedTest {
|
|
95
|
+
builtPlace: BuiltPlace;
|
|
96
|
+
scriptContent: string;
|
|
97
|
+
hasJestConfig: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
/**
|
|
92
101
|
* Build, deploy, and run a test for a single package using the provided
|
|
93
102
|
* JobContext. The context determines the execution environment (cloud or local).
|
|
@@ -98,17 +107,15 @@ export async function runSingleTestAsync(
|
|
|
98
107
|
context: JobContext,
|
|
99
108
|
options: SingleTestOptions
|
|
100
109
|
): Promise<SingleTestResult> {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
overrides,
|
|
111
|
-
} = options;
|
|
110
|
+
const prepared = await prepareTestAsync(context, options);
|
|
111
|
+
return executePreparedTestAsync(context, prepared, options);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function prepareTestAsync(
|
|
115
|
+
context: JobContext,
|
|
116
|
+
options: SingleTestOptions
|
|
117
|
+
): Promise<PreparedTest> {
|
|
118
|
+
const { packagePath, packageName, scriptText, target, overrides } = options;
|
|
112
119
|
|
|
113
120
|
const sessionId = randomUUID();
|
|
114
121
|
const builtPlace = await context.buildPlaceAsync({
|
|
@@ -140,6 +147,29 @@ export async function runSingleTestAsync(
|
|
|
140
147
|
scriptText ??
|
|
141
148
|
(await readTestScriptAsync(packagePath, builtPlace.target.scriptTemplate));
|
|
142
149
|
|
|
150
|
+
return {
|
|
151
|
+
builtPlace,
|
|
152
|
+
scriptContent,
|
|
153
|
+
hasJestConfig: await packageHasJestConfigAsync(packagePath),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function executePreparedTestAsync(
|
|
158
|
+
context: JobContext,
|
|
159
|
+
prepared: PreparedTest,
|
|
160
|
+
options: SingleTestOptions
|
|
161
|
+
): Promise<SingleTestResult> {
|
|
162
|
+
const {
|
|
163
|
+
packagePath,
|
|
164
|
+
packageName,
|
|
165
|
+
// 300s is the Open Cloud maximum. The old 120s default failed real suites
|
|
166
|
+
// with no jest summary at all, so "--timeout 300" had become folklore
|
|
167
|
+
// everyone had to know before their first useful run.
|
|
168
|
+
timeoutMs = 300_000,
|
|
169
|
+
scriptText,
|
|
170
|
+
} = options;
|
|
171
|
+
const { builtPlace, scriptContent, hasJestConfig } = prepared;
|
|
172
|
+
|
|
143
173
|
const deployment = await context.deployBuiltPlaceAsync({
|
|
144
174
|
builtPlace,
|
|
145
175
|
packageName,
|
|
@@ -185,7 +215,7 @@ export async function runSingleTestAsync(
|
|
|
185
215
|
structured !== undefined &&
|
|
186
216
|
!structured.ranJest &&
|
|
187
217
|
scriptText === undefined &&
|
|
188
|
-
|
|
218
|
+
hasJestConfig;
|
|
189
219
|
|
|
190
220
|
const reasons = mergeFailureReasons(result.errorMessage, [
|
|
191
221
|
...(smokeTestedWithSpecs
|