Testing
Coqui uses a small number of test layers with different goals:
- Pest unit tests under
tests/Unit/cover PHP classes directly. - Bash tests under
tests/bash/cover launcher, installer helper, and other shell-specific behavior. - PHPStan runs separately as static analysis and should be treated as part of the test gate.
The current suite is mostly SQLite-backed and filesystem-backed unit testing. Most tests create temporary workspaces, temporary SQLite databases, and real store/tool instances instead of relying heavily on mocks.
Test Layout
tests/Unit/Agent/— agent orchestration, loop execution, prompt budgeting, evaluatorstests/Unit/Api/— API managers, handlers, middleware, webhook processingtests/Unit/Config/— config parsing, guards, discovery, role/toolkit resolutiontests/Unit/Storage/— SQLite persistence and query behaviortests/Unit/Toolkit/andtests/Unit/Tool/— tool and toolkit behaviortests/bash/— launcher, mock-installer, and signal-handling tests
Default Commands
For normal development, keep the default path fast:
composer test
composer analyseConvenience wrappers are also available:
make test
make analyse
./scripts/ci-test.shTo include the bash launcher suite:
composer test-bash
make test-launcherCompact test commands reducing output noise:
composer test -- --compact
make test-compactcomposer test -- --compact 2>&1 | rg -i 'warning|deprecated|risky|error|fail(ed)?'
make test-problemsDeveloper Policy
During active development, the default local gate is:
composer test
composer analyseLocal coverage is optional and should be run on demand when you need coverage data or when you are working on the coverage workflow itself. CI coverage is still required: the GitHub Actions coverage lane must continue to pass and produce build/coverage/clover.xml.
This keeps local iteration fast during large refactors while preserving a real end-to-end coverage check in CI.
Coverage Commands
Coverage is opt-in locally and reporting-only in CI right now.
Use one of these commands:
composer test:coverage
composer test -- --coverage
make test-coverage
./scripts/ci-test.sh --coverageUse composer test:coverage when you want the repository wrapper to auto-enable a coverage driver when possible. Use composer test -- --coverage when your PHP process is already configured for coverage.
The wrapper also supports two environment overrides:
COQUI_TEST_COVERAGE_MEMORY_LIMIT=768M composer test:coverage
COQUI_TEST_COVERAGE_DRIVER=pcov composer test:coverage
COQUI_TEST_COVERAGE_DRIVER=xdebug composer test:coverageCOQUI_TEST_COVERAGE_MEMORY_LIMITdefaults to512Mfor coverage runs.COQUI_TEST_COVERAGE_DRIVERdefaults toauto, which preferspcovand falls back toxdebug.
The coverage lane uses the same wrapper command shape with a Clover output path:
composer test:coverage -- --clover build/coverage/clover.xmlThat command writes Clover XML to build/coverage/clover.xml.
Profiling Commands
Xdebug profiling is local-only and intended for short-lived bottleneck investigations. It is slower than normal test runs and should stay out of the default edit loop.
Use the profiling wrapper when you want cachegrind output from a test run:
composer test:profile
composer test:profile -- tests/Unit/Config/ContextWindowResolutionTest.php --filter=context
COQUI_TEST_PROFILE_OUTPUT_DIR=build/profiles/tests/context-window composer test:profileThe wrapper requires the xdebug CLI extension and writes cachegrind files to build/profiles/tests by default.
It excludes the performance test group by default because profiler overhead makes wall-clock benchmark thresholds meaningless.
Optional overrides:
COQUI_TEST_PROFILE_MEMORY_LIMIT=768M composer test:profile
COQUI_TEST_PROFILE_OUTPUT_DIR=build/profiles/tests/slow-run composer test:profile
COQUI_TEST_PROFILE_OUTPUT_NAME=cachegrind.out.%p.%t composer test:profile
COQUI_TEST_PROFILE_INCLUDE_PERFORMANCE=1 composer test:profile -- tests/Unit/PerformanceTest.phpCOQUI_TEST_PROFILE_MEMORY_LIMITdefaults to512M.COQUI_TEST_PROFILE_OUTPUT_DIRdefaults tobuild/profiles/tests.COQUI_TEST_PROFILE_OUTPUT_NAMEdefaults tocachegrind.out.%p.COQUI_TEST_PROFILE_INCLUDE_PERFORMANCEdefaults to0, which keeps benchmark-only tests out of profiled runs unless you explicitly opt in.
For application profiling, keep the runtime unchanged and launch the existing entry point under Xdebug only for the run you want to inspect:
mkdir -p build/profiles/app
XDEBUG_MODE=profile \
XDEBUG_CONFIG="output_dir=$(pwd)/build/profiles/app profiler_output_name=cachegrind.out.%p" \
./bin/coqui-launcher --repl-onlyYou can swap ./bin/coqui-launcher --repl-only for ./bin/coqui-launcher --api-only, ./bin/coqui-launcher, or ./bin/coqui --prompt "..." depending on the slow path you are investigating.
Analyzing Profile Output
Xdebug profiling writes cachegrind.out.* files that can be opened directly by QCacheGrind, KCacheGrind, or Webgrind.
If you want a browser UI without installing a desktop profiler locally, start the bundled Webgrind overlay:
make docker-webgrind-up
open http://localhost:3390That overlay serves one flat profile directory at a time. By default it points at build/profiles/tests, because Webgrind does not reliably browse nested profile trees.
To inspect application profiles instead, start it with a different source directory:
COQUI_WEBGRIND_SOURCE_DIR=./build/profiles/app make docker-webgrind-up
open http://localhost:3390The overlay does not change the base Coqui image or install Xdebug in Docker; profiling still comes from composer test:profile or any direct XDEBUG_MODE=profile run that writes into build/profiles.
Typical workflow:
- Run one narrow test or one narrow application flow so the output stays small enough to inspect.
- Open the newest file from
build/profiles/testsorbuild/profiles/appin QCacheGrind or point Webgrind at that directory. - Sort by self cost and inclusive cost to find hot functions, then drill into callees and callers.
- Repeat the same run after a change and compare the hottest frames instead of comparing unrelated broad runs.
When both PCOV and Xdebug are installed, keep using PCOV for routine coverage and switch to Xdebug only for profiling or debugger-backed investigations.
How Coverage Drivers Work
Pest coverage requires one of these CLI extensions:
pcov— preferred for routine coverage runs because it is lightweightxdebug— useful when you also need step debugging
Check what is currently installed:
php -m | grep -Ei '^(pcov|xdebug)$'
php --iniIf both are installed, prefer PCOV for routine coverage runs.
If you keep xdebug or pcov loaded in your normal CLI all the time, also disable CLI JIT locally to avoid the JIT is incompatible with third party extensions... startup warning:
opcache.jit=0
opcache.jit_buffer_size=0On Homebrew PHP, a late-loading file such as /opt/homebrew/etc/php/8.4/conf.d/zz-local-no-jit.ini is usually the cleanest place for that local override.
Linux Setup
The simplest Linux path is Ubuntu or Debian with PHP packages installed from the ondrej/php PPA, which Coqui already documents elsewhere for PHP 8.4.
Install PCOV:
sudo apt-get update
sudo apt-get install -y php8.4-pcov
php -m | grep -i pcovInstall Xdebug instead:
sudo apt-get update
sudo apt-get install -y php8.4-xdebug
php -m | grep -i xdebugIf your distro package names differ, install the matching package for your active PHP CLI version.
When using raw PECL instead of distro packages:
sudo pecl install pcov
sudo pecl install xdebugThen enable the extension in your CLI .ini scan directory and verify with php --ini.
For Xdebug coverage with plain Pest:
XDEBUG_MODE=coverage composer test -- --coveragemacOS Setup
With Homebrew PHP, install PHP first if needed:
brew install php@8.4 composer
php -vThen install a coverage driver with PECL.
For Apple Silicon Homebrew PHP, the most reliable setup is:
mkdir -p /opt/homebrew/lib/php/pecl/20240924
printf '\n' | pecl install -f xdebug
CPPFLAGS='-I/opt/homebrew/opt/pcre2/include' \
CFLAGS='-I/opt/homebrew/opt/pcre2/include' \
CPATH='/opt/homebrew/opt/pcre2/include' \
printf '\n' | pecl install -f pcov
php -m | grep -Ei '^(pcov|xdebug)$'If both are installed, leave composer test as your default day-to-day command and use composer test:coverage only when you actually need coverage data.
Install PCOV:
pecl install pcovInstall Xdebug:
pecl install xdebugFind your active CLI configuration paths:
php --iniOn Homebrew PHP, the additional .ini scan directory is typically one of these:
/opt/homebrew/etc/php/8.4/conf.d/on Apple Silicon/usr/local/etc/php/8.4/conf.d/on Intel Macs
Enable PCOV with an .ini file containing:
extension=pcov.so
pcov.enabled=1Enable Xdebug with an .ini file containing:
zend_extension=xdebug.soFor local profiling convenience, many developers also add:
xdebug.mode=developThen override the mode per run with XDEBUG_MODE=profile or XDEBUG_MODE=coverage so normal CLI usage stays lighter than a permanently enabled profiler.
Then verify the CLI sees the extension:
php -m | grep -Ei '^(pcov|xdebug)$'For Xdebug coverage with plain Pest:
XDEBUG_MODE=coverage composer test -- --coverageCI Parity
To mirror the local CI pipeline:
./scripts/ci-test.sh
./scripts/ci-test.sh --install
./scripts/ci-test.sh --coverageThe GitHub Actions workflow keeps the full matrix fast by running coverage on one dedicated lane instead of on every job.
Troubleshooting
If composer test:coverage fails with a coverage-driver error:
- Install PCOV or Xdebug for the active PHP CLI.
- Re-run
php -m | grep -Ei '^(pcov|xdebug)$'. - Re-run
php --iniand confirm the extension is loaded from the CLI config, not just FPM or Apache. - If you need to force one driver while debugging the environment, run
COQUI_TEST_COVERAGE_DRIVER=pcov composer test:coverageorCOQUI_TEST_COVERAGE_DRIVER=xdebug composer test:coverage. - If the coverage run needs more headroom, run
COQUI_TEST_COVERAGE_MEMORY_LIMIT=768M composer test:coverage.
If composer test -- --coverage fails while Xdebug is installed:
- Run it as
XDEBUG_MODE=coverage composer test -- --coverage.
If composer test:profile does not write any cachegrind files:
- Re-run
php -m | grep -Ei '^xdebug$'and confirm the active CLI actually loads Xdebug. - Re-run
php --iniand confirm the extension is loaded from the CLI config, not just FPM or Apache. - Set an explicit writable directory with
COQUI_TEST_PROFILE_OUTPUT_DIR=build/profiles/tests/debug composer test:profile. - If you are profiling the application directly, include both
XDEBUG_MODE=profileand anXDEBUG_CONFIGvalue withoutput_dir=....
If coverage works in CI but not locally:
- Compare your local
php -v,php -m, andphp --inioutput with the CI environment. - Make sure you are using the same PHP major/minor version the repo targets.
If tests fail only on shell-heavy suites:
- Run them from macOS, Linux, or WSL2.
- Windows CI intentionally does not treat all Unix shell behavior as a product bug.