mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-13 04:03:30 +00:00
A developer's Clash and a CI runner's squid both export HTTP_PROXY and its siblings. Now that the harness honors them, an ambient value decides test outcomes: this PR has already recorded a proxy's 502 page as a snapshot's expected output, and let a runner's own export stand in for "what the user exported" in an assertion about inherited names. A Vitest setup file clears the proxy names before any suite runs, wired into every configuration that declares a setup. Real-API e2e is cleared too: before proxy support existed every request connected directly and that suite passed, so direct is the environment it is known to work in. `NODE_USE_ENV_PROXY` cannot be cleared this way — Node samples the proxy environment at process start — and the module says so. A proxy application never exports it, and the eight names one does export are fully handled: with all of them set, the affected suites pass. The wiring is what regresses, so that is what the test pins. Configurations are discovered rather than listed, because the web suites carry no setup today and a hand-written list would let one of them gain a setup without gaining this one. `plugin.spec.ts` kept its own copy of the eight names to guard against the machine; it never sets a proxy variable itself, so the setup replaces that entirely. `install.spec.ts` had one assertion waiting on a DNS miss with no deadline, which timed out once under load.
365 lines
20 KiB
TypeScript
365 lines
20 KiB
TypeScript
import { spawnSync } from 'node:child_process'
|
|
import { fileURLToPath } from 'node:url'
|
|
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
import { resolvePwshPath } from './packages/shell/pwsh-local/src/resolve.ts'
|
|
import { defineConfig } from 'vitest/config'
|
|
import { standardDecoratorPlugin, vitestExecArgv } from './vitest.shared.ts'
|
|
import { COVERAGE_EXEMPT_ENV, coverageExemptHeavySuites } from './scripts/coverage-exempt.ts'
|
|
import { COVERAGE_PARTITION_MODE_ENV } from './scripts/coverage-partitions.ts'
|
|
|
|
// Prints exact `path:line:col` records for every uncovered statement, branch
|
|
// path, and function when a file misses the per-file 100% gate — the built-in
|
|
// threshold ERRORs name only the file. Absolute path because istanbul-reports
|
|
// require()s custom reporters (which is also why the reporter is CJS).
|
|
const uncoveredLocationsReporter = fileURLToPath(new URL('./scripts/coverage-uncovered-locations.cjs', import.meta.url))
|
|
|
|
// Resolution facade shared by every plugin instance below: tsconfig.base.json
|
|
// has no include, which vite-tsconfig-paths treats as match-all, so its paths
|
|
// map applies to every test file. paths must win over package exports so built
|
|
// lib/ never loads a second module-singleton copy.
|
|
const pathsPlugin = (): ReturnType<typeof tsconfigPaths> => tsconfigPaths({ projects: ['./tsconfig.base.json'] })
|
|
|
|
const windowsUnsupportedPackages = process.platform === 'win32'
|
|
? [
|
|
// Bash-requiring suites (a real POSIX shell is unavailable on Windows).
|
|
// The pwsh-requiring suites (pwsh-local, tool-pwsh) deliberately stay
|
|
// INCLUDED: PowerShell ships with Windows, so they run natively here.
|
|
// This explicit list (not a 'packages/shell/*' glob) keeps
|
|
// packages/shell/shell — the Service Definition package — running on Windows.
|
|
'packages/shell/bash-local',
|
|
'packages/shell/bash-sandbox',
|
|
'packages/shell/tool-bash',
|
|
'packages/hooks/*',
|
|
'packages/terminal/terminal-bash',
|
|
'packages/sandbox/sandbox-local',
|
|
]
|
|
: []
|
|
|
|
const windowsUnsupportedTests = process.platform === 'win32'
|
|
? [
|
|
...windowsUnsupportedPackages.map(path => `${path}/tests/**/*.spec.ts`),
|
|
'packages/subprocess/subprocess/tests/**/*.spec.ts',
|
|
'packages/subprocess/subprocess-local/tests/local.spec.ts',
|
|
'packages/subprocess/subprocess-local/tests/process-inspector.spec.ts',
|
|
'packages/subprocess/subprocess-local/tests/spawn.spec.ts',
|
|
'packages/subprocess/subprocess-local/tests/terminal.spec.ts',
|
|
// Oracle-diff suites: they compare the worker's POSIX path/url faces
|
|
// against the host Node's own answers, which are win32 semantics on
|
|
// Windows. The worker always speaks POSIX; the Linux lanes hold the diff.
|
|
'packages/experimental/webworker-runtime/tests/node/path-diff.spec.ts',
|
|
'packages/experimental/webworker-runtime/tests/node/shim-diff.spec.ts',
|
|
// The subprocess ladder over the worker's child_process face: its kill
|
|
// rung reaches the in-worker process table through `process.kill`,
|
|
// which the ladder's win32 branch replaces with taskkill-by-real-pid —
|
|
// undeliverable to a table pid. The worker host always reports 'linux',
|
|
// so the Linux lanes hold the ladder.
|
|
'packages/experimental/webworker-runtime/tests/node/child-process.spec.ts',
|
|
]
|
|
: []
|
|
|
|
// These suites compare against or assemble the Worker's fixed Linux platform.
|
|
// Host-native Windows and macOS behavior is not their oracle.
|
|
const nonLinuxWebWorkerTests = process.platform === 'linux'
|
|
? []
|
|
: [
|
|
'packages/experimental/webworker-runtime/tests/node/fs-watch-stream.spec.ts',
|
|
'packages/experimental/webworker-runtime/tests/node/sandbox-stack.spec.ts',
|
|
]
|
|
|
|
const platformUnsupportedTests = [...windowsUnsupportedTests, ...nonLinuxWebWorkerTests]
|
|
|
|
const windowsUnsupportedCoveragePackages = process.platform === 'win32'
|
|
? [...windowsUnsupportedPackages, 'packages/subprocess/*']
|
|
: []
|
|
|
|
// Windows-only packages: their sources execute exclusively on win32 (koffi
|
|
// loads Win32 libraries), so the Linux coverage lane can never cover them.
|
|
// The Windows dev/CI lane exercises them through the probe/runner suites; the
|
|
// per-file 100% gate must not fail on their Linux-uncovered paths.
|
|
const windowsOnlyCoverageExclusions = process.platform !== 'win32'
|
|
? [
|
|
'packages/sandbox/sandbox-windows-acl/src/**/*.ts',
|
|
// The koffi-backed Win32 table (Toolhelp32/GetProcessTimes/taskkill)
|
|
// executes only on win32; its decision logic is unit-pinned on every
|
|
// host through the injected-internals suites.
|
|
'packages/subprocess/subprocess-local/src/windows-inspector.ts',
|
|
]
|
|
: []
|
|
|
|
// The confinement runner entry executes exclusively as a spawned child
|
|
// process (the sandbox seam's argv-prefix wrapper): its module-level main()
|
|
// would run the confinement in-process if imported, and vitest's v8 coverage
|
|
// never measures child processes. Its behavior is pinned end-to-end by
|
|
// tests/runner.spec.ts, which spawns the real entry through tsx.
|
|
const windowsRunnerCoverageExclusions = process.platform === 'win32'
|
|
? ['packages/sandbox/sandbox-windows-acl/src/runner.ts']
|
|
: []
|
|
|
|
// pwsh-local's run/start/lifecycle suites self-skip without a real pwsh
|
|
// (executor.spec.ts hasPwsh), leaving this file
|
|
// far below per-file 100% on pwsh-less hosts; the exemption keeps those hosts
|
|
// green while CI runners ship pwsh and still enforce the full bar. The probe
|
|
// runs the suites' own resolution (the dependency-free resolve.ts module),
|
|
// so the exemption is active exactly when the suites skip — a mismatched
|
|
// narrower probe could exempt the file on hosts whose suites actually run.
|
|
const pwshCoverageExclusions = spawnSync(resolvePwshPath(), ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', '$true'], { encoding: 'utf8' }).status === 0
|
|
? []
|
|
: [
|
|
'packages/shell/pwsh-local/src/index.ts',
|
|
'packages/shell/pwsh-sandbox/src/**/*.ts',
|
|
]
|
|
|
|
const testIncludes = [
|
|
'packages/*/*/tests/**/*.spec.{ts,tsx}',
|
|
'apps/*/tests/**/*.spec.ts',
|
|
'scripts/**/*.spec.ts',
|
|
]
|
|
|
|
// The instrumented coverage gate sets this env; the exempt heavy suites then
|
|
// run beside it uninstrumented (membership contract in scripts/coverage-exempt.ts).
|
|
// A set-but-not-'1' value is a misconfiguration, not a silent no-op.
|
|
const coverageExemptRaw = process.env[COVERAGE_EXEMPT_ENV]
|
|
if (coverageExemptRaw !== undefined && coverageExemptRaw !== '' && coverageExemptRaw !== '1') {
|
|
throw new Error(`vitest config: ${COVERAGE_EXEMPT_ENV} must be '1' or unset, got ${JSON.stringify(coverageExemptRaw)}.`)
|
|
}
|
|
const coverageExemptExcludes = coverageExemptRaw === '1'
|
|
? coverageExemptHeavySuites.map(suite => suite.exclude)
|
|
: []
|
|
|
|
const coveragePartitionRaw = process.env[COVERAGE_PARTITION_MODE_ENV]
|
|
if (coveragePartitionRaw !== undefined && coveragePartitionRaw !== '' && coveragePartitionRaw !== '1') {
|
|
throw new Error(`vitest config: ${COVERAGE_PARTITION_MODE_ENV} must be '1' or unset, got ${JSON.stringify(coveragePartitionRaw)}.`)
|
|
}
|
|
const coveragePartitionMode = coveragePartitionRaw === '1'
|
|
|
|
// These suites exercise process-global state, process APIs, or timing-sensitive process I/O
|
|
// that worker threads cannot isolate reliably under aggregate gate contention.
|
|
// Keep the narrow exception in forks while the rest of the inventory avoids per-file processes.
|
|
const processBoundTests = [
|
|
'packages/session/session-persistence-jsonl/tests/jsonl.spec.ts',
|
|
'packages/subagent/subagent-acp/tests/subagent-acp.spec.ts',
|
|
'packages/subprocess/subprocess-local/tests/process-exit.spec.ts',
|
|
'packages/subprocess/subprocess-local/tests/spawn.spec.ts',
|
|
'packages/context/time-context/tests/time-context.spec.ts',
|
|
'packages/llm/llm-pi-ai/tests/adapter.spec.ts',
|
|
'packages/boot/app-boot/tests/app-boot.spec.ts',
|
|
'packages/workflow/workflow-worker-thread/tests/session.spec.ts',
|
|
]
|
|
|
|
export default defineConfig({
|
|
plugins: [pathsPlugin(), standardDecoratorPlugin()],
|
|
test: {
|
|
setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts'],
|
|
// .tsx: client component specs (jsdom via per-file @vitest-environment pragma).
|
|
include: testIncludes,
|
|
exclude: platformUnsupportedTests,
|
|
// One coverage invocation aggregates both projects. Every suite forks for
|
|
// Node stability; process-bound suites stay separate for inventory control.
|
|
projects: [
|
|
{
|
|
plugins: [pathsPlugin(), standardDecoratorPlugin()],
|
|
test: {
|
|
name: 'thread-safe',
|
|
execArgv: vitestExecArgv,
|
|
// Node 24 has aborted in its CJS lexer (v8::ToLocalChecked Empty
|
|
// MaybeLocal in cjs_lexer::Parse) from worker threads on macOS,
|
|
// Linux, and Windows. Forked workers avoid that shared thread path.
|
|
pool: 'forks',
|
|
setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts'],
|
|
include: testIncludes,
|
|
exclude: [
|
|
...platformUnsupportedTests,
|
|
...processBoundTests,
|
|
...coverageExemptExcludes,
|
|
],
|
|
},
|
|
},
|
|
{
|
|
plugins: [pathsPlugin(), standardDecoratorPlugin()],
|
|
test: {
|
|
name: 'process-bound',
|
|
execArgv: vitestExecArgv,
|
|
pool: 'forks',
|
|
setupFiles: ['./scripts/test-proxy-environment.ts', './scripts/test-invariants.ts'],
|
|
include: processBoundTests,
|
|
exclude: [
|
|
...platformUnsupportedTests,
|
|
...coverageExemptExcludes,
|
|
],
|
|
},
|
|
},
|
|
],
|
|
coverage: {
|
|
provider: 'v8',
|
|
// Coverage measures OUR runtime source. Types-only files carry no
|
|
// executable code; vendor/ and application/config fixtures are out of scope.
|
|
// .tsx: client components are gated like everything else (jsdom lane).
|
|
include: ['packages/*/*/src/**/*.{ts,tsx}'],
|
|
// Types-only files have no runtime coverage. Importing self-executing bins/workers would boot
|
|
// them inside the unit process, so real subprocess/Worker tests cover their thin entry glue.
|
|
exclude: [
|
|
'packages/*/*/src/types.ts',
|
|
'packages/*/*/src/bin.ts',
|
|
'packages/*/*/src/worker.ts',
|
|
// Dynamic Host/Client composition is covered by its focused lifecycle
|
|
// tests and assembled application checks rather than per-file coverage.
|
|
'packages/self-modification/*/src/**/*.{ts,tsx}',
|
|
// A killed executable lint-contract test can leave a non-product source probe behind.
|
|
'packages/*/*/src/oxlint-contract-*.ts',
|
|
// Client/web UI files whose remaining branches need a browser-grade
|
|
// harness the jsdom lane doesn't cover yet. TODO(gui): cover and
|
|
// remove as the client test lane matures.
|
|
'packages/client/ui-trajectory/src/*',
|
|
// Trajectory's compact Markdown projection retains deferred branch coverage.
|
|
'packages/client/ui-primitives/src/markdown/plain-text.ts',
|
|
'packages/client/ui-user-questions/src/client/QuestionComposer.tsx',
|
|
'packages/client/ui-primitives/src/Menu.tsx',
|
|
'packages/client/ui-primitives/src/RiskConfirmation.tsx',
|
|
'packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx',
|
|
'packages/client/ui-workspace/src/client/WorkspacePicker.tsx',
|
|
'packages/client/ui-workspace/src/client/rows/WorkspaceBrowser.tsx',
|
|
'packages/client/ui-renderer/src/client/*',
|
|
// Session object internals retain the runtime GUI debt exemption; the
|
|
// new Controller entry, transport, Agent scope, and adapters stay gated.
|
|
'packages/api/session-controller/src/client/sessions/*',
|
|
'packages/api/session-controller/src/client/ordered-baseline.ts',
|
|
'packages/api/session-controller/src/client/time-zone.ts',
|
|
// Keep the browser conversation tree under its existing GUI debt
|
|
// exemption while gating the newly stateful Host half and vocabulary.
|
|
'packages/client/ui-conversation/src/client/*',
|
|
'packages/client/ui-conversation/src/invariant.ts',
|
|
// Chat presentation and assembly retain the same GUI debt exemption;
|
|
// package wiring and the new approval-detail adapter remain gated.
|
|
'packages/client/ui-chat/src/client/chat/!(ApprovalCommand).{ts,tsx}',
|
|
'packages/client/ui-chat/src/client/conversation-nodes/*',
|
|
'packages/client/ui-chat/src/client/details/*',
|
|
'packages/client/ui-chat/src/client/model/*',
|
|
'packages/client/ui-chat/src/client/contract/context-provenance.ts',
|
|
'packages/client/ui-chat/src/client/contract/snapshot.ts',
|
|
'packages/client/ui-chat/src/client/historical-images.ts',
|
|
'packages/client/ui-primitives/src/DisclosureRow.tsx',
|
|
'packages/client/ui-tool/src/*',
|
|
'packages/client/ui-slots/src/*',
|
|
'packages/client/ui-layout/src/*',
|
|
'packages/client/web/src/*',
|
|
'packages/host/webserver/src/*',
|
|
// The browser-worker runtime and its image packer: the executing
|
|
// composition is a real dedicated Worker driven by the web browser lane
|
|
// (apps/web/tests/preview-boot.e2e.ts), which unit-process V8 coverage
|
|
// cannot observe. Unit specs cover the algorithmic cores; the assembled
|
|
// evidence is that boot. TODO(webworker): revisit when a browser-grade
|
|
// coverage lane exists.
|
|
'packages/experimental/webworker-runtime/src/**',
|
|
'packages/experimental/webworker-packer/src/*',
|
|
// Inspector execution adapters run in a Node Worker, the Host native
|
|
// inspector session, or a browser realm, outside attributable parent
|
|
// Vitest coverage.
|
|
'packages/experimental/inspector/src/client/**',
|
|
'packages/experimental/inspector/src/host/bridge/**',
|
|
'packages/experimental/inspector/src/host/cdp/**',
|
|
'packages/experimental/inspector/src/worker/bridge/**',
|
|
'packages/experimental/inspector/src/worker/cdp/**',
|
|
'packages/experimental/inspector/src/worker/realms/**',
|
|
'packages/experimental/inspector/src/worker/{entry,server}.ts',
|
|
// Keep already-complete Inspector modules under the per-file gate and
|
|
// enumerate the remaining direct-test debt instead of exempting src/**.
|
|
// TODO(inspector): close these branch gaps and remove the entries.
|
|
'packages/experimental/inspector/src/host/plugin.ts',
|
|
'packages/experimental/inspector/src/shared/bridge/{control-codec,rpc}.ts',
|
|
'packages/experimental/inspector/src/shared/bridge/messages/observation.ts',
|
|
'packages/experimental/inspector/src/shared/bridge/messages/query/codec.ts',
|
|
'packages/experimental/inspector/src/shared/bridge/messages/runtime/{command-codec,console-frames,frames,value-codec}.ts',
|
|
'packages/experimental/inspector/src/shared/bridge/messages/sources/{codec,frames}.ts',
|
|
'packages/experimental/inspector/src/worker/inspection/{cordis-store,query-router,realm-store}.ts',
|
|
'packages/client/modules/src/client/system.ts',
|
|
'packages/client/hmr/src/client/index.ts',
|
|
// Web config-tree boot round: the new host-side web-transport halves
|
|
// whose remaining branches need real-composition/process harnesses.
|
|
// TODO(gui): cover and remove with the client test lane above.
|
|
'packages/client/modules/src/index.ts',
|
|
'packages/client/modules/src/invariant.ts',
|
|
'packages/client/modules/src/client/index.ts',
|
|
'packages/client/modules/src/client/manifest.ts',
|
|
'packages/client/hmr/src/index.ts',
|
|
'packages/client/hmr/src/invariant.ts',
|
|
'packages/client/connection/src/index.ts',
|
|
'packages/client/connection/src/http-bridge.ts',
|
|
// This assembly imports generated Host-for-Client code that exists
|
|
// only in lib; the post-build built-bin smoke executes both entries.
|
|
'packages/api/remotes/src/index.ts',
|
|
'packages/api/remotes/src/client/index.ts',
|
|
// The Team browser entry binds its source-covered mount lifecycle to
|
|
// the generated Team Remote contribution, which likewise exists only in lib.
|
|
'packages/experimental/client-ui-agent-team/src/client/index.ts',
|
|
// Slash/command/input round: per-file gaps deferred with the same
|
|
// client-lane debt. TODO(gui): cover and remove with the lane above.
|
|
'packages/client/connection/src/client/fixture.ts',
|
|
'packages/client/ui-commands/src/index.ts',
|
|
'packages/client/ui-skill/src/index.ts',
|
|
'packages/client/ui-input-trigger/src/index.ts',
|
|
'packages/client/ui-subagent/src/index.ts',
|
|
'packages/client/ui-commands/src/client/popup.ts',
|
|
'packages/client/ui-commands/src/client/directory.ts',
|
|
'packages/client/ui-commands/src/client/service.ts',
|
|
'packages/client/ui-commands/src/client/PopupSelectView.tsx',
|
|
'packages/client/ui-model-selection/src/index.ts',
|
|
'packages/client/ui-permission-presets/src/index.ts',
|
|
'packages/client/ui-model-selection/src/client/ModelSelect.tsx',
|
|
'packages/client/ui-model-selection/src/client/directory.ts',
|
|
'packages/client/ui-model-selection/src/client/index.ts',
|
|
'packages/client/ui-model-selection/src/client/service.ts',
|
|
'packages/client/ui-input-trigger/src/client/controller.ts',
|
|
'packages/client/ui-input-trigger/src/client/service.ts',
|
|
'packages/client/ui-input-trigger/src/core/menu.ts',
|
|
'packages/client/ui-input-trigger/src/core/detect.ts',
|
|
'packages/client/ui-sidebar/src/client/index.ts',
|
|
'packages/client/ui-skill/src/client/index.ts',
|
|
'packages/client/ui-workspace/src/client/index.ts',
|
|
'packages/test-support/client-runtime/src/translate.ts',
|
|
'packages/client/ui-primitives/src/JsonTree.tsx',
|
|
'packages/client/ui-settings-models/src/client/DeepSeekOnboardingDialog.tsx',
|
|
'packages/client/ui-settings-models/src/client/welcome-store.ts',
|
|
'packages/extensions/*/src/**/*.ts',
|
|
'packages/extensions/*/src/**/*.tsx',
|
|
// Typert generator: correctness is pinned by its fixture suites and
|
|
// the byte-for-byte catalog reproduction test; per-file coverage
|
|
// would put whole-workspace compiler analysis under v8
|
|
// instrumentation — the coverage lane's longest tail.
|
|
'packages/typert/generator/src/*.ts',
|
|
// Experimental webworker-runtime is outside the coverage requirement
|
|
// by decision: its correctness signal is its uninstrumented suite and
|
|
// the packer's end-to-end image spec.
|
|
'packages/experimental/webworker-runtime/src/**/*.ts',
|
|
// Projection/command round: executor lifecycle branches and the
|
|
// registry's drive tails need the same maturing lanes. TODO(gui):
|
|
// cover and remove with the client test lane above.
|
|
'packages/interaction/commands/src/index.ts',
|
|
'packages/interaction/commands/src/invariant.ts',
|
|
'packages/session/session-projection/src/index.ts',
|
|
...windowsUnsupportedCoveragePackages.map(path => `${path}/src/**/*.ts`),
|
|
...windowsOnlyCoverageExclusions,
|
|
...windowsRunnerCoverageExclusions,
|
|
...pwshCoverageExclusions,
|
|
],
|
|
// 100% or it doesn't merge (docs/testing.md: excessive tests are welcome).
|
|
// Per-file so a well-covered big file can't subsidize a bare one.
|
|
// Every v8 ignore comment must carry a reason — see the quality-gates Agent Note
|
|
// (.agents/notes/implemented/process/2026-06-11-quality-gates.md).
|
|
thresholds: coveragePartitionMode
|
|
? undefined
|
|
: {
|
|
perFile: true,
|
|
statements: 100,
|
|
branches: 100,
|
|
functions: 100,
|
|
lines: 100,
|
|
},
|
|
reporter: coveragePartitionMode
|
|
? []
|
|
: process.env.CI
|
|
? ['text', uncoveredLocationsReporter]
|
|
: ['text', 'html', uncoveredLocationsReporter],
|
|
},
|
|
},
|
|
})
|