Skip to content

Testing

Start with the smallest check that can prove your change. Then verify the committed patch before pushing. A local pass is not proof of a complete machine installation, and a skipped check is not a pass.

Commands below start at the repository root unless they explicitly enter cli/. CI and releases explains what runs after a push.

ChangeStart withBroaden coverage when…
Documentation or skillsLocal docs stageHeadings, paths, or selectors changed: check incoming references too; site changes also need a visual review
TOML, categories, or source layoutconfig_drift and local config stageLoading, merging, or profile behavior changed
Task selection or dependenciesAffected command suite and task_executionShared catalog or scheduler behavior changed
A concrete resourceDomain unit tests and affected lifecycle_contracts / e2e_apply casesShared planning, adapters, or failure accounting changed
Process executioncargo test --locked --profile ci infra::exec:: from cli/Timeout, cancellation, or native process behavior changed: test Windows too
Wrappers or hooksIsolated wrapper/hook fixturesIntegration behavior changed: use a disposable environment
Desktop or application contentDesktop shell / Application configurationRendering, focus, or session behavior needs native testing
CI selection or workflowsLocal ci stage and the changed job’s commandDependencies or triggers changed: include classification fixtures
Arch package recipesRecipe validationA real package build/install is needed: use disposable Arch

For Rust changes, also run formatting and host Clippy. Add Windows-target Clippy for platform-sensitive code, and the full Rust suite for shared configuration, catalog, or engine changes. Use the pinned toolchain.

Kind of checkWhat it can change
Fixture testsTemporary files, repositories, and subprocesses within controlled roots
Repository checksBuild caches and sometimes network access; CLI checks can write logs and selection metadata
Host integrationReal home files, packages, services, registry values, or Git history

Do not run update, remove, package updates, elevation, or desktop restarts to compensate for missing tests. Full install/remove and application integration belong in disposable homes/runners/VMs. The full commit-hook suite also creates commits; a clean developer checkout is not a safe fixture.

The canonical entrypoints are check.sh and Check.ps1. Select stages for your patch rather than always running everything.

Linux:

Terminal window
sh .github/workflows/scripts/linux/check.sh --list
sh .github/workflows/scripts/linux/check.sh --strict docs
sh .github/workflows/scripts/linux/check.sh --strict fmt clippy test config

Windows:

Terminal window
pwsh -File .github\workflows\scripts\windows\Check.ps1 -List
pwsh -File .github\workflows\scripts\windows\Check.ps1 -Strict fmt clippy test config

With no stage names, the scripts run their default stages:

StageWhat it checks
fmtRust formatting
clippyHost Clippy for all targets, with warnings rejected
testFull Rust test suite, not shell/PowerShell integration scripts
rustdocPrivate-item documentation and configured rustdoc lints
configCLI configuration, source paths, and required config files
docsMarkdown links, skill metadata/fixtures, task selectors, and the Starlight build, published links, and browser regressions; Linux entrypoint only
ciWorkflow contracts, path/release classification, aggregate gating, and actionlint; Linux entrypoint only
shellShared ShellCheck discovery and rules
powershellPSScriptAnalyzer at Warning/Error severity
audit / denyDependency advisories / dependency policy

Cargo verification uses --locked and the ci profile where applicable. The scripts use tools from PATH; they do not select the pinned compiler for you. Compiler-dependent checks reject the wrong Rust version.

Missing tools usually produce SKIP. --strict, -Strict, or DOTFILES_CHECK_STRICT=1 makes skips fail. An installed tool’s nonzero exit is a failure, including exit code 2. See Analyzer prerequisites for required tools and the PowerShell entrypoint difference.

Two stages are opt-in: msrv attempts to install and compile with the minimum Rust version from Cargo.toml; Linux qml runs Qt Quick Test. --all on Linux includes both, while Windows -All adds msrv. These options can require downloads or additional tools; they do not run mutation tests or host integration.

The docs stage needs Python, Cargo, the Node version in docs/.node-version, and the site dependencies and Chromium runtime. Its selector check runs a Rust fixture. It also validates repository skill metadata and runs the validator’s synthetic regressions without third-party Python dependencies. The supported frontmatter convention and manual routing cases live in Skill maintenance. Metadata checks do not measure agent instruction effectiveness.

The source link check inspects tracked and unignored new Markdown files, including unstaged moves. It checks local inline links, not external URLs, reference-style links, Mermaid rendering, or the truth of the prose. A quick link-only check is:

Terminal window
python3 -B .github/workflows/scripts/linux/check-doc-links.py .

Generated site output and node_modules are ignored. New documents do not need to be staged to check their links.

Install the locked dependencies and browser runtime once:

Terminal window
cd docs
npm ci
npx playwright install chromium

On a minimal Linux machine, Playwright may report missing system libraries. Its install --with-deps chromium command can install those libraries, but that is a host package change: use an authorized environment or a disposable runner. CI uses that option on its disposable Ubuntu runner.

For site-only changes, run from the repository root:

Terminal window
DIR="$PWD" sh .github/workflows/scripts/linux/test-docs.sh docs_links docs_site

The site check runs Astro diagnostics, Markdown-transform fixtures, the static build, matching page titles/navigation/filenames, published URL/anchor/asset, canonical sitemap, and robots/indexability checks. Browser regressions exercise the homepage, top-level topic navigation, search, theme persistence, warning callouts, and every Mermaid diagram at desktop and mobile sizes. Diagram labels must stay at least 14 CSS pixels. Mobile emulation is not a native phone/browser test.

The browser suite starts and stops its own loopback preview on port 4322; keep that port free. For manual preview and changes to the content pipeline, see Documentation site.

Commit the intended patch, then run the manual Linux pre-push checker (Python 3.11 or newer):

Terminal window
python3 -B .github/workflows/scripts/linux/pre-push.py --plan
python3 -B .github/workflows/scripts/linux/pre-push.py

The default range is origin/main..HEAD. Make sure that remote-tracking ref is current. For another destination or base, pass --base <remote-sha> and optionally --head <commit>. The plan prints the selected checks without running them. An invalid range fails; an empty range reports that no checks ran.

The checker exports the selected commit into a temporary Git checkout and runs CI-selected local checks with strict skip handling. Unstaged fixes cannot satisfy a committed patch. It reuses the Cargo target cache and redirects CLI state and logs into temporary directories.

Have the pinned compiler on PATH and required tools, MSRV, Windows target, and Playwright Chromium runtime installed first; this checker does not install them. It restores locked npm dependencies inside the isolated checkout. Cargo and dependency checks may still need network/cache access. It is a manual command, not an installed Git hook, and does not push.

Resolve selected local failures before publishing. Its REMAINING CI list identifies native Windows, host integration, mutation, or publication work left to CI. Report unavailable checks explicitly. After pushing, inspect the exact commit’s aggregate and informational results.

Rust integration targets under cli/tests/ use fixtures. They are different from CI’s host-mutating integration jobs.

TargetMain responsibility
config_driftReal configuration, source, and category invariants
domain_boundariesArchitectural dependencies and platform/environment boundaries
update_command, remove_command, list_command, check_commandCommand membership, selection, loading, and outcomes
task_executionTask/resource convergence and dry-run
task_outputStatuses, reasons, logs, and exit behavior
lifecycle_contractsPreview, apply/repeat, retry, and conservative removal across real tasks
e2e_applyCLI convergence against controlled state
behavioral_ciCross-cutting regressions for CI assumptions

Combine related targets in one invocation:

Terminal window
cd cli
cargo test --locked --profile ci --test config_drift --test check_command
cargo test --locked --profile ci --test update_command -- --list
cargo test --locked --profile ci --test update_command repository

The last command filters by a test-name substring. Check the test count: a misspelled filter can run zero tests and still exit successfully.

Put a regression at the lowest boundary that proves the behavior. Use unit tests for parsing, state transitions, resource errors, and adapters; command tests for selection, dependency wiring, elevation policy, and exit status; lifecycle/end-to-end fixtures for state across runs.

Shared sequential/parallel scheduler contracts live in scheduler/conformance.rs. Adjacent tests cover synchronization and output ordering; batch_reports.rs and parallel.rs cover partial failure, cancellation, and in-flight accounting.

Use common::cli_command for CLI subprocess fixtures. It isolates home/config/cache/state/logs, clears inherited DOTFILES_* and GIT_* overrides, sets a fixture Git boundary, and disables self/repository updates. Use injected environments/executors and isolated loggers for in-process tests.

common/lifecycle.rs provides real symlink, Git-hook, and Git-setting lifecycle fixtures. Assert exact changes, counts, preserved state, and behavior across fresh contexts, not only a success message. Named case tables help repeated input/rejection cases; keep separate cases where platform contracts differ.

To check this checkout using the current source, without wrapper bootstrap:

Terminal window
cd cli
cargo run --locked --profile ci -- check --root .. --profile desktop \
--only config-diagnostics,symlink-sources,config-files

These commands explicitly select this checkout with --root ...

This is the local config stage. It fails on configuration diagnostics, checks configured symlink/permission sources including retained inactive definitions, and verifies required TOML files. Structural conflicts can fail during loading, before a check task runs.

To include external plugin and script checks:

Terminal window
cd cli
cargo run --locked --profile ci -- check --root .. --profile desktop --verbose

That adds APM pack --dry-run --verbose, ShellCheck, and PSScriptAnalyzer where applicable. Discovery and file-read failures are not treated as empty input.

Add --overlay /path/to/private-dotfiles to validate combined configuration. Keep its diagnostics private until sanitized: a public-only check cannot prove an overlay has no conflicts.

CheckRequired tools
Local APM package validationapm
ShellCheckshellcheck and POSIX sh
PSScriptAnalyzerPowerShell 7 (pwsh) and the PSScriptAnalyzer module

Missing tools produce an unmet skip in CLI checks. --fail-on-skip or a present CI variable makes those skips fail. Windows PowerShell does not substitute for pwsh in the analyzer.

If pwsh is available but PSScriptAnalyzer is absent or broken, CLI checks, the Linux local checker, and the commit hook fail. The Windows local Check.ps1 entrypoint instead skips a missing module; -Strict makes that skip fail too. Install the prerequisites for the checks you intend to run.

Preview the smallest affected selection with the current build:

Terminal window
cd cli
cargo run --locked --profile ci -- update --root .. --profile desktop \
--only symlinks --dry-run --verbose

For removal, use remove instead of update. Substitute agent-packages or another selector as appropriate.

A preview must not apply normal managed-state changes, but it can inspect the host and write logs or selection metadata. Overlay scripts execute their own check/preview code. Only preview trusted configuration.

Prove non-mutation using fixture assertions on files, commands, and state; the words “dry run” in output are not proof. See Private overlays.

Mutation testing temporarily changes code to check whether tests notice a wrong branch, lost error, or incorrect preview. Consider it for behavior changes, especially failure handling, scheduling, and partial success.

Run cargo-mutants in a disposable checkout, never the shared working copy. Inspect each MISSED result: add an assertion for observable behavior or explain why the mutation is equivalent. Ordinary tests and --all do not run mutation testing.

With cargo-mutants installed, select the affected module in that disposable checkout. For example:

Terminal window
cd cli
cargo mutants --file src/app/filter.rs --cargo-arg=--profile=ci --cargo-arg=--locked

Use --in-diff with a unified Git diff for changed-line coverage. Record any narrowed scope and inspect mutants.out before cleaning up. CI handles its changed-code selection, shards, and native-code exclusions; see Mutation coverage instead of copying that setup into each local command. Linux checks do not provide native Windows mutation coverage.

These use fixture tools rather than bootstrapping or installing the real CLI:

Terminal window
sh .github/workflows/scripts/linux/test-hook-inputs.sh \
test_staged_ci_guards test_ci_change_classification test_staged_ci_guard_deletions
sh .github/workflows/scripts/linux/test-shell-wrapper.sh \
test_wrapper_preserves_runtime_context test_wrapper_uses_cargo_artifact

With no selector, test-hook-inputs.sh runs all input regressions. Its build metadata case needs the pinned Rust compiler; without it, that case skips.

On Windows, dot-source the wrapper suite to load only the isolated functions:

Terminal window
. .\.github\workflows\scripts\windows\Test-ShellWrapper.ps1
Test-IsolatedWrapperPath
Test-CargoArtifactPath

Full wrapper suites can build/copy binaries or use BINARY_PATH. Read their setup before running all cases.

These inspect staged content and do not create commits:

Terminal window
sh hooks/check-sensitive.sh
sh hooks/check-rust.sh
sh hooks/check-ci-guards.sh
DOTFILES_HOOKS_FULL=1 sh hooks/pre-commit

The Rust and CI guards export the index and reuse caches without stashing or overwriting working files. Full mode remains staged-change-aware; it is not committed-range validation. See Git hooks.

Never run test-git-hooks.sh in your developer checkout. It creates real commits. A clean tree is not a safe fixture; do not use its force override.

The full suite runs in CI’s disposable checkout. Prefer the isolated local regressions above; use a disposable repository and home if you need full integration coverage. Do not reuse a failed fixture, because its existing commits can make a later check see an empty diff.

Terminal window
python3 -B .github/workflows/scripts/linux/test-maintenance.py

These fixtures check merge/rebase cleanup, protected branches, worktrees, and file/link/directory retention in temporary repositories and directories. They do not start services or delete real user files.

QML tests need Qt 6.8 or newer Quick Test and use inert Quickshell mocks:

Terminal window
sh .github/workflows/scripts/linux/check.sh --strict qml

Python helpers, lock scripts, and stock-cache concurrency use isolated fixtures:

Terminal window
python3 -B -m unittest discover \
-s symlinks/config/quickshell/tests/python -p 'test_*.py'
python3 -B -m unittest discover \
-s symlinks/config/hypr/scripts/tests -p 'test_*.py'
sh .github/workflows/scripts/linux/test-stocks.sh

Offscreen tests cover logic/layout, not native rendering, focus, or a live compositor/network session. Helpers mock desktop actions and market requests. On Windows use python -B; Linux-only cases skip. Record native manual coverage separately.

These use isolated homes and mock commands:

Terminal window
python3 -B .github/workflows/scripts/linux/test-shell-config.py
python3 -B -m unittest discover -s symlinks/vim/tests -p 'test_*.py'
pwsh -NoProfile -File symlinks/config/powershell/tests/Test-Prompt.ps1

The PowerShell fixture covers first-start discovery, custom Cargo paths, literal profile-fragment directories, and PATH deduplication on reload. Install required native tools; a missing-tool skip gives no coverage.

The ci-success gate checks that selected jobs succeeded and unselected jobs were intentionally skipped. See CI and releases for scheduling and informational jobs; do not interpret an omitted job as a pass.

Terminal window
python3 -B .github/workflows/scripts/linux/test-ci-changes.py

These cover classification, aggregate gating, publishing contracts, renames/deletions, unusual paths, and invalid ranges. Temporary Git repositories and mocked gh calls keep fixture commits and tags local; nothing is published. Use ClassificationTests GateTests as arguments for just those cases. Also run the local ci stage when workflow syntax or contracts change.

Publishing compares the exact tested commit to the last published release, not merely the previous push. See Release selection for why a docs-only push can recover a previously failed release.

Linux cross-target Clippy catches Windows type/import errors:

Terminal window
cd cli
cargo clippy --locked --profile ci --target x86_64-pc-windows-gnu \
--all-targets -- -D warnings

It does not exercise Windows registry, UAC, symlink/junction, path, or process behavior. Native Windows tests are still required. The CI coverage matrix lists those boundaries.

Executor tests launch their ignored child-process fixture themselves; do not run it manually with --ignored.