@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.
@@ -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
- }).pipe(Effect.scoped);
326
+ });
280
327
 
281
328
  if (import.meta.main) runMain(NAME, testEntry);