Merge pull request #1708 from Runfusion/gsxdsm/slow-tests
fix: keep Fusion test verification bounded
This commit is contained in:
5
.changeset/fast-tests-progress.md
Normal file
5
.changeset/fast-tests-progress.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Keep Fusion verification progress moving by making targeted script tests honor file arguments, reaping verification subprocess groups after clean exits, and preventing the line-count audit from blocking `pnpm test`. The changed-test runner now caps reverse-dependent fan-out so a foundational-package edit no longer expands into a whole-workspace run, and the executor/verification guidance now directs agents to scope verification to changed files rather than running the full workspace test suite.
|
||||
20
.github/workflows/full-suite.yml
vendored
20
.github/workflows/full-suite.yml
vendored
@@ -166,6 +166,26 @@ jobs:
|
||||
- name: Assert every dashboard test file is gated or skip-listed
|
||||
run: node scripts/check-test-inventory.mjs --dashboard-curated
|
||||
|
||||
line-count-audit:
|
||||
name: Line-count audit
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js and pnpm
|
||||
uses: ./.github/actions/setup-node-pnpm
|
||||
with:
|
||||
skip-install: "true"
|
||||
|
||||
# FNXC:TestInfrastructure 2026-06-21-10:26:
|
||||
# Keep line-count drift visible in automated post-merge signal without restoring it to the blocking PR gate.
|
||||
# The guard is intentionally absent from pretest/pr-checks so task verification and merge progress are not blocked by file-size cleanup work.
|
||||
- name: Run line-count audit
|
||||
continue-on-error: true
|
||||
run: pnpm check:line-count
|
||||
|
||||
# The engine-slow tier (src/**/*.slow.test.ts) runs here with a non-empty
|
||||
# execution assertion, so a glob/config drift that silently empties the tier
|
||||
# fails this workflow instead of passing vacuously. Engine slow tests do real
|
||||
|
||||
@@ -14,8 +14,9 @@
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
"scripts": {
|
||||
"pretest": "node scripts/check-no-nohup.mjs && node scripts/check-no-kill-4040.mjs && node scripts/check-no-test-timeout-appeasement.mjs && node scripts/check-file-line-count.mjs",
|
||||
"pretest:full": "node scripts/check-no-nohup.mjs && node scripts/check-no-kill-4040.mjs && node scripts/check-no-test-timeout-appeasement.mjs && node scripts/check-file-line-count.mjs",
|
||||
"pretest": "node scripts/check-no-nohup.mjs && node scripts/check-no-kill-4040.mjs && node scripts/check-no-test-timeout-appeasement.mjs",
|
||||
"pretest:full": "node scripts/check-no-nohup.mjs && node scripts/check-no-kill-4040.mjs && node scripts/check-no-test-timeout-appeasement.mjs",
|
||||
"check:line-count": "node scripts/check-file-line-count.mjs",
|
||||
"test:gate": "node scripts/check-no-nohup.mjs && node scripts/check-no-kill-4040.mjs && node scripts/check-no-test-timeout-appeasement.mjs && pnpm --filter @fusion/engine test:core && pnpm --filter @runfusion/fusion test:ci-shape",
|
||||
"smoke:boot": "node scripts/boot-smoke.mjs",
|
||||
"local": "node scripts/start-local.mjs",
|
||||
@@ -31,7 +32,7 @@
|
||||
"build:exe": "pnpm build && pnpm --filter @runfusion/fusion build:exe",
|
||||
"build:exe:all": "pnpm build && pnpm --filter @runfusion/fusion build:exe:all",
|
||||
"test": "node scripts/test-changed.mjs",
|
||||
"test:scripts": "node --test scripts/__tests__/*.test.mjs",
|
||||
"test:scripts": "node scripts/run-script-tests.mjs",
|
||||
"test:workflow-release-check": "node scripts/workflow-reliability-release-check.mjs",
|
||||
"fn:cache-stats": "node scripts/cache-stats.mjs",
|
||||
"test:full": "node scripts/test-changed.mjs --full --no-cache && pnpm --filter @fusion/engine test:slow",
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
import { AgentStore } from "../agent-store.js";
|
||||
import { aggregateAgentTokenUsage } from "../agent-token-usage.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("aggregateAgentTokenUsage", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let agentStore: AgentStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import type { WorkflowIr } from "../workflow-ir-types.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
function browserDemoLifecycleIr(): WorkflowIr {
|
||||
return {
|
||||
@@ -33,7 +33,10 @@ function browserDemoLifecycleIr(): WorkflowIr {
|
||||
}
|
||||
|
||||
describe("browser demo lifecycle workflow", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: ReturnType<typeof harness.store>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import {
|
||||
BUILTIN_WORKFLOWS,
|
||||
@@ -13,7 +13,7 @@ import { BUILTIN_WORKFLOW_SETTINGS } from "../builtin-workflow-settings.js";
|
||||
import { resolveColumnFlags } from "../trait-registry.js";
|
||||
import { compileWorkflowToSteps } from "../workflow-compiler.js";
|
||||
import { DEFAULT_WORKFLOW_COLUMN_IDS, parseWorkflowIr, serializeWorkflowIr } from "../workflow-ir.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
const EXECUTE_NODE_MAX_RETRIES = 2;
|
||||
|
||||
@@ -414,7 +414,10 @@ describe("built-in workflows", () => {
|
||||
});
|
||||
|
||||
describe("store integration", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: ReturnType<typeof harness.store>;
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { TombstonedTaskResurrectionError } from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("FN-5233 tombstone sticky-window duplicate intake", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { join } from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi, beforeAll, afterAll } from "vitest";
|
||||
import * as extractor from "../goal-citation-extractor.js";
|
||||
import { getAgentLogFilePath, readAgentLogEntries } from "../agent-log-file-store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("goal citations store integration", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
// BOTH flag states (workflowColumns OFF and ON) — see the `flagStates` loop.
|
||||
// Any divergence between the two flag states is a U4 parity FAILURE.
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { allowsAutoMergeProcessing, resolveEffectiveAutoMerge } from "../task-merge.js";
|
||||
import { VALID_TRANSITIONS } from "../types.js";
|
||||
import type { Column, Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
const ALL_COLUMNS: Column[] = ["triage", "todo", "in-progress", "in-review", "done", "archived"];
|
||||
const MOVE_SOURCES = ["user", "engine", "scheduler"] as const;
|
||||
@@ -35,7 +35,10 @@ const flagStates: Array<{ label: string; workflowColumns: boolean }> = [
|
||||
|
||||
for (const flag of flagStates) {
|
||||
describe(`moveTaskInternal characterization — ${flag.label}`, () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: ReturnType<typeof harness.store>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore moveTask preserveStatus", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { aggregatePluginActivations } from "../plugin-activation-analytics.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("aggregatePluginActivations", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("settings precedence", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { TaskDeletedError } from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("soft-delete QA boundary audit (FN-5124)", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { join } from "node:path";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
import { TaskDeletedError } from "../store.js";
|
||||
import type { Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("FN-5208 soft-delete resurrection guards", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { TombstonedTaskResurrectionError } from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("FN-5233 tombstoned createTask behavior", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { describe, it, expect, afterEach, beforeEach } from "vitest";
|
||||
import { describe, it, expect, afterEach, beforeEach, beforeAll, afterAll } from "vitest";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import {
|
||||
StepParserRegistry,
|
||||
StepParserRegistrationError,
|
||||
@@ -266,7 +266,10 @@ describe("step-parsers registry (U12, KTD-12)", () => {
|
||||
});
|
||||
|
||||
describe("parseStepsFromPrompt-through-registry parity (KTD-12)", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { existsSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -8,10 +8,13 @@ import { AgentStore } from "../agent-store.js";
|
||||
import { CentralDatabase } from "../central-db.js";
|
||||
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { setCreateFnAgent } from "../ai-engine-loader.js";
|
||||
import { TaskStore } from "../store.js";
|
||||
import { setTaskCreatedHook } from "../task-creation-hooks.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore createTask title summarization deferred hook", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from "vitest";
|
||||
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -10,14 +10,23 @@ import { DependencyCycleError, TaskStore, TaskHasDependentsError } from "../stor
|
||||
import { setCreateFnAgent } from "../ai-engine-loader.js";
|
||||
import { setTaskCreatedHook } from "../task-creation-hooks.js";
|
||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
// FNXC:TestInfrastructure 2026-06-21-11:30:
|
||||
// Use the shared in-memory harness (build store once, truncate+reset between
|
||||
// tests) instead of per-test recreate. Per-test mkdtemp + new TaskStore +
|
||||
// recursive rm dominated this 51-test file's wall-clock; the shared harness
|
||||
// amortizes setup while preserving isolation via full table truncation +
|
||||
// filesystem reset. Part of the FN-5048 "do not add slow tests" cleanup.
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
rootDir = harness.rootDir();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
import {
|
||||
DependencyCycleError,
|
||||
detectDependencyCycle,
|
||||
} from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("detectDependencyCycle", () => {
|
||||
const lookup = (graph: Record<string, string[]>) => (taskId: string) => graph[taskId];
|
||||
@@ -45,7 +45,10 @@ describe("detectDependencyCycle", () => {
|
||||
});
|
||||
|
||||
describe("TaskStore dependency cycle guard", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore execution timing semantics", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store = harness.store();
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -9,10 +9,13 @@ import { CentralDatabase } from "../central-db.js";
|
||||
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
||||
import { allowsAutoMergeProcessing, resolveEffectiveAutoMerge } from "../task-merge.js";
|
||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore parent-task duplicate intake", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store = harness.store();
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -8,10 +8,13 @@ import { AgentStore } from "../agent-store.js";
|
||||
import { CentralDatabase } from "../central-db.js";
|
||||
import { InvalidFileScopeError, isValidFileScopeEntry, parseStepHeadings, TaskStore, TaskHasDependentsError } from "../store.js";
|
||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore plugin activation persistence", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { CentralDatabase } from "../central-db.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { describe, expect, it, beforeEach, afterEach } from "vitest";
|
||||
import { describe, expect, it, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import type { PrInfo } from "../types.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore prInfos", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: ReturnType<typeof harness.store>;
|
||||
|
||||
const pr = (number: number, patch: Partial<PrInfo> = {}): PrInfo => ({
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { TaskStore } from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore.applyPrMergedTransition", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import type { TaskStore } from "../store.js";
|
||||
|
||||
describe("TaskStore reliability aggregations", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -8,10 +8,13 @@ import { AgentStore } from "../agent-store.js";
|
||||
import { CentralDatabase } from "../central-db.js";
|
||||
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { __setTaskActivityLogLimitsForTesting, TaskStore } from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore RunMutationContext", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -8,10 +8,13 @@ import { AgentStore } from "../agent-store.js";
|
||||
import { CentralDatabase } from "../central-db.js";
|
||||
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
import {
|
||||
detectSelfDefeatingDependency,
|
||||
SELF_DEFEATING_OPERATION_VERBS,
|
||||
SelfDefeatingDependencyError,
|
||||
} from "../store.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("self-defeating dependency detection", () => {
|
||||
it.each(SELF_DEFEATING_OPERATION_VERBS)("matches verb %s", (verb) => {
|
||||
@@ -50,7 +50,10 @@ describe("self-defeating dependency detection", () => {
|
||||
});
|
||||
|
||||
describe("TaskStore create-time self-defeating dep guard", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
await harness.beforeEach();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { appendFile, readFile, writeFile, mkdir, rm, readdir, unlink } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -8,10 +8,13 @@ import { AgentStore } from "../agent-store.js";
|
||||
import { CentralDatabase } from "../central-db.js";
|
||||
import { TaskStore, TaskHasDependentsError } from "../store.js";
|
||||
import { buildResearchDocumentKey, type Task } from "../types.js";
|
||||
import { createTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness, makeTmpDir } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi, beforeAll, afterAll } from "vitest";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("TaskStore", () => {
|
||||
// FN-5048: watcher polling tests run faster with per-test harness than shared FTS-rebuild resets.
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi, beforeAll, afterAll } from "vitest";
|
||||
|
||||
const { summarizeTitleMock } = vi.hoisted(() => ({
|
||||
summarizeTitleMock: vi.fn(),
|
||||
@@ -13,10 +13,13 @@ vi.mock("../ai-summarize.js", async (importOriginal) => {
|
||||
});
|
||||
|
||||
import { setTaskCreatedHook } from "../task-creation-hooks.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("task creation hook", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
|
||||
beforeEach(async () => {
|
||||
setTaskCreatedHook(undefined);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, beforeAll, afterAll } from "vitest";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import type { TaskStore } from "../store.js";
|
||||
|
||||
describe("TaskStore dependency mutations", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import {
|
||||
WORKFLOW_PARITY_OBSERVED_MUTATION,
|
||||
WORKFLOW_PARITY_DRIFT_MUTATION,
|
||||
} from "../workflow-parity.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
describe("getWorkflowParitySummary (CU-U5)", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: ReturnType<typeof harness.store>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
// - concurrent move-vs-delete under the task lock ends moved-then-re-homed or
|
||||
// re-homed, never lost/undefined.
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import type { WorkflowIr } from "../workflow-ir-types.js";
|
||||
import {
|
||||
OccupiedColumnsError,
|
||||
@@ -51,7 +51,10 @@ function customIr(name: string, cols: string[], entryId: string): WorkflowIr {
|
||||
}
|
||||
|
||||
describe("workflow reconciliation (U5)", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: ReturnType<typeof harness.store>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import {
|
||||
validateSettingValuePatch,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "../workflow-settings.js";
|
||||
import type { WorkflowSettingDefinition, WorkflowIrV2 } from "../workflow-ir-types.js";
|
||||
import { BUILTIN_WORKFLOW_SETTINGS } from "../builtin-workflow-settings.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
const BUILTIN_CODING = "builtin:coding";
|
||||
const PROJECT = "proj-1";
|
||||
@@ -174,7 +174,10 @@ describe("findOrphanedSettingValues", () => {
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("TaskStore.updateWorkflowSettingValues", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
beforeEach(harness.beforeEach);
|
||||
afterEach(harness.afterEach);
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { isBuiltinWorkflowId } from "../builtin-workflows.js";
|
||||
import { createTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
import { createSharedTaskStoreTestHarness } from "./store-test-helpers.js";
|
||||
|
||||
/**
|
||||
* U2 / R5 / KTD-3 — lazy idempotent migration of legacy user-authored workflow
|
||||
* steps into the dual fragment + combined-workflow representation.
|
||||
*/
|
||||
describe("TaskStore.migrateLegacyWorkflowSteps (U2/R5)", () => {
|
||||
const harness = createTaskStoreTestHarness();
|
||||
const harness = createSharedTaskStoreTestHarness();
|
||||
|
||||
beforeAll(harness.beforeAll);
|
||||
afterAll(harness.afterAll);
|
||||
let store: ReturnType<typeof harness.store>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -295,7 +295,7 @@ For bug-class/bug-fix tasks, add and fill in the exact \`## Symptom Verification
|
||||
|
||||
### Step {N-1}: Testing & Verification
|
||||
|
||||
> ZERO failures allowed for checks required by this task's quality gates. Run impacted/package-scoped verification first; run workspace-wide suites only when the task or workflow explicitly requires them, or during final integration after impacted checks pass.
|
||||
> ZERO failures allowed for checks required by this task's quality gates. Run impacted/package-scoped verification first. Do NOT run the full workspace test suite (\`pnpm test:full\`, \`pnpm verify:workspace\`, or whole-package \`pnpm --filter <pkg> test\`) as routine or final-integration verification — a full run is allowed ONLY when the task or workflow explicitly requires it.
|
||||
> If keeping lint/tests/build/typecheck green requires edits outside the initial File Scope, make those fixes as part of this task.
|
||||
|
||||
- [ ] Run lint check (\`pnpm lint\`)
|
||||
@@ -363,7 +363,7 @@ If this task REMOVES existing functionality (deleting modules, settings, API end
|
||||
- For bug fixes and UI-affordance add/remove tasks, populate \`## Surface Enumeration\` with this checklist from \`docs/testing.md\`: providers/bridges/execution paths; desktop + mobile breakpoints/platforms; empty/undefined/duplicate/populated data states; shared hooks/components/modules/helpers; every component that renders the affordance; leftover shells after removal.
|
||||
- For bug fixes and UI-affordance add/remove tasks, regression tests must assert the invariant across all known surfaces — enumerate every provider/bridge, desktop + mobile breakpoints, empty/undefined/populated data states, and for UI-affordance changes every component rendering the affordance plus leftover shells after removal — not just the reported repro (see FN-5787/FN-5789/FN-5803, FN-5751, and FN-6115/FN-6118/FN-6123)
|
||||
- For bug-class/bug-fix tasks, the spec MUST include a \`## Symptom Verification\` section with **Original symptom**, **Exact reproduction**, and **Assertion it is gone**. The final verification step must perform symptom-based acceptance: reproduce the original failure and prove it is gone with a real automated test. Green build/tests alone are insufficient. Feature/docs/non-bug tasks are not required to carry \`## Symptom Verification\`.
|
||||
- Include targeted tests in implementation steps and full quality-gate runs in final verification
|
||||
- Include targeted tests in implementation steps and bounded/changed-scoped quality-gate runs in final verification; never run the full workspace test suite unless the task explicitly requires it
|
||||
|
||||
## Duplicate check
|
||||
Before writing a spec, call \`fn_task_list\` to find existing active tasks, then call \`fn_task_search\` with 2-4 distinct keyword phrases from the task title and description (for example file paths, error symptoms, and symbol names).
|
||||
@@ -525,7 +525,7 @@ For bug-class/bug-fix tasks, add and fill in the exact \`## Symptom Verification
|
||||
|
||||
### Step {N-1}: Testing & Verification
|
||||
|
||||
> ZERO failures allowed for checks required by this task's quality gates. Run impacted/package-scoped verification first; run workspace-wide suites only when the task or workflow explicitly requires them, or during final integration after impacted checks pass.
|
||||
> ZERO failures allowed for checks required by this task's quality gates. Run impacted/package-scoped verification first. Do NOT run the full workspace test suite (\`pnpm test:full\`, \`pnpm verify:workspace\`, or whole-package \`pnpm --filter <pkg> test\`) as routine or final-integration verification — a full run is allowed ONLY when the task or workflow explicitly requires it.
|
||||
> If keeping lint/tests/build/typecheck green requires edits outside the initial File Scope, make those fixes as part of this task.
|
||||
|
||||
- [ ] Run lint check (\`pnpm lint\`)
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
detectMarathonVerification,
|
||||
normalizeVerificationCommand,
|
||||
runVerificationCommand,
|
||||
__testOnlyReapVerificationProcessGroup,
|
||||
type RunVerificationOptions,
|
||||
} from "../run-verification-tool.js";
|
||||
|
||||
@@ -368,6 +369,56 @@ describe("runVerificationCommand", { timeout: 30000 }, () => {
|
||||
expect(result.timedOut).toBe(true);
|
||||
expect(result.durationMs).toBeLessThan(5_000);
|
||||
});
|
||||
|
||||
itPosix("reaps background children after a command exits cleanly", async () => {
|
||||
/*
|
||||
* FNXC:Verification 2026-06-21-10:00:
|
||||
* A clean shell exit is not enough evidence that verification is fully done; background children must be gone too or later task completion can stall behind leaked test workers.
|
||||
*/
|
||||
const childScript = "setInterval(() => {}, 1000)";
|
||||
const parentScript = [
|
||||
"const { spawn } = require('node:child_process');",
|
||||
`const child = spawn(process.execPath, ['-e', ${JSON.stringify(childScript)}], { stdio: 'ignore' });`,
|
||||
"console.log(child.pid);",
|
||||
"child.unref();",
|
||||
].join(" ");
|
||||
const result = await runVerificationCommand({
|
||||
command: `${process.execPath} -e ${JSON.stringify(parentScript)}`,
|
||||
cwd: tempDir,
|
||||
timeoutMs: 30_000,
|
||||
onHeartbeat: vi.fn(),
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
const leakedPid = Number.parseInt(result.stdout.trim(), 10);
|
||||
expect(Number.isFinite(leakedPid)).toBe(true);
|
||||
expect(result.timedOut).toBe(false);
|
||||
});
|
||||
|
||||
it("escalates non-timeout process-group reaping with fake timers", () => {
|
||||
/*
|
||||
* FNXC:Verification 2026-06-21-10:26:
|
||||
* Keep timer assertions on a narrow seam with fake timers so the integration test above never polls wall-clock time while still pinning SIGTERM -> SIGKILL escalation.
|
||||
*/
|
||||
vi.useFakeTimers();
|
||||
const kill = vi.fn();
|
||||
const supervised = { kill } as unknown as Parameters<typeof __testOnlyReapVerificationProcessGroup>[0];
|
||||
|
||||
try {
|
||||
__testOnlyReapVerificationProcessGroup(supervised);
|
||||
expect(kill).toHaveBeenCalledTimes(1);
|
||||
expect(kill).toHaveBeenCalledWith("SIGTERM");
|
||||
|
||||
vi.advanceTimersByTime(499);
|
||||
expect(kill).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.advanceTimersByTime(1);
|
||||
expect(kill).toHaveBeenCalledTimes(2);
|
||||
expect(kill).toHaveBeenLastCalledWith("SIGKILL");
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("output capture", () => {
|
||||
|
||||
@@ -32,12 +32,18 @@ import { executorLog } from "./logger.js";
|
||||
const MAX_OUTPUT_BYTES = 200 * 1024; // 200 KB
|
||||
const QUIET_HEARTBEAT_INTERVAL_MS = 60_000; // emit synthetic heartbeat after 60s silence
|
||||
const SIGKILL_GRACE_MS = 10_000;
|
||||
const NORMAL_EXIT_REAP_GRACE_MS = 500;
|
||||
export const DEFAULT_TIMEOUT_PACKAGE_SEC = 300;
|
||||
export const DEFAULT_TIMEOUT_WORKSPACE_SEC = 900;
|
||||
export const MAX_TIMEOUT_SEC = 1800;
|
||||
|
||||
/*
|
||||
FNXC:Verification 2026-06-21-12:05:
|
||||
Verification must stay bounded — never run the full workspace test suite as the verification path.
|
||||
A foundational-package edit reverse-expands a full run across the whole workspace and stalls the task (see FN-5048 + the test-changed reverse-dependent blast cap); scope verification to the changed files/package instead.
|
||||
*/
|
||||
export const BOUNDED_VERIFICATION_GUIDANCE =
|
||||
"Prefer a bounded targeted command such as `pnpm --filter <pkg> exec vitest run src/path/to/test.ts --silent=passed-only --reporter=dot` before rerunning broader suites.";
|
||||
"Scope verification to the changed files: prefer a bounded targeted command such as `pnpm --filter <pkg> exec vitest run src/path/to/test.ts --silent=passed-only --reporter=dot`. Do NOT run the full workspace test suite (`pnpm test:full`, `pnpm verify:workspace`, or whole-package `pnpm --filter <pkg> test`) as verification.";
|
||||
export const MARATHON_SOFT_CAP_SEC = 120;
|
||||
|
||||
const packageDirCache = new Map<string, string | null>();
|
||||
@@ -309,6 +315,27 @@ function killVerificationProcess(supervised: SupervisedChild, signal: NodeJS.Sig
|
||||
supervised.kill(signal);
|
||||
}
|
||||
|
||||
function reapVerificationProcessGroup(supervised: SupervisedChild): void {
|
||||
/*
|
||||
* FNXC:Verification 2026-06-21-10:00:
|
||||
* Verification commands may spawn background test/dev children and then let the shell exit cleanly.
|
||||
* Reap the process group after normal close so fn_run_verification does not report completion while orphaned test workers keep later task progress stuck.
|
||||
*
|
||||
* FNXC:Verification 2026-06-21-10:26:
|
||||
* Apply this reap to every non-timeout close, including externally signal-terminated exits.
|
||||
* The supervisor kill path tolerates already-gone process groups, and the extra reap keeps all non-timeout exits from leaking background verification workers.
|
||||
*/
|
||||
killVerificationProcess(supervised, "SIGTERM");
|
||||
const forceKillTimer = setTimeout(() => {
|
||||
killVerificationProcess(supervised, "SIGKILL");
|
||||
}, NORMAL_EXIT_REAP_GRACE_MS);
|
||||
forceKillTimer.unref?.();
|
||||
}
|
||||
|
||||
export function __testOnlyReapVerificationProcessGroup(supervised: SupervisedChild): void {
|
||||
reapVerificationProcessGroup(supervised);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tool parameter schema
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -562,6 +589,9 @@ export async function runVerificationCommand(
|
||||
`[fn_run_verification] command failed (exit=${exitCode}, signal=${signal ?? "none"}): ${command}`,
|
||||
);
|
||||
}
|
||||
if (!timedOut) {
|
||||
reapVerificationProcessGroup(supervised);
|
||||
}
|
||||
|
||||
resolve({
|
||||
success,
|
||||
|
||||
@@ -366,6 +366,56 @@ test("decideExecutionPlan: expands changed packages with reverse dependents", ()
|
||||
assert.deepEqual(plan.packages, ["@fusion/core", "@fusion/engine", "@fusion/dashboard"]);
|
||||
});
|
||||
|
||||
// FNXC:TestInfrastructure 2026-06-21-10:42: a foundational-package edit must NOT
|
||||
// reverse-expand into a whole-workspace vitest sweep. Cap to the directly changed
|
||||
// package and delegate reverse-dependent coverage to the merge-gate suite.
|
||||
test("decideExecutionPlan: foundational-package edit reverse-blast is capped to direct packages", () => {
|
||||
// 10-package workspace where @fusion/core is depended on by 8 others (>=60%).
|
||||
const dependents = ["p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8"];
|
||||
const reverseDependencyMap = new Map([
|
||||
["@fusion/core", dependents],
|
||||
...dependents.map((d) => [d, []]),
|
||||
["@fusion/standalone", []],
|
||||
]);
|
||||
const plan = decideExecutionPlan({
|
||||
forceFullSuite: false,
|
||||
comparisonBase: "abc123",
|
||||
changedFiles: ["packages/core/src/store.ts"],
|
||||
packageNameByDir: basePackageMap,
|
||||
reverseDependencyMap,
|
||||
});
|
||||
|
||||
assert.equal(plan.mode, "changed");
|
||||
assert.equal(plan.reason, "reverse-dependent-blast-capped");
|
||||
assert.deepEqual(plan.packages, ["@fusion/core"]);
|
||||
});
|
||||
|
||||
// A leaf-ish change with only a couple of dependents in a large workspace must
|
||||
// still expand normally — the cap is for foundational blast, not any expansion.
|
||||
test("decideExecutionPlan: narrow reverse-dependent expansion is NOT capped", () => {
|
||||
const reverseDependencyMap = new Map([
|
||||
["@fusion/engine", ["@fusion/dashboard"]],
|
||||
["@fusion/dashboard", []],
|
||||
["@fusion/core", []],
|
||||
["p1", []],
|
||||
["p2", []],
|
||||
["p3", []],
|
||||
["p4", []],
|
||||
["p5", []],
|
||||
]);
|
||||
const plan = decideExecutionPlan({
|
||||
forceFullSuite: false,
|
||||
comparisonBase: "abc123",
|
||||
changedFiles: ["packages/engine/src/index.ts"],
|
||||
packageNameByDir: basePackageMap,
|
||||
reverseDependencyMap,
|
||||
});
|
||||
|
||||
assert.equal(plan.mode, "changed");
|
||||
assert.equal(plan.reason, undefined);
|
||||
assert.deepEqual(plan.packages, ["@fusion/engine", "@fusion/dashboard"]);
|
||||
});
|
||||
|
||||
test("decideExecutionPlan: no affected package resolved → gate", () => {
|
||||
const plan = decideExecutionPlan({
|
||||
forceFullSuite: false,
|
||||
|
||||
@@ -27,6 +27,12 @@ governs hand-written source.
|
||||
//
|
||||
// Run `node scripts/check-file-line-count.mjs --update` to rewrite the baseline
|
||||
// after an intentional, reviewed change to the set of oversized files.
|
||||
//
|
||||
// FNXC:TestInfrastructure 2026-06-21-10:00:
|
||||
// Line-count drift remains visible through the explicit check:line-count audit,
|
||||
// but it must not block `pnpm test` from reaching the real test runner. The test
|
||||
// preflight owns fast safety checks; broad god-file cleanup is tracked separately
|
||||
// so unrelated task completion is not stuck before tests start.
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
41
scripts/run-script-tests.mjs
Normal file
41
scripts/run-script-tests.mjs
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { globSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
/*
|
||||
FNXC:TestInfrastructure 2026-06-21-10:00:
|
||||
Script-test verification must honor forwarded file arguments so targeted checks stay fast inside Fusion tasks.
|
||||
The old package script always expanded scripts/__tests__/*.test.mjs before forwarded args, turning `pnpm test:scripts -- scripts/__tests__/x.test.mjs` into the full script suite and making task completion look stalled.
|
||||
*/
|
||||
|
||||
const forwarded = process.argv.slice(2).filter((arg) => arg !== "--");
|
||||
const repoRoot = fileURLToPath(new URL("..", import.meta.url));
|
||||
const testFiles = forwarded.length > 0
|
||||
? forwarded.map((file) => resolve(repoRoot, file))
|
||||
: globSync("scripts/__tests__/*.test.mjs", { cwd: repoRoot }).sort().map((file) => resolve(repoRoot, file));
|
||||
|
||||
if (testFiles.length === 0) {
|
||||
console.error("[run-script-tests] no script test files matched");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const child = spawn(process.execPath, ["--test", ...testFiles], {
|
||||
stdio: "inherit",
|
||||
cwd: repoRoot,
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
process.exit(code ?? 1);
|
||||
});
|
||||
|
||||
child.on("error", (error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1209,6 +1209,17 @@ const fullSuiteEnv = {
|
||||
FUSION_TEST_CONCURRENCY: process.env.FUSION_TEST_CONCURRENCY || String(concurrency),
|
||||
};
|
||||
|
||||
/*
|
||||
FNXC:TestInfrastructure 2026-06-21-10:42:
|
||||
Reverse-dependent blast cap thresholds. A foundational-package edit (e.g.
|
||||
@fusion/core) reverse-expands to ~the whole workspace; capping past 60% of a
|
||||
workspace of at least 8 packages keeps a one-line core edit from triggering a
|
||||
25-package vitest sweep, while leaving leaf-package expansion (a few dependents)
|
||||
and small synthetic test fixtures untouched.
|
||||
*/
|
||||
export const WIDE_REVERSE_DEPENDENT_FRACTION = 0.6;
|
||||
export const MIN_WORKSPACE_FOR_BLAST_CAP = 8;
|
||||
|
||||
export function decideExecutionPlan({
|
||||
forceFullSuite,
|
||||
comparisonBase,
|
||||
@@ -1228,12 +1239,34 @@ export function decideExecutionPlan({
|
||||
const affectedPackages = resolveAffectedPackages(changedFiles, packageNameByDir);
|
||||
if (!affectedPackages || affectedPackages.length === 0) return { mode: "gate", reason: "no-affected-package" };
|
||||
|
||||
return {
|
||||
mode: "changed",
|
||||
packages: reverseDependencyMap
|
||||
? expandWithReverseDependents(affectedPackages, reverseDependencyMap)
|
||||
: affectedPackages,
|
||||
};
|
||||
if (!reverseDependencyMap) return { mode: "changed", packages: affectedPackages };
|
||||
|
||||
const expanded = expandWithReverseDependents(affectedPackages, reverseDependencyMap);
|
||||
|
||||
/*
|
||||
FNXC:TestInfrastructure 2026-06-21-10:42:
|
||||
Cap the reverse-dependent fan-out for foundational-package edits. A single
|
||||
`@fusion/core` source change reverse-expands to ~the entire workspace (every
|
||||
package imports core), so `pnpm test` bundled all 25 packages into one
|
||||
`vitest --changed` invocation that ran for the full 20-min `changed`-class
|
||||
watchdog ceiling and pinned the task (the engine runs project testCommand
|
||||
"pnpm test" as its verification gate; on timeout the task fully restarts and
|
||||
re-runs the sweep, stacking into hours). When expansion balloons past most of
|
||||
a real (non-fixture) workspace, test only the DIRECTLY changed packages scoped
|
||||
and delegate cross-cutting reverse-dependent coverage to the merge-gate suite,
|
||||
which already runs first in changed mode and is the project's thin/trusted net.
|
||||
Guarded by MIN_WORKSPACE_FOR_BLAST_CAP so tiny synthetic maps still expand fully.
|
||||
*/
|
||||
const totalPackages = reverseDependencyMap.size;
|
||||
const expandedBeyondDirect = expanded.length > affectedPackages.length;
|
||||
const isWideBlast =
|
||||
totalPackages >= MIN_WORKSPACE_FOR_BLAST_CAP &&
|
||||
expanded.length >= Math.ceil(totalPackages * WIDE_REVERSE_DEPENDENT_FRACTION);
|
||||
if (expandedBeyondDirect && isWideBlast) {
|
||||
return { mode: "changed", packages: affectedPackages, reason: "reverse-dependent-blast-capped" };
|
||||
}
|
||||
|
||||
return { mode: "changed", packages: expanded };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1467,6 +1500,13 @@ export async function main(argv = process.argv.slice(2)) {
|
||||
label: "test:gate (pre-affected)",
|
||||
});
|
||||
|
||||
if (plan.reason === "reverse-dependent-blast-capped") {
|
||||
// FNXC:TestInfrastructure 2026-06-21-10:42: surface the cap so coverage is never silently dropped.
|
||||
console.log(
|
||||
"[test-changed] reverse-dependent fan-out capped: a foundational-package edit reverse-expanded to most of the workspace. " +
|
||||
"Testing only the directly changed packages scoped; cross-cutting reverse-dependent coverage is delegated to the merge-gate suite (ran above).",
|
||||
);
|
||||
}
|
||||
console.log(`[test-changed] running tests for changed packages: ${activePackages.join(", ")}`);
|
||||
if (cachedPackages.length > 0) {
|
||||
console.log(`[test-changed] skipping cached packages: ${cachedPackages.join(", ")}`);
|
||||
|
||||
Reference in New Issue
Block a user