dubzzz/fast-check
github.com/dubzzz/fast-check ↗ · 1032 files · 4.4 MB
Overview
fast-check is a TypeScript property-based testing library for JavaScript and TypeScript developers: users describe input generators and predicates, and the library generates cases, shrinks failures, and reports reproducible seed/path coordinates. At commit ecf7193810d1057253808105444e147d948051a1, the core exposes asyncProperty and promise-returning check/assert APIs, alongside model-based testing, controlled promise scheduling, and execution plugins. The pnpm monorepo also contains Jest, Vitest, and AVA integrations, Node worker execution, packaging and global-poisoning utilities, and a Docusaurus documentation website.
System map
The system centers on an importable testing library rather than an application server. Generation, execution, shrinking, and reporting are separate core modules; adapters and worker orchestration form additional runtime boundaries. Builds produce publishable library artifacts and a static documentation site. The inspected runner retains execution state in memory and exposes replay coordinates through its reports.
- The workspace includes examples, packages/*, and website; root scripts orchestrate package builds, Vitest tests and benchmarks, parallel typechecking, oxfmt, oxlint, and publication lint.
- The core package exports lib/fast-check.js, publishes lib, declares sideEffects:false, and depends on pure-rand; fast-check.ts explicitly exports generators, runner APIs, model runners, scheduler APIs, and plugins.
- asyncProperty constructs PropertyImplem around tuple arbitraries; check coordinates parameter qualification, plugin instances, input iteration, predicate execution, and RunExecution reporting.
- RunExecution owns counters, failure values, error objects, paths, and optional execution trees in memory; toRunDetails returns those values and the qualified run configuration.
- Jest and Vitest export enriched test/it functions, while AVA exports testProp; @fast-check/worker adds a Node worker_threads boundary around predicate execution.
- buildConfigFor derives src entrypoints from package exports, emits ESM plus declarations, externalizes bare imports, and emits CJS only for conditional exports; core build metadata uses GITHUB_SHA or a fixed git rev-parse HEAD shell command.
- The website is Docusaurus with React pages, autogenerated documentation navigation, Algolia search/Ask AI, offline PWA activation, and TypeDoc-generated API documentation from the core entrypoint.
- Build Status gates package publication on required checks and tags at HEAD, publishes npm artifacts with attestations, and contains a disabled Netlify publication job; separate workflows create draft releases, announce on Bluesky, and force-mirror main/tags to Tangled.
Feature catalog
Users primarily consume a programmatic API for generative testing and reproducible failure diagnosis. Generator composition, model commands, and promise scheduling support more complex test scenarios. Plugins and framework adapters customize execution and integration. Companion packages and the documentation website provide additional developer-facing capabilities.
- asyncProperty accepts arbitraries and a synchronous or asynchronous predicate; check returns RunDetails, while assert reports failure through a rejected promise by default.
- The public generator catalog includes numbers, strings, dates, functions, collections, recursive structures, JSON, entity graphs, network-shaped strings, identifiers, and typed arrays.
- sample materializes generated values; statistics classifies values into one or more categories, sorts category counts, and emits percentage lines through the configured logger.
- modelRun and asyncModelRun execute commands conditionally against model/real pairs; scheduledModelRun combines commands with a scheduler that also supports promises, functions, sequences, waiting, and task reports.
- Execution options include seed, randomType, numRuns, examples, replay path, skip tolerance, verbosity, and endOnFailure; public plugins add lifecycle hooks, unbiased generation, equal-value handling, interruption, and predicate timeouts.
- Vitest .prop supports tuple or record arbitraries and forwards test modifiers; ordinary wrapped Vitest tests receive context.g. Jest exposes enriched test/it, and AVA testProp exposes only, failing, skip, serial, before, and after.
- Worker properties select file/property/predicate isolation and main-thread/worker generation; poisoning utilities detect or restore global changes, and packaged utilities list published files or remove nonpublished content.
- The documentation surface offers Quick Start, tutorials, API reference, blog, search, and support navigation; the homepage leads to a Vitest tutorial scaffold containing sort.mjs and sort.test.mjs.
Data flow
Input data enters through arbitrary factories, predicates, and execution parameters. Generated values carry shrink context, while runner-owned state records execution outcomes and replay coordinates. Plugins transform generation and predicate execution before results reach reporting. Worker execution introduces message serialization or RNG-state reconstruction as an additional boundary.
- asyncProperty splits its arguments into arbitraries and predicate, wraps each arbitrary in AlwaysShrinkableArbitrary, and converts the resulting tuple into positional predicate arguments.
- check overlays local parameters on readConfigureGlobal; qualification defaults to 100 runs, 100 skips per run, empty examples/path, and xorshift128plus, normalizing supplied seeds to a 32-bit representation.
- Each check creates a symbol-keyed plugin Map; global plugin builders precede local builders, and reverse-order decoration makes earlier plugins wrap later generation/run behavior.
- toss yields supplied examples before random cases, jumping the RNG before each generated case; PropertyImplem converts run IDs to bias frequency and protects undefined shrink context with a placeholder.
- Value stores value_ and context and optionally exposes clone-aware value access; RunnerIterator passes value_ to the predicate and retains the associated Value for subsequent shrinking.
- Predicate true/undefined becomes success, thrown PreconditionFailure becomes skip/interruption, and other errors become PropertyFailure; failed outcomes replace the active iterator with shrinks, while skips replenish the initial-run budget.
- RunExecution converts accumulated state into RunDetails containing seed, counterexamplePath, errorInstance, and configuration; assert then calls a custom reporter or formats the counterexample, shrink count, and reproduction parameters.
- Worker mode sends either generated inputs or rngState/runId payloads with predicate and run identifiers; workers reconstruct inputs when needed and return success, failure, or skipped messages, which BasicPool correlates to the current registration.
Behavior traces
Source reads followed ordinary assertion, failure replay, worker-backed assertion, and framework registration through their output boundaries. Additional traces cover sampling, model commands, scheduler release, and website onboarding. These are execution-order reconstructions from source, not observed runtime results. The replay flow reconstructs a failure from seed/path rather than loading persisted execution objects.
- Assertion flow: asyncProperty → PropertyImplem(tuple(...)) → assert → check → read(parameters) → plugin construction/decoration → toss → SourceValuesIterator → runIt → RunnerIterator; runIt awaits each predicate and feeds its result back before advancing.
- Failure flow: PropertyImplem.run converts an error/false result → RunnerIterator.handleResult calls RunExecution.fail → the iterator switches to property.shrink → each further failure extends the path; endOnFailure substitutes an empty shrink iterator before execution.
- Replay flow: check with a nonempty path → lazyToss → pathWalk parses colon-separated indices → drops initial producers → generates the selected value → repeatedly shrinks and drops candidates → ordinary execution; RunExecution.mergePaths incorporates the starting offset.
- Worker flow: propertyFor → workerProperty → runMainThread selects a pool/builds the property → worker assert injects the worker plugin → beforeEach acquires a lock and worker → register/postMessage → runWorker executes the predicate → response resolves the run → assert finally invokes associated pool cleanup; file-scoped pools may defer actual worker termination.
- Vitest .prop flow: buildTest exposes .prop → buildTestProp selects tuple or record conversion → buildTestWithPropRunner selects a seed and creates asyncProperty → registered callback collects suite hooks and adds lifecycle plugins → awaited fc.assert executes the property.
- Sampling flow: sample → streamSample merges parameters → toProperty wraps the input → toss or replay pathWalk → take(numRuns) → value_ extraction → array materialization; statistics consumes that stream and emits sorted category percentages.
- Model/scheduler flow: asyncModelRun → internalAsyncModelRun → setup → genericModelRun chains commands → await command.check → conditionally await command.run; scheduledModelRun wraps commands, schedules startModel, then waits for the model and all remaining scheduler tasks.
- Website onboarding flow: Home renders HomepageHeader and feature content → Quick Start links to basic-setup → the guide scaffolds a tutorial with degit → identifies sort.mjs/sort.test.mjs and Vitest → instructs npm test; homepage example demonstrates awaited assert(asyncProperty(...)).
Testing signals
Tests and CI were inspected, not executed; no passing result or coverage percentage is asserted. Vitest discovers tests across packages, examples, and website, with both colocated specifications and package-level integration suites. The inspected tests address execution, replay, generator invariants, scheduler behavior, worker timeouts, and adapter subprocess integration. Coverage configuration and documentation filters impose explicit limits, and this inventory is bounded rather than exhaustive.
- Root Vitest configuration discovers test/spec files across workspace projects, excludes test-bundle, .test-artifacts, and tutorial templates, sets a 120-second timeout, and scopes coverage to core source.
- Colocated Runner.spec.ts checks default run counts, generation/execution ordering, endOnFailure, replay-before-shrink-suppression, rejected assertions, reproduction messages, and original-error causes.
- ReplayFailures integration tests reconstruct counterexamples with sample(seed,path), rebuild shrink-path prefixes, replay the minimal failure, test endOnFailure behavior, and reject impossible replay paths.
- ArbitraryAssertions uses fast-check itself to compare generated and shrunk values from identical seeds; an afterEach plugin checks global poisoning and attempts restoration before rethrowing a detected change.
- Scheduler specifications use Vitest mocks and controlled unresolved promises to check empty-queue errors, task selection, act wrapping, and waiting; worker E2E tests assert a timeout for a predicate that blocks its worker event loop.
- Vitest adapter integration tests generate isolated specification/config files, invoke a child Node/Vitest process, and inspect textual pass/fail/skip markers; child environments remove listed agent variables to preserve reporter output.
- Documentation tests scan core-block arbitrary Markdown, evaluate selected snippets, compare regenerated content, and optionally write updates; SKIP_DOCUMENTATION bypasses them, and Unicode examples explicitly depend on the runtime's Unicode version.
- CI defines sharded Linux tests on Node 22/24/latest and macOS/Windows tests on Node 24, enables coverage on macOS, and separately checks packaged bundles and TypeScript latest/5.8 fixtures; configured core-only coverage does not quantify adapter or website coverage.
Risk map
These risks are source observations or explicitly labeled hypotheses, not confirmed vulnerabilities. The main runtime sensitivities are nonterminating generation, user-controlled execution, cleanup ordering, and compatibility assumptions. Filesystem deletion and release automation require separate operational caution. Partial inspection does not establish whole-repository absence of authentication, network activity, tests, or other safeguards.
- FilterArbitrary.generate retries synchronously without a retry bound; timeout decorates predicate execution, cannot stop a running predicate, and has no effect on synchronous execution. An always-rejecting generator can therefore stall before predicate timeout handling.
- Worker execution loads a supplied file URL through new Worker without sandbox restrictions in that constructor; main-thread inputs must cross messaging serialization, worker-generated inputs disable shrinking, and package documentation requires its own assert to avoid lingering workers.
- Hypothesis: terminateIfStillRunning searches for w.worker !== worker before splicing, which appears capable of removing another pooled worker or the last entry when no unequal worker exists. Reproduction is needed before calling this a confirmed cleanup defect. (inferred)
- Completion hooks are attached with runDetailsPromise.then rather than a rejection handler/finally; all hooks are attempted after a resolved RunDetails, but that implementation does not provide the same cleanup path when the run promise rejects before producing details.
- removeNonPublishedFiles accepts a caller-supplied packageRoot, normalizes it, and recursively removes excluded children; dryRun defaults to false and normalized relative child paths are compared against the caller-supplied keep strings, making an incorrect root operationally consequential.
- Global parameters are held and returned by reference, while installed global plugins append to a module-level array returned directly to the runner; resetConfigureGlobal clears parameters but does not clear that separate plugin registry.
- Hypothesis: Sampler.toProperty still identifies properties by an own runBeforeEach field, while PropertyImplem exposes generate/shrink/run without that field. Direct property sampling merits a regression check; worker property classes also retain legacy hook forwarding. (inferred)
- Documentation/configuration drift is visible: custom-reports says verbosity defaults to 1 but readVerbose defaults to None, CONTRIBUTING names Prettier while package.json runs oxfmt, and the core advertises Node >=12.17 while source uses iterator helpers and CI legacy bundles target Node 22.12.
Extension points
Most extension mechanisms are typed APIs and composable factories. Arbitraries define generation and shrinking, plugins wrap execution, and custom RNG factories control randomness. Commands and scheduler hooks adapt testing to stateful or asynchronous systems. Documentation and build configuration provide additional repository-level extension surfaces.
- Custom Arbitrary<T> implementations provide generate, canShrinkWithoutContext, and shrink, returning Value objects with context; new public generators are explicitly exported through fast-check.ts.
- Arbitrary.filter, map, and chain are composition factories; map accepts an optional unmapper for user-provided values, while chain retains original/chained shrink context and cloned randomness.
- Plugin builders receive their execution index and a shared symbol-keyed PluginStore; instances can implement decorateGenerate, decorateRun, onAllRunsComplete, and afterAll.
- installGlobalPlugin registers outer wrappers for every runner, while per-call plugins remain local; reporting plugins can throw in onAllRunsComplete to override default assertion reporting, and the older reporter option remains supported.
- randomType accepts a seeded generator factory; qualification validates exposed min/max bounds and adapts generators when needed, allowing alternatives to the default xorshift128plus.
- Model runners accept user setup functions and iterable commands with check/run behavior; SchedulerImplem accepts a cloneable TaskSelector and act function, with additional customAct wrappers per scheduled task or wait.
- Generated objects can expose cloneMethod for repeatable access and toStringMethod/asyncToStringMethod for custom diagnostic serialization; Value and stringify explicitly dispatch through those contracts.
- Repository extensions can reuse buildConfigFor with package export mappings; website documentation is discovered through an autogenerated sidebar, and Docusaurus plugins add PWA, TypeDoc, and LLM-readable documentation outputs.
Contributor handoff
Tests and CI were inspected, not executed, and this brief is a bounded source inventory rather than an exhaustive audit. Future agents should verify the pinned API and execution contracts before changing generators, plugins, or adapters. Prefer small changes and the existing package/project scripts when implementation and execution are authorized. Publication, destructive packaging, documentation rewriting, and external submission need distinct handling.
- Read root/package manifests, pnpm-workspace.yaml, tsconfig.common.json, vitest.config.mjs, and fast-check.ts first; package.json specifies pnpm 12.5.1, and workspace configuration restricts dependency build scripts and enforces release-age/trust policies.
- Use this commit's asyncProperty and awaited check/assert contracts; inspect Runner.ts before relying on historical synchronous-property APIs or assuming check cannot throw during parameter qualification/replay.
- For arbitrary changes, preserve generation/context/shrink contracts and read the actual colocated ArbitraryAssertions helper; CONTRIBUTING's example still references a historical test/unit location and NextArbitraryAssertions name.
- When execution is authorized, start with the root test script in one-shot mode and a relevant --project/file selection, then use applicable typecheck, format:check, and lint:check scripts; tests and CI in this brief were only inspected.
- For changes affecting exports or runtime packaging, consult the bundle/type CI jobs: they unpack build artifacts, simulate published contents, run publint and bundle fixtures, and check API extraction plus TypeScript latest/5.8 consumers.
- DEFAULT_SEED configures core tests and EXPECT_DEFAULT_SEED can require it; SKIP_DOCUMENTATION bypasses expensive snippet checks, while UPDATE_CODE_SNIPPETS enables Markdown writes. Keep those modes explicit and remember the documentation test evaluates source snippets.
- Worker changes require reading main.ts, MainThreadRunner, BasicPool, and generation-mode implementations; use worker assert for cleanup, preserve message runId correlation, and account for disabled shrinking in worker-generated mode.
- Treat packaged deletion and release workflows as mutation boundaries; AI_POLICY.md rejects automated external submissions and unreproduced reports/fixes, while CONTRIBUTING requests changesets and scoped gitmoji PR titles for human-owned contributions.
Languages
| Language | Files | Share of source |
|---|---|---|
| TypeScript | 632 | 87% |
| Markdown | 204 | docs/config |
| JavaScript | 88 | 12% |
| JSON | 30 | docs/config |
| YAML | 23 | docs/config |
| CSS | 9 | docs/config |
| Shell | 3 | 0% |
Start reading here
Probable source entrypoint: packages/ — inferred from the directory tree and manifest, not the upstream docs.
Source roots
packages/ (664 files)
Repository structure
| Top-level path | Files |
|---|---|
packages/ | 664 |
website/ | 218 |
examples/ | 51 |
.changeset/ | 50 |
.github/ | 29 |
.devcontainer/ | 3 |
skills/ | 1 |
scripts/ | 1 |
Docs found: CONTRIBUTING.md
Package
package.json — @fast-check/monorepo, 17 declared dependencies.
@atproto/api@changesets/cli@fast-check/packaged@types/jest@types/node@vitest/coverage-v8all-contributors-clicross-envoxfmtoxlintoxlint-tsgolintpkg-pr-new
CI & config entrypoints
.github/workflows/build-status.yml— GitHub Actions workflow.github/workflows/clean-caches.yml— GitHub Actions workflow.github/workflows/codeql-analysis.yml— GitHub Actions workflow.github/workflows/safe-release.yml— GitHub Actions workflow.github/workflows/mirror-tangled.yml— GitHub Actions workflow.github/workflows/add-contributor.yml— GitHub Actions workflow.github/workflows/validate-pr-title.yml— GitHub Actions workflow.github/workflows/zizmor.yml— GitHub Actions workflow.github/workflows/pr-pnpm-conflicts.yml— GitHub Actions workflow.github/workflows/generate-changelog.yml— GitHub Actions workflow
Structural health signals
Average score 8.5/10 across 96 analyzed source files; 1 hotspot surfaced.
website/blog/2024-12-01-advent-of-pbt-day-1/BlueskyComments.js— 416 lines, score 5.9 (Deep nesting, Long function/block)packages/fast-check/src/arbitrary/_internals/helpers/TokenizeRegex.ts— 470 lines, score 6.7 (Large file, Deep nesting)website/docusaurus.config.ts— 294 lines, score 6.7 (Deep nesting, Long function/block)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
ecf7193810d12026-10-01 — 💥 Drop `beforeEach`/`afterEach` methods (#7386)4dd0a3d8a7962026-10-01 — 🔧 Drop unneeded pnpm overrides (#7387)a0bec914e1b42026-10-01 — ⬆️ Update dependency typescript to v7 (#7123)70c97d42a0812026-09-30 — 💥 Drop `timeout` from `Parameters` (#7377)948f127dc3122026-09-30 — 👷 Drop intermediary check job (#7384)