Releases: TestFlowLabs/doctest
v1.10.0
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
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 --updateMutual 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<?phpcrash —CodeGeneratorwas re-parsingrawCode(unfiltered) instead of usingexecutableCode(ShikiFilter-processed), producing<?php\n<?php\n...syntax errors. Fixed by storingresultComments/debugMarkersonCodeBlockand using the pre-processedexecutableCodepipeline. - Malformed HTML comment protection —
MarkdownRewriternow handles malformed multi-line comments without crashing - Missing result_comment updates —
Executornow collects all assertion details before returning, enabling// =>updates --update --stop-on-failurevalidation — Mutual exclusion enforced at CLI level
Refactoring
- Batch display block rewrite with pre-computed progress (race condition prevention)
- Removed dead
allAssertionsvariable inCodeBlockExtractor - Removed dead
processableIndexMapvariable inExecutor
Documentation
- Added
--updateoption to CLI reference and options page - Added update feature documentation with workflow guide
- Added
doctest-outputto HTML comment types table - Added update mode exit codes section
- Added
--parallelto 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
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 —
:1targets the first PHP block,:2the 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— NewblockIndicesproperty stores file-to-index mappingDocTestCommand— Parsesfile:Nsyntax from CLI arguments before config creationDocTest— 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 documentation —
file:Nsyntax added to CLI options reference - Quick reference example —
file:Nusage 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
New Feature
- Pre-execution syntax check — DocTest now runs
php -lon generated temp files before executing them. Parse errors (like missing semicolons) are reported with clearSyntax error: ...messages instead of the crypticProcess failed with exit code 255.
Bug Fixes
- Duplicate JSON decoding — Consolidated duplicate
json_decodecalls inExecutor::evaluateNormalResult - Error handler safety —
preg_matcherror handler now usestry/finallyto ensure proper restoration - Warning-free test output — Replaced
@preg_matchwithset_error_handlerfor clean Pest output - Parallel worker validation — Negative/zero
--parallelvalues are now clamped to minimum 1 - File read errors —
DocTest::extractBlocksnow throwsRuntimeExceptionon I/O failure instead of silently returning empty
Refactoring
- Parser deduplication —
HtmlCommentAttributeParsernow delegates toAttributeParser::resolveAttributes(), eliminating 48 lines of duplicated regex constants and parsing logic - Dead code removal — Removed unused
WorkItem::groupBlocksparameter andisGroup()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
setupandteardownto 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
Bug Fix
Circular Reference Handling in var_export
var_export()throwsErrorExceptionwhen encountering circular references (common with Eloquent models in Laravel whereset_error_handlerconverts warnings to exceptions)- Added
__doctest_safe_export()helper to generated code that wrapsvar_export()in try-catch, falling back toprint_r()which handles circular references gracefully (shows*RECURSION*) - Affects both
// => dd()debug markers and// => valueresult comments
Full Changelog
v1.6.1 - Bug Fixes
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 — Parallel Execution
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 4Or configure in doctest.php:
return [
'execution' => [
'parallel' => 4,
],
];Features
- Fixed-size worker pool — N concurrent
proc_openprocesses with non-blocking I/O - CPU auto-detection —
--parallelwithout 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: Nin 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 detectionWorkerPool— fixed-size process pool with timeout supportParallelExecutor— orchestrates CodeGenerator → WorkerPool → cleanupWorkItem— value object for parallel queue itemsTempDirectory— run-scoped temp directory management
CLI
doctest --parallel # auto-detect CPU cores
doctest --parallel <N> # or -p <N>
doctest --parallel 4 --stop-on-failureDocumentation
- New Parallel Execution documentation page
- Updated Configuration with
execution.paralleloption
v1.5.0
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-attrcomments before a code block now throwRuntimeExceptioninstead 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
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/*.phpfiles, each becomes a named profile - Per-block attribute —
bootstrap="name"in code fence info string - Profile composition —
bootstrap="laravel,database"loads multiple profiles left to right - Group validation — All blocks in a group must use identical bootstrap profiles
bootstraps_dirconfig — 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
--helpincludes bootstrap attributes and profiles section
Tests
- 28 new tests (541 total) covering unit, integration, and edge cases