Skip to content

Releases: TestFlowLabs/doctest

v1.10.0

08 Mar 22:28

Choose a tag to compare

What's New

Tested Percentage in Summary Output

The summary now displays a tested percentage showing the ratio of executed blocks to total blocks, giving immediate insight into documentation test coverage.

Before:

Blocks: 72  Passed: 31  Skipped: 41  Parallel: 8  Duration: 2.13s

After:

Blocks: 72  Passed: 31  Skipped: 41
Tested: 43.1% (31/72)  Parallel: 8  Duration: 2.13s

The Tested percentage is calculated as (passed + failed) / total × 100 — showing what proportion of code blocks are actually being executed by DocTest.

v1.9.0 — Update Mode & Shiki Bug Fix

22 Feb 19:57

Choose a tag to compare

New Features

--update / -u Flag

Automatically updates outdated assertion values in markdown files — inspired by Jest's --updateSnapshot. Instead of manually finding and fixing stale values, run doctest --update and let it rewrite them for you.

Updatable assertion types:

  • <!-- doctest: value --> (output assertions without wildcards)
  • <!-- doctest-json: {...} --> (JSON assertions)
  • // => value (result comment assertions)

Non-updatable (require human judgment): <!-- doctest-contains -->, <!-- doctest-matches -->, <!-- doctest-expect -->

# Update all stale assertions
vendor/bin/doctest docs/ --update

# Update a specific block
vendor/bin/doctest docs/api.md:42 --update

Mutual exclusions: --update cannot be combined with --dry-run or --stop-on-failure.

<!-- doctest-output --> Directive

Marks a display-only output block that gets refreshed on --update but is never asserted against during normal runs. Display output ≠ assertion — they serve different purposes.

\`\`\`php
echo "Hello\nWorld";
\`\`\`
<!-- doctest-output -->
\`\`\`
Hello
World
\`\`\`

Options: <!-- doctest-output: lines=5 --> (first N lines), <!-- doctest-output: tail=3 --> (last N lines)

Update Mode Reporter

  • (pencil) symbol for updated assertions
  • Summary: "Updated: N assertions in M files"
  • Exit code 0 on successful update, 1 if non-updatable failures remain

Bug Fixes

  • Shiki <?php // [!code hide] double <?php crashCodeGenerator was re-parsing rawCode (unfiltered) instead of using executableCode (ShikiFilter-processed), producing <?php\n<?php\n... syntax errors. Fixed by storing resultComments/debugMarkers on CodeBlock and using the pre-processed executableCode pipeline.
  • Malformed HTML comment protectionMarkdownRewriter now handles malformed multi-line comments without crashing
  • Missing result_comment updatesExecutor now collects all assertion details before returning, enabling // => updates
  • --update --stop-on-failure validation — Mutual exclusion enforced at CLI level

Refactoring

  • Batch display block rewrite with pre-computed progress (race condition prevention)
  • Removed dead allAssertions variable in CodeBlockExtractor
  • Removed dead processableIndexMap variable in Executor

Documentation

  • Added --update option to CLI reference and options page
  • Added update feature documentation with workflow guide
  • Added doctest-output to HTML comment types table
  • Added update mode exit codes section
  • Added --parallel to quick reference table
  • Added missing exit code 3 to CI/CD docs
  • Aligned wildcard examples and mutual exclusion cross-references

v1.8.0 — Block Index Targeting

11 Feb 22:43

Choose a tag to compare

New Feature

Block Index Targeting (file:N)

Run a specific PHP code block within a markdown file using file:N syntax:

vendor/bin/doctest README.md:3      # Run only the 3rd PHP block
vendor/bin/doctest docs/api.md:1 -v # Run 1st block with verbose output
  • 1-based indexing:1 targets the first PHP block, :2 the second, etc.
  • Fast iteration — Ideal for debugging or developing a single block without running the entire file
  • Exit code 3 — Returned when the specified block index is out of range (no testable blocks found)
  • Works with all other options (-v, -vv, --stop-on-failure, etc.)

Implementation:

  • DocTestConfig — New blockIndices property stores file-to-index mapping
  • DocTestCommand — Parses file:N syntax from CLI arguments before config creation
  • DocTest — Filters extracted blocks by index after extraction, before execution

Documentation

  • Cross-language comparison page — New docs page comparing DocTest with doctest tools in Python, Rust, Elixir, and Go
  • Block index documentationfile:N syntax added to CLI options reference
  • Quick reference examplefile:N usage added to CLI index page

Bug Fix

  • VitePress rendering — Escaped mustache syntax ({{ }}) in comparison docs to prevent template processing errors

Full Changelog: v1.7.0...v1.8.0

v1.7.0 — Pre-Syntax Check & Code Review Cleanup

08 Feb 21:41

Choose a tag to compare

New Feature

  • Pre-execution syntax check — DocTest now runs php -l on generated temp files before executing them. Parse errors (like missing semicolons) are reported with clear Syntax error: ... messages instead of the cryptic Process failed with exit code 255.

Bug Fixes

  • Duplicate JSON decoding — Consolidated duplicate json_decode calls in Executor::evaluateNormalResult
  • Error handler safetypreg_match error handler now uses try/finally to ensure proper restoration
  • Warning-free test output — Replaced @preg_match with set_error_handler for clean Pest output
  • Parallel worker validation — Negative/zero --parallel values are now clamped to minimum 1
  • File read errorsDocTest::extractBlocks now throws RuntimeException on I/O failure instead of silently returning empty

Refactoring

  • Parser deduplicationHtmlCommentAttributeParser now delegates to AttributeParser::resolveAttributes(), eliminating 48 lines of duplicated regex constants and parsing logic
  • Dead code removal — Removed unused WorkItem::groupBlocks parameter and isGroup() method
  • AssertionParser reuse — Moved from per-call instantiation to class property in Executor

Documentation Fixes

  • Fixed incorrect temp file location in security docs
  • Added missing setup and teardown to attribute list in README
  • Corrected attribute count from 7 to 8 in integrations page

Test Improvements

  • Added empty group execution test
  • Added *RECURSION* content verification to circular reference test

v1.6.2 - Bug Fix

08 Feb 20:58

Choose a tag to compare

Bug Fix

Circular Reference Handling in var_export

  • var_export() throws ErrorException when encountering circular references (common with Eloquent models in Laravel where set_error_handler converts warnings to exceptions)
  • Added __doctest_safe_export() helper to generated code that wraps var_export() in try-catch, falling back to print_r() which handles circular references gracefully (shows *RECURSION*)
  • Affects both // => dd() debug markers and // => value result comments

Full Changelog

v1.6.1...v1.6.2

v1.6.1 - Bug Fixes

08 Feb 20:40

Choose a tag to compare

Bug Fixes

__DIR__ Resolution in Code Blocks

  • __DIR__ magic constant in extracted code blocks now resolves to the source markdown file's directory, not the temporary execution directory
  • This fixes issues where include __DIR__ . '/../path/to/file.php' would fail because the temp file lives in /tmp/doctest/

Silent Runtime Error Detection

  • DocTest now detects when a framework (e.g., Laravel) catches a runtime error, renders it to stdout, and exits with code 0
  • Previously these blocks would silently pass with no assertions; now they correctly fail with the error message shown

Full Changelog

v1.6.0...v1.6.1

v1.6.0 — Parallel Execution

08 Feb 16:57

Choose a tag to compare

Parallel Execution

Run code blocks concurrently using multiple worker processes. Significantly speeds up test suites with many independent blocks.

Quick Start

# Auto-detect CPU cores
doctest --parallel

# Specify worker count
doctest --parallel 4

Or configure in doctest.php:

return [
    'execution' => [
        'parallel' => 4,
    ],
];

Features

  • Fixed-size worker pool — N concurrent proc_open processes with non-blocking I/O
  • CPU auto-detection--parallel without a value detects logical cores (Linux, macOS, Windows)
  • Deterministic output — results always reported in source order regardless of worker completion
  • Stop-on-failure — works with parallel: stops dispatching new blocks, waits for running workers
  • Per-block bootstrap — bootstrap profiles resolved per-block and included in each worker's temp file
  • Summary output — shows Parallel: N in the summary line when running in parallel mode

What Runs in Parallel

Block Type Parallel? Notes
Normal blocks Yes Standard code blocks with assertions
throws blocks Yes Exception-expecting blocks
parse_error blocks Yes Parse error-expecting blocks
ignore blocks No Skipped immediately
no_run blocks No Syntax check only
Grouped blocks No Groups run sequentially (shared state)

New Classes

  • CpuCoreDetector — cross-platform CPU core detection
  • WorkerPool — fixed-size process pool with timeout support
  • ParallelExecutor — orchestrates CodeGenerator → WorkerPool → cleanup
  • WorkItem — value object for parallel queue items
  • TempDirectory — run-scoped temp directory management

CLI

doctest --parallel         # auto-detect CPU cores
doctest --parallel <N>     # or -p <N>
doctest --parallel 4 --stop-on-failure

Documentation

v1.5.0

08 Feb 11:46

Choose a tag to compare

New Features

HTML Comment Attribute Syntax

A new way to specify attributes using HTML comments before code blocks. This preserves editor syntax highlighting (PHPStorm, VS Code) which breaks when attributes are added to the info string.

<!-- doctest-attr: ignore -->
```php
// Full syntax highlighting preserved
echo 'Hello, World!';

All attributes are supported: `ignore`, `no_run`, `throws`, `parse_error`, `setup`, `teardown`, `group`, `bootstrap`.

Both sources can coexist — info string for one attribute, HTML comment for another. Conflicts (same attribute from both sources) throw a `RuntimeException`.

**Only one `doctest-attr` comment per block** — multiple comments before a single block throw a `RuntimeException`.

### Debug Dump (`// => dd()`)
Inspect expression values inline without affecting test results. Works like `// =>` but shows the value instead of asserting it.

```php
$x = 42; // => dd()

Output:

dd $x = 42 => 42
  • Always visible regardless of verbosity level
  • Does not affect pass/fail — blocks with only dd() markers always pass
  • Works alongside regular assertions and in groups
  • Multiple dd() markers per block supported

Bug Fixes

  • Multiple doctest-attr comments before a code block now throw RuntimeException instead of silently ignoring all but the first

Documentation

  • Added dedicated pages: Debug Dump, Bootstrap, HTML Comment Syntax
  • Added bootstrap and bootstraps_dir to configuration docs
  • Updated sidebar navigation with all new pages
  • Updated assertion tables, CLI help text, and writing guide

Stats

  • 620 tests, 1230 assertions
  • 99.8% mutation score
  • 50 commits since v1.4.0

v1.4.0 — Bootstrap Profiles

08 Feb 09:33

Choose a tag to compare

Bootstrap Profiles

Per-block bootstrap environments via the .doctest/ directory. Different code blocks can now load different setups — some need a framework, others need a database, others need neither.

New Features

  • Bootstrap profiles — Create .doctest/*.php files, each becomes a named profile
  • Per-block attributebootstrap="name" in code fence info string
  • Profile compositionbootstrap="laravel,database" loads multiple profiles left to right
  • Group validation — All blocks in a group must use identical bootstrap profiles
  • bootstraps_dir config — Customize the profile directory (default: .doctest)

Execution Order

Global bootstrap → Profile(s) → Setup → Code → Teardown

Example

```php bootstrap="laravel"
echo config('app.name');
```

```php bootstrap="laravel,database"
$count = DB::table('users')->count();
echo $count;
```

```php
echo strtoupper('hello');
```
<!-- doctest: HELLO -->

Documentation

  • Framework Bootstrap docs updated with profiles, composition, groups, and Orchestra Testbench examples
  • README and landing page updated
  • CLI --help includes bootstrap attributes and profiles section

Tests

  • 28 new tests (541 total) covering unit, integration, and edge cases

v1.3.0

08 Feb 05:07

Choose a tag to compare

What's New

  • Dynamic version resolution via Composer\InstalledVersions (replaces hardcoded version)
  • Version displayed in --help output header