@avi2dg/checks 0.49.0 → 0.50.1
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 +16 -0
- package/docs/gates/checks-ci-wiring.md +2 -1
- package/docs/gates/checks-test-layout.md +1 -0
- package/docs/gates/checks-test.md +24 -6
- package/package.json +1 -1
- package/src/delivery/ci-wiring.ts +2 -1
- package/src/testing/test-layout.ts +1 -1
- package/src/testing/test-skips.ts +44 -9
- package/src/testing/test.ts +70 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of `@avi2dg/checks`, newest first, written by the release from its conventional commits.
|
|
4
4
|
|
|
5
|
+
## 0.50.1
|
|
6
|
+
|
|
7
|
+
Released 2026-10-20.
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
|
|
11
|
+
- **delivery:** admit the self-hosted iphone runner in private-repo ci-wiring [#193](https://github.com/avi2d/checks/pull/193)
|
|
12
|
+
|
|
13
|
+
## 0.50.0
|
|
14
|
+
|
|
15
|
+
Released 2026-10-20.
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- **testing:** cap local checks-test at the unit tier plus a declared smoke list [#191](https://github.com/avi2d/checks/pull/191)
|
|
20
|
+
|
|
5
21
|
## 0.49.0
|
|
6
22
|
|
|
7
23
|
Released 2026-10-20.
|
|
@@ -48,7 +48,8 @@ A here-document that `sh`, `bash`, `dash`, `ksh` or `zsh` reads is a script, and
|
|
|
48
48
|
The text of any other here-document is data, so a call there counts only inside a command substitution when the delimiter is unquoted.
|
|
49
49
|
A mutation job never names `CI_RUNS_ON` directly in `runs-on`, as `vars.CI_RUNS_ON` or `vars['CI_RUNS_ON']`, so an override for an outage cannot send its mutation runs to hosted runners.
|
|
50
50
|
Any other job that names `CI_RUNS_ON` in `runs-on` carries exactly the hosted-default expression `${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}`.
|
|
51
|
-
In a private repository each mutation job carries exactly the labels `[self-hosted, Linux, X64, winbox]
|
|
51
|
+
In a private repository each mutation job carries exactly the labels `[self-hosted, Linux, X64, winbox]`.
|
|
52
|
+
Every other job there carries exactly the hosted-default expression, or exactly the labels `[self-hosted, macOS, iphone]` of the Mac CI runner that holds the iPhone Simulator.
|
|
52
53
|
A public repository may keep `runs-on: ubuntu-latest` on every job, because a pull request from a fork runs its own code on the runner.
|
|
53
54
|
The check knows a repository is private only from GitHub's event, so a run outside CI judges only the rules that hold in either.
|
|
54
55
|
|
|
@@ -27,6 +27,7 @@ It fails unless the repository holds this shape, and names the file and the path
|
|
|
27
27
|
A test at the `e2e`, `live` or `pixel` level may do all of it, under `tests/quarantine/` as well.
|
|
28
28
|
Helpers in `tests/lib/**` answer to the same rule, since an in-process test reaches them.
|
|
29
29
|
- `scripts.test` is `checks-test`, optionally with `--parallel=<workers>`, which runs `bun test --randomize --parallel=<workers>` as [checks-test](checks-test.md#parallel-runs) says.
|
|
30
|
+
It refuses `--full`, so a local run stays capped.
|
|
30
31
|
- `scripts.lint` runs this check, itself or through `checks-lint` called by its bare bin name.
|
|
31
32
|
- `bunfig.toml` carries every `[test]` key of the shipped preset with the same value.
|
|
32
33
|
`[test].pathIgnorePatterns` is the preset's `["**/tests/quarantine/**", "**/tests/live/**", "**/tests/pixel/**", "repos/**"]`, which the check pins itself, so the kit's own repository, whose bunfig is the preset, cannot drift it either.
|
|
@@ -8,7 +8,8 @@ audience: consumers
|
|
|
8
8
|
|
|
9
9
|
## What it checks
|
|
10
10
|
|
|
11
|
-
It runs the
|
|
11
|
+
It runs the unit tier plus the smoke list outside CI and the full suite under CI.
|
|
12
|
+
It runs them with `bun test --randomize --parallel=<workers>`, as [Parallel runs](#parallel-runs) says, and reads Bun's JUnit report from that run.
|
|
12
13
|
It runs Bun with `CI=true`, so `test.only` fails the run in every environment.
|
|
13
14
|
Bun exits zero when tests skip, so `checks-test` checks every skipped test against its source declaration.
|
|
14
15
|
A test that `test.skip`, `test.skipIf`, `test.if`, `test.todo` or an enclosing `describe.skip` skips fails unless the test declares its reason.
|
|
@@ -44,10 +45,25 @@ A skipped test or a todo without `skipReason` fails in every environment.
|
|
|
44
45
|
## What it reads
|
|
45
46
|
|
|
46
47
|
`checks-test` reads test source files and the JUnit report written by its own run.
|
|
47
|
-
It
|
|
48
|
+
It reads no skip list from `package.json`, only the smoke list.
|
|
48
49
|
It counts a run as `ci` when `CI` is true and as `local` otherwise.
|
|
50
|
+
A local run covers `./tests/unit` plus the smoke list the repository declares.
|
|
51
|
+
The smoke list lives in `package.json` under `checks.smoke` as an array of test file paths.
|
|
52
|
+
A repository with no smoke list runs the unit tier alone outside CI.
|
|
53
|
+
A local run with no unit test and no smoke list runs nothing and passes.
|
|
54
|
+
A `ci` run covers the full default suite.
|
|
55
|
+
A smoke entry that names no test file fails the run.
|
|
56
|
+
A smoke entry under `tests/quarantine/`, `tests/live/` or `tests/pixel/` fails the run, since Bun's ignored paths would drop it.
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"checks": {
|
|
61
|
+
"smoke": ["tests/e2e/checkout.test.ts"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The default suite takes only the arguments [Arguments](#arguments) lists.
|
|
51
67
|
Use `checks-test --tier=live` or `checks-test --tier=pixel` for a named test tier.
|
|
52
68
|
Use `checks-test --tier=unit` or `checks-test --tier=e2e` to run only one level of the default suite.
|
|
53
69
|
A live or pixel run clears Bun's ignored paths, runs only `./tests/live` or `./tests/pixel`, and still checks each skip.
|
|
@@ -103,8 +119,10 @@ A timings option waits for Bun to honour timings under random order.
|
|
|
103
119
|
|
|
104
120
|
## Arguments
|
|
105
121
|
|
|
106
|
-
The default command takes no arguments except `--parallel=<workers>`, as [Parallel runs](#parallel-runs) says, and `--junit=<file>`.
|
|
107
|
-
|
|
122
|
+
The default command takes no arguments except `--parallel=<workers>`, as [Parallel runs](#parallel-runs) says, `--full` and `--junit=<file>`.
|
|
123
|
+
`--full` runs the full suite outside CI, so a night run covers what a local run skips.
|
|
124
|
+
A `package.json` script never takes `--full`, as [checks-test-layout](checks-test-layout.md) says of `scripts.test`.
|
|
125
|
+
Its named tier options are `--tier=live`, `--tier=pixel`, `--tier=unit` and `--tier=e2e`, and a tier run takes `--parallel=<workers>` too, but not `--full`.
|
|
108
126
|
`--junit=<file>` keeps the JUnit report Bun wrote at `<file>`, whether the tests passed or failed, so a night run hands it to [checks-night-run](checks-night-run.md).
|
|
109
127
|
A `package.json` script never takes `--junit`, as [checks-test-layout](checks-test-layout.md) says of `scripts.test`.
|
|
110
128
|
Each option appears at most once.
|
|
@@ -116,7 +134,7 @@ It refuses test filters because every test excluded by a filter would look skipp
|
|
|
116
134
|
| --- | --- |
|
|
117
135
|
| 0 | every test passed and every skip has a matching site reason |
|
|
118
136
|
| 1 | a test failed, a parallel worker crashed, a skip lacks a site reason or a declaration is stale in CI |
|
|
119
|
-
| 2 | an argument is not one it takes, a declaration cannot be read, `CI` is not a boolean or Bun wrote no report |
|
|
137
|
+
| 2 | an argument is not one it takes, a declaration or the smoke list cannot be read, a smoke entry names no runnable test, `CI` is not a boolean or Bun wrote no report |
|
|
120
138
|
|
|
121
139
|
## Sample output
|
|
122
140
|
|
package/package.json
CHANGED
|
@@ -84,6 +84,7 @@ const OVERRIDE_ACCESS = String.raw`vars\s*(?:\.\s*CI_RUNS_ON\b|\[\s*'CI_RUNS_ON'
|
|
|
84
84
|
const RUNNER_OVERRIDE = new RegExp(String.raw`\b${OVERRIDE_ACCESS}`, "i");
|
|
85
85
|
const HOSTED_OVERRIDE = new RegExp(String.raw`^\$\{\{\s*${OVERRIDE_ACCESS}\s*\|\|\s*'ubuntu-latest'\s*\}\}$`, "i");
|
|
86
86
|
const MUTATION_RUNNER = ["self-hosted", "Linux", "X64", "winbox"];
|
|
87
|
+
const DEVICE_RUNNER = ["self-hosted", "macOS", "iphone"];
|
|
87
88
|
const HOSTED_DEFAULT = "${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}";
|
|
88
89
|
const EventRepository = Schema.fromJsonString(Schema.Struct({ repository: Schema.Struct({ private: Schema.Boolean }) }));
|
|
89
90
|
|
|
@@ -294,7 +295,7 @@ function runnerFault(runsOn: unknown, mutation: boolean, visibility: Visibility)
|
|
|
294
295
|
if (mutation && overridden) return `a mutation job reads CI_RUNS_ON, so an override moves its mutation runs off winbox; ${pin}`;
|
|
295
296
|
if (mutation) return visibility === "private" && !sameLabels(runsOn, MUTATION_RUNNER) ? `a mutation job in a private repository runs off winbox; ${pin}` : undefined;
|
|
296
297
|
const hosted = typeof runsOn === "string" && HOSTED_OVERRIDE.test(runsOn.trim());
|
|
297
|
-
if (hosted || (visibility !== "private" && !overridden)) return undefined;
|
|
298
|
+
if (hosted || sameLabels(runsOn, DEVICE_RUNNER) || (visibility !== "private" && !overridden)) return undefined;
|
|
298
299
|
return `the job is not on a hosted runner CI_RUNS_ON can override; set runs-on: ${HOSTED_DEFAULT}`;
|
|
299
300
|
}
|
|
300
301
|
|
|
@@ -252,7 +252,7 @@ export function scriptViolations(manifest: unknown, files: readonly string[] = [
|
|
|
252
252
|
const lint = isRecord(scripts) ? scripts["lint"] : undefined;
|
|
253
253
|
const violations: Violation[] = [];
|
|
254
254
|
const options = scriptOptions(test);
|
|
255
|
-
if (options === undefined || options.tier !== undefined) {
|
|
255
|
+
if (options === undefined || options.tier !== undefined || options.full === true) {
|
|
256
256
|
violations.push({
|
|
257
257
|
file,
|
|
258
258
|
line: undefined,
|
|
@@ -144,21 +144,21 @@ function selectedTierFile(file: string, tier: TestTier | undefined): boolean {
|
|
|
144
144
|
return file.startsWith(`tests/${tier}/`);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
tier: TestTier | undefined,
|
|
150
|
-
) {
|
|
151
|
-
const fs = yield* FileSystem.FileSystem;
|
|
152
|
-
const path = yield* Path.Path;
|
|
153
|
-
const files = yield* Effect.tryPromise({
|
|
147
|
+
const scanTestFiles = Effect.fn("scanTestFiles")(function* (root: string) {
|
|
148
|
+
return yield* Effect.tryPromise({
|
|
154
149
|
try: () =>
|
|
155
150
|
Promise.all(
|
|
156
151
|
TEST_FILES.map((pattern) =>
|
|
157
152
|
Array.fromAsync(new Bun.Glob(pattern).scan({ cwd: root, onlyFiles: true })),
|
|
158
153
|
),
|
|
159
|
-
).then((groups) => groups.flat().
|
|
160
|
-
catch: (cause) => new SkipDeclarationError({ message:
|
|
154
|
+
).then((groups) => groups.flat().sort()),
|
|
155
|
+
catch: (cause) => new SkipDeclarationError({ message: "cannot find test files: " + String(cause) }),
|
|
161
156
|
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const readDeclarationsIn = Effect.fn("readDeclarationsIn")(function* (root: string, files: readonly string[]) {
|
|
160
|
+
const fs = yield* FileSystem.FileSystem;
|
|
161
|
+
const path = yield* Path.Path;
|
|
162
162
|
const declarations: SkipDeclaration[] = [];
|
|
163
163
|
for (const file of files) {
|
|
164
164
|
const source = yield* fs.readFileString(path.join(root, file)).pipe(
|
|
@@ -178,3 +178,38 @@ export const readSkipDeclarations = Effect.fn("readSkipDeclarations")(function*
|
|
|
178
178
|
}
|
|
179
179
|
return declarations;
|
|
180
180
|
});
|
|
181
|
+
|
|
182
|
+
export const readSkipDeclarations = Effect.fn("readSkipDeclarations")(function* (
|
|
183
|
+
root: string,
|
|
184
|
+
tier: TestTier | undefined,
|
|
185
|
+
) {
|
|
186
|
+
const files = (yield* scanTestFiles(root)).filter((file) => selectedTierFile(file, tier));
|
|
187
|
+
return yield* readDeclarationsIn(root, files);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
export type SmokeScope =
|
|
191
|
+
| { readonly kind: "valid"; readonly files: readonly string[] }
|
|
192
|
+
| { readonly kind: "invalid"; readonly message: string };
|
|
193
|
+
|
|
194
|
+
export function smokeFiles(scanned: readonly string[], smoke: readonly string[]): SmokeScope {
|
|
195
|
+
const missing = smoke.filter((file) => !scanned.includes(file));
|
|
196
|
+
if (missing.length > 0) return { kind: "invalid", message: "checks.smoke names no test file at " + missing.join(", ") };
|
|
197
|
+
const ignored = smoke.filter((file) => !selectedTierFile(file, undefined));
|
|
198
|
+
if (ignored.length > 0) {
|
|
199
|
+
return {
|
|
200
|
+
kind: "invalid",
|
|
201
|
+
message: "checks.smoke cannot name a quarantined, live or pixel test at " + ignored.join(", "),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
const wanted = new Set(smoke);
|
|
205
|
+
return { kind: "valid", files: scanned.filter((file) => selectedTierFile(file, "unit") || wanted.has(file)) };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const readSmokeDeclarations = Effect.fn("readSmokeDeclarations")(function* (
|
|
209
|
+
root: string,
|
|
210
|
+
smoke: readonly string[],
|
|
211
|
+
) {
|
|
212
|
+
const scoped = smokeFiles(yield* scanTestFiles(root), smoke);
|
|
213
|
+
if (scoped.kind === "invalid") return yield* new SkipDeclarationError({ message: scoped.message });
|
|
214
|
+
return { files: scoped.files, declarations: yield* readDeclarationsIn(root, scoped.files) };
|
|
215
|
+
});
|
package/src/testing/test.ts
CHANGED
|
@@ -4,7 +4,13 @@ import { Config, Console, Effect, FileSystem, Option, Path, Schema } from "effec
|
|
|
4
4
|
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
5
5
|
import { TEST_ENTRY_POINT } from "../core/gates.ts";
|
|
6
6
|
import { runMain, Usage } from "../core/main.ts";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
readSkipDeclarations,
|
|
9
|
+
readSmokeDeclarations,
|
|
10
|
+
type Environment,
|
|
11
|
+
type SkipDeclaration,
|
|
12
|
+
type TestTier,
|
|
13
|
+
} from "./test-skips.ts";
|
|
8
14
|
import { NAME_SEPARATOR, parseReport, ReportError, reporterArgs, type TestResult } from "./test-report.ts";
|
|
9
15
|
|
|
10
16
|
export type { Environment, SkipDeclaration } from "./test-skips.ts";
|
|
@@ -24,10 +30,12 @@ export class SkipGateError extends Schema.TaggedError<SkipGateError>()("SkipGate
|
|
|
24
30
|
export type TestOptions = {
|
|
25
31
|
readonly tier?: TestTier;
|
|
26
32
|
readonly workers?: number;
|
|
33
|
+
readonly full?: boolean;
|
|
27
34
|
};
|
|
28
35
|
|
|
29
36
|
const NAME = TEST_ENTRY_POINT.bin;
|
|
30
|
-
const USAGE = `usage: ${NAME} [--tier=live | --tier=pixel | --tier=unit | --tier=e2e] [--parallel=<workers>] [--junit=<file>], and it refuses test filters`;
|
|
37
|
+
const USAGE = `usage: ${NAME} [--tier=live | --tier=pixel | --tier=unit | --tier=e2e] [--parallel=<workers>] [--full] [--junit=<file>], and it refuses test filters`;
|
|
38
|
+
const FULL = "--full";
|
|
31
39
|
const TIERS = new Map<string, TestTier>([
|
|
32
40
|
["--tier=live", "live"],
|
|
33
41
|
["--tier=pixel", "pixel"],
|
|
@@ -40,6 +48,7 @@ const JUNIT = /^--junit=(.+)$/;
|
|
|
40
48
|
function optionOf(arg: string): TestOptions | undefined {
|
|
41
49
|
const tier = TIERS.get(arg);
|
|
42
50
|
if (tier !== undefined) return { tier };
|
|
51
|
+
if (arg === FULL) return { full: true };
|
|
43
52
|
const workers = PARALLEL.exec(arg)?.[1];
|
|
44
53
|
return workers === undefined ? undefined : { workers: Number(workers) };
|
|
45
54
|
}
|
|
@@ -51,6 +60,7 @@ export function testOptions(args: readonly string[]): TestOptions | undefined {
|
|
|
51
60
|
if (option === undefined || Object.keys(option).some((key) => key in options)) return undefined;
|
|
52
61
|
options = { ...options, ...option };
|
|
53
62
|
}
|
|
63
|
+
if (options.tier !== undefined && options.full === true) return undefined;
|
|
54
64
|
return options;
|
|
55
65
|
}
|
|
56
66
|
|
|
@@ -158,6 +168,32 @@ const environmentOf = Config.Boolean("CI").pipe(
|
|
|
158
168
|
Effect.mapError((cause) => new SkipGateError({ message: `cannot tell a ci run from a local one: ${cause.message}` })),
|
|
159
169
|
);
|
|
160
170
|
|
|
171
|
+
export type SuiteSelection =
|
|
172
|
+
| { readonly kind: "full" }
|
|
173
|
+
| { readonly kind: "tier"; readonly tier: TestTier }
|
|
174
|
+
| { readonly kind: "smoke" };
|
|
175
|
+
|
|
176
|
+
export function selectSuite(options: TestOptions, environment: Environment): SuiteSelection {
|
|
177
|
+
if (options.tier !== undefined) return { kind: "tier", tier: options.tier };
|
|
178
|
+
if (options.full === true || environment === "ci") return { kind: "full" };
|
|
179
|
+
return { kind: "smoke" };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const SmokeManifest = Schema.Struct({
|
|
183
|
+
checks: Schema.optional(Schema.Struct({ smoke: Schema.optional(Schema.Array(Schema.NonEmptyString)) })),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export function smokeList(manifest: unknown): readonly string[] | undefined {
|
|
187
|
+
if (!Schema.is(SmokeManifest)(manifest)) return undefined;
|
|
188
|
+
return manifest.checks?.smoke ?? [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function suiteArgs(selection: SuiteSelection, smoke: readonly string[]): readonly string[] {
|
|
192
|
+
if (selection.kind === "tier") return tierArgs(selection.tier);
|
|
193
|
+
if (selection.kind === "smoke") return ["./tests/unit", ...smoke.map((file) => `./${file}`)];
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
|
|
161
197
|
function tierArgs(tier: TestTier | undefined): readonly string[] {
|
|
162
198
|
if (tier === undefined) return [];
|
|
163
199
|
// The preset ignores tests/live and tests/pixel, so only those tiers clear the ignore list.
|
|
@@ -169,11 +205,24 @@ export function workerCount(workers: number | undefined): number {
|
|
|
169
205
|
return workers ?? availableParallelism();
|
|
170
206
|
}
|
|
171
207
|
|
|
172
|
-
const
|
|
208
|
+
const readSmokeList = Effect.fn("readSmokeList")(function* (root: string) {
|
|
209
|
+
const file = (yield* Path.Path).join(root, "package.json");
|
|
210
|
+
const text = yield* (yield* FileSystem.FileSystem).readFileString(file);
|
|
211
|
+
const manifest = yield* Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown))(text).pipe(
|
|
212
|
+
Effect.mapError((cause) => new SkipGateError({ message: "cannot read " + file + " as JSON: " + cause.message })),
|
|
213
|
+
);
|
|
214
|
+
const smoke = smokeList(manifest);
|
|
215
|
+
if (smoke === undefined) {
|
|
216
|
+
return yield* new SkipGateError({ message: file + ": checks.smoke must be an array of test file paths" });
|
|
217
|
+
}
|
|
218
|
+
return smoke;
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const runSuite = Effect.fn("runSuite")(function* (outfile: string, args: readonly string[], workers: number | undefined) {
|
|
173
222
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
174
|
-
const
|
|
223
|
+
const command = ["test", "--randomize", `--parallel=${workerCount(workers)}`, ...args, ...reporterArgs(outfile)];
|
|
175
224
|
return yield* spawner.exitCode(
|
|
176
|
-
ChildProcess.make(process.execPath,
|
|
225
|
+
ChildProcess.make(process.execPath, command, {
|
|
177
226
|
stdin: "ignore",
|
|
178
227
|
stdout: "inherit",
|
|
179
228
|
stderr: "inherit",
|
|
@@ -189,11 +238,25 @@ const testEntry = Effect.gen(function* () {
|
|
|
189
238
|
const { options, junit } = entry;
|
|
190
239
|
const fs = yield* FileSystem.FileSystem;
|
|
191
240
|
const path = yield* Path.Path;
|
|
192
|
-
const declarations = yield* readSkipDeclarations(process.cwd(), options.tier);
|
|
193
241
|
const environment = yield* environmentOf;
|
|
242
|
+
const selection = selectSuite(options, environment);
|
|
243
|
+
const smoke = selection.kind === "smoke" ? yield* readSmokeList(process.cwd()) : [];
|
|
244
|
+
const scope =
|
|
245
|
+
selection.kind === "smoke"
|
|
246
|
+
? yield* readSmokeDeclarations(process.cwd(), smoke)
|
|
247
|
+
: {
|
|
248
|
+
files: undefined,
|
|
249
|
+
declarations: yield* readSkipDeclarations(process.cwd(), selection.kind === "tier" ? selection.tier : undefined),
|
|
250
|
+
};
|
|
251
|
+
if (scope.files?.length === 0) {
|
|
252
|
+
yield* Console.log(`${NAME}: no unit test and no smoke list, so this local run ran nothing; declare checks.smoke or pass ${FULL}`);
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
const { declarations } = scope;
|
|
194
256
|
|
|
195
257
|
const outfile = path.join(yield* fs.makeTempDirectoryScoped({ prefix: "checks-test-" }), "junit.xml");
|
|
196
|
-
const suitePassed =
|
|
258
|
+
const suitePassed =
|
|
259
|
+
(yield* runSuite(outfile, suiteArgs(selection, smoke), options.workers)) === ChildProcessSpawner.ExitCode(0);
|
|
197
260
|
const written = yield* fs.exists(outfile);
|
|
198
261
|
if (written && Option.isSome(junit)) {
|
|
199
262
|
yield* fs.makeDirectory(path.dirname(junit.value), { recursive: true });
|