@avi2dg/checks 0.64.0 → 0.65.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 +8 -0
- package/docs/bins.md +1 -1
- package/docs/design.md +3 -2
- package/docs/gates/checks-advisories.md +8 -6
- package/docs/gates/checks-ci-wiring.md +45 -7
- package/docs/gates/checks-flake.md +6 -7
- package/docs/gates/checks-lint.md +1 -1
- package/docs/gates/checks-mutation-scope.md +1 -1
- package/docs/gates/checks-mutation.md +1 -0
- package/docs/gates/checks-night-run.md +7 -7
- package/docs/gates/checks-release-notes.md +5 -0
- package/docs/gates/checks-release-pr.md +2 -1
- package/docs/gates/checks-release-push.md +4 -3
- package/docs/gates/checks-setup.md +2 -0
- package/docs/gates/checks-test.md +28 -1
- package/docs/how-to/set-up-a-repository.md +4 -0
- package/package.json +5 -1
- package/src/delivery/ci-wiring.ts +20 -7
- package/src/delivery/job-timeout.ts +30 -0
- package/src/delivery/release-push.ts +1 -1
- package/src/delivery/release-schedule.ts +53 -18
- package/src/delivery/schedule-policy.ts +64 -0
- package/src/delivery/setup-workflow.ts +43 -0
- package/src/delivery/setup.ts +2 -39
- package/src/testing/affected-tests.ts +261 -0
- package/src/testing/mutation-scope-graph.ts +13 -0
- package/src/testing/mutation-scope.ts +2 -9
- package/src/testing/test-skips.ts +3 -3
- package/src/testing/test.ts +57 -10
package/src/testing/test.ts
CHANGED
|
@@ -4,9 +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 { readPullRequestNarrowing } from "./affected-tests.ts";
|
|
7
8
|
import {
|
|
9
|
+
readDeclarationsIn,
|
|
8
10
|
readSkipDeclarations,
|
|
9
11
|
readSmokeDeclarations,
|
|
12
|
+
scanTestFiles,
|
|
13
|
+
selectedTierFile,
|
|
10
14
|
type Environment,
|
|
11
15
|
type SkipDeclaration,
|
|
12
16
|
type TestTier,
|
|
@@ -171,7 +175,8 @@ const environmentOf = Config.Boolean("CI").pipe(
|
|
|
171
175
|
export type SuiteSelection =
|
|
172
176
|
| { readonly kind: "full" }
|
|
173
177
|
| { readonly kind: "tier"; readonly tier: TestTier }
|
|
174
|
-
| { readonly kind: "smoke" }
|
|
178
|
+
| { readonly kind: "smoke" }
|
|
179
|
+
| { readonly kind: "files"; readonly files: readonly string[] };
|
|
175
180
|
|
|
176
181
|
export function selectSuite(options: TestOptions, environment: Environment): SuiteSelection {
|
|
177
182
|
if (options.tier !== undefined) return { kind: "tier", tier: options.tier };
|
|
@@ -191,6 +196,7 @@ export function smokeList(manifest: unknown): readonly string[] | undefined {
|
|
|
191
196
|
export function suiteArgs(selection: SuiteSelection, smoke: readonly string[]): readonly string[] {
|
|
192
197
|
if (selection.kind === "tier") return tierArgs(selection.tier);
|
|
193
198
|
if (selection.kind === "smoke") return ["./tests/unit", ...smoke.map((file) => `./${file}`)];
|
|
199
|
+
if (selection.kind === "files") return selection.files.map((file) => `./${file}`);
|
|
194
200
|
return [];
|
|
195
201
|
}
|
|
196
202
|
|
|
@@ -218,6 +224,37 @@ const readSmokeList = Effect.fn("readSmokeList")(function* (root: string) {
|
|
|
218
224
|
return smoke;
|
|
219
225
|
});
|
|
220
226
|
|
|
227
|
+
// A live or pixel tier and --full run whole, since no pull request picks them.
|
|
228
|
+
export function narrowedTier(options: TestOptions, selection: SuiteSelection): { readonly tier: TestTier | undefined } | undefined {
|
|
229
|
+
if (options.full === true) return undefined;
|
|
230
|
+
if (selection.kind === "full") return { tier: undefined };
|
|
231
|
+
if (selection.kind === "tier" && (selection.tier === "unit" || selection.tier === "e2e")) return { tier: selection.tier };
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const narrowSelection = Effect.fn("narrowSelection")(function* (options: TestOptions, selection: SuiteSelection, environment: Environment) {
|
|
236
|
+
const narrowed = narrowedTier(options, selection);
|
|
237
|
+
if (environment !== "ci" || narrowed === undefined) return selection;
|
|
238
|
+
const tests = (yield* scanTestFiles(process.cwd())).filter((file) => selectedTierFile(file, narrowed.tier));
|
|
239
|
+
const narrowing = yield* readPullRequestNarrowing(process.cwd(), tests);
|
|
240
|
+
if (Option.isNone(narrowing)) return selection;
|
|
241
|
+
if (narrowing.value.kind === "full") {
|
|
242
|
+
yield* Console.log(`${NAME}: ${narrowing.value.because}, so the whole suite runs`);
|
|
243
|
+
return selection;
|
|
244
|
+
}
|
|
245
|
+
yield* Console.log(`${NAME}: the pull request reaches ${narrowing.value.files.length} of ${tests.length} test file(s)`);
|
|
246
|
+
return { kind: "files", files: narrowing.value.files } satisfies SuiteSelection;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const readScope = Effect.fn("readScope")(function* (selection: SuiteSelection, smoke: readonly string[]) {
|
|
250
|
+
if (selection.kind === "smoke") return yield* readSmokeDeclarations(process.cwd(), smoke);
|
|
251
|
+
const declarations =
|
|
252
|
+
selection.kind === "files"
|
|
253
|
+
? yield* readDeclarationsIn(process.cwd(), selection.files)
|
|
254
|
+
: yield* readSkipDeclarations(process.cwd(), selection.kind === "tier" ? selection.tier : undefined);
|
|
255
|
+
return { files: undefined, declarations };
|
|
256
|
+
});
|
|
257
|
+
|
|
221
258
|
const runSuite = Effect.fn("runSuite")(function* (outfile: string, args: readonly string[], workers: number | undefined) {
|
|
222
259
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
223
260
|
const command = ["test", "--randomize", `--parallel=${workerCount(workers)}`, ...args, ...reporterArgs(outfile)];
|
|
@@ -239,15 +276,13 @@ const testEntry = Effect.gen(function* () {
|
|
|
239
276
|
const fs = yield* FileSystem.FileSystem;
|
|
240
277
|
const path = yield* Path.Path;
|
|
241
278
|
const environment = yield* environmentOf;
|
|
242
|
-
const selection = selectSuite(options, environment);
|
|
279
|
+
const selection = yield* narrowSelection(options, selectSuite(options, environment), environment);
|
|
280
|
+
if (selection.kind === "files" && selection.files.length === 0) {
|
|
281
|
+
yield* Console.log(`${NAME}: the pull request changes no file a test reads, so no test ran`);
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
243
284
|
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
|
-
};
|
|
285
|
+
const scope = yield* readScope(selection, smoke);
|
|
251
286
|
if (scope.files?.length === 0) {
|
|
252
287
|
yield* Console.log(`${NAME}: no unit test and no smoke list, so this local run ran nothing; declare checks.smoke or pass ${FULL}`);
|
|
253
288
|
return true;
|
|
@@ -257,6 +292,18 @@ const testEntry = Effect.gen(function* () {
|
|
|
257
292
|
const outfile = path.join(yield* fs.makeTempDirectoryScoped({ prefix: "checks-test-" }), "junit.xml");
|
|
258
293
|
const suitePassed =
|
|
259
294
|
(yield* runSuite(outfile, suiteArgs(selection, smoke), options.workers)) === ChildProcessSpawner.ExitCode(0);
|
|
295
|
+
return yield* judgeRun(outfile, junit, suitePassed, declarations, environment);
|
|
296
|
+
}).pipe(Effect.scoped);
|
|
297
|
+
|
|
298
|
+
const judgeRun = Effect.fn("judgeRun")(function* (
|
|
299
|
+
outfile: string,
|
|
300
|
+
junit: Option.Option<string>,
|
|
301
|
+
suitePassed: boolean,
|
|
302
|
+
declarations: Parameters<typeof judgeSkips>[1],
|
|
303
|
+
environment: Environment,
|
|
304
|
+
) {
|
|
305
|
+
const fs = yield* FileSystem.FileSystem;
|
|
306
|
+
const path = yield* Path.Path;
|
|
260
307
|
const written = yield* fs.exists(outfile);
|
|
261
308
|
if (written && Option.isSome(junit)) {
|
|
262
309
|
yield* fs.makeDirectory(path.dirname(junit.value), { recursive: true });
|
|
@@ -276,6 +323,6 @@ const testEntry = Effect.gen(function* () {
|
|
|
276
323
|
const verdict = judgeSkips(results, judged, environment);
|
|
277
324
|
yield* passes(verdict) ? Console.log(report(verdict)) : Console.error(report(verdict));
|
|
278
325
|
return suitePassed && crashedFiles.length === 0 && passes(verdict);
|
|
279
|
-
})
|
|
326
|
+
});
|
|
280
327
|
|
|
281
328
|
if (import.meta.main) runMain(NAME, testEntry);
|