Add Testo test framework support; improve PHPUnit runnable detection - #127
Add Testo test framework support; improve PHPUnit runnable detection#127roxblnfk wants to merge 6 commits into
Conversation
|
We require contributors to sign our Contributor License Agreement, and we don't have @roxblnfk on file. You can sign our CLA at https://zed.dev/cla. Once you've signed, post a comment here that says '@cla-bot check'. |
|
@cla-bot check |
|
The cla-bot has been summoned, and re-checked this pull request! |
|
Follow-up: only offer PHPUnit on a bare Refined the split from the previous comment. PHPUnit tests always extend
Same shape as the naming-convention PHPUnit patterns (rooted at the class), so it stays linear and doesn't bring back the match-limit blow-up. |
- Require a base_clause on the name-convention (*Test) patterns: a PHPUnit test class always extends TestCase (directly or transitively), so a *Test class with no `extends` is not PHPUnit and is no longer tagged phpunit-test (it is most likely a test from another framework). - Disambiguate method-level #[Test] by the imported attribute (PHPUnit\Framework\Attributes\Test) via the file's `use`, and by a fully-qualified #[\PHPUnit\Framework\Attributes\Test], instead of the class name — so it no longer misfires on another framework's method-level #[Test]. This supersedes zed-extensions#121, which matched any attribute whose last name segment is "Test" (and would also tag e.g. #[\Testo\Test] or #[\App\Test] as PHPUnit).
Testo (https://php-testo.github.io) marks tests with the #[Test] attribute instead of naming/inheritance conventions. runnables.scm (tag `testo-test`): - class-level #[Test] -> the class plus every public void/never method (other return types are data providers and are skipped); - free functions annotated with #[Test]; - method-level #[Test], disambiguated from PHPUnit by `use Testo\Test` (PHP forbids two imports sharing an alias) or a fully-qualified #[\Testo\Test]; - `return new ApplicationConfig(...)` config files (tag `testo-config`). tasks.json: run a symbol/file/whole suite via `php vendor/bin/testo` (--filter/--path), and run a config via --config.
Per-attribute type runnables (gutter icon on the attribute, running only that kind via `--type=`), for methods and free functions alike: * #[Test] / #[\Testo\Test] -> --type=test * #[TestInline] -> --type=inline (repeatable) * #[Bench] -> --type=bench * #[TestRectorFixtures] (class-level) -> --type=rector-fixture Each test symbol also keeps a "run all kinds" icon on its name (no `--type`), and matching tasks were added to tasks.json. Match #[Test] locally instead of via program-rooted `use` correlation. Correlating a bare #[Test] with its `use Testo\Test` / `use PHPUnit\...\Test` import required a query rooted at `program`/`namespace` spanning the whole file; tree-sitter binds the `use` and the method independently, so it materializes one in-progress match state per (use-statement x method) pair (predicates only filter at completion, so the states pile up even when the pattern matches nothing). On real files this exhausts the query match limit and later runnables are dropped -- gutters vanish from some line downward, triggered by a second attribute_group or by many `use` lines. All patterns are now rooted at the declaration (method/function/class), so each match is O(1) states. Consequence: a bare #[Test] can no longer be attributed by syntax alone, so it is treated as Testo; PHPUnit keeps its naming-convention detection and the fully-qualified #[\PHPUnit\Framework\Attributes\Test].
For a bare method-level `#[Test]` (ambiguous between Testo and PHPUnit, with no local way to tell them apart), stop guessing and split the two frameworks by gutter position: the Testo run stays on the attribute row (the `--type=test` icon), and the method-name row now runs PHPUnit. The user picks a framework by where they click. Fully-qualified `#[\Testo\Test]` and free functions remain unambiguously Testo on the name. Also add a run-all gutter on the class name for a class whose `#[Test]` lives on the methods rather than on the class, so the whole class can be run at once. Rooted at the class (never at `program`), so it stays linear in the number of methods and does not reintroduce the match-state blow-up; it fires once per matching method but those collapse to one indicator by row.
PHPUnit tests always extend `TestCase`, so a bare `#[Test]` in a class with no `extends` is not PHPUnit. Gate the method-name PHPUnit runnable on a `base_clause`: an inheriting class still gets the PHPUnit run on the method name, while a bare `#[Test]` in a non-inheriting class (a Testo test) keeps only its Testo attribute runnable and a clean method-name row. The rule is rooted at the class (like the naming-convention PHPUnit patterns), so it stays linear -- the base_clause is a single node, not a per-`use` multiplier, and does not reintroduce the match-state blow-up.
… class A public void/never method in a class-level #[Test] class is a test case, so tag it `testo-type-test` (the single "run this test" action, same as a method-level #[Test] attribute) instead of `testo-test`. The run/file/all menu now lives only on the class name, consistent with how the method-level attribute already behaves.
707c142 to
f585340
Compare
|
Rebased onto For the record, since the force-push rewrote every commit on the branch:
|


Added: Testo support
Runnables and tasks for Testo, which marks tests with attributes rather than by naming convention:
#[Test]→ run button on the class and on each publicvoid/nevermethod (other return types are data providers, skipped);#[Test]is on the methods (not on the class) → run button on the class name too, so the whole class can be run at once;#[Test];#[Test]→--type=test,#[TestInline]→--type=inline,#[Bench]→--type=bench,#[TestRectorFixtures](class-level) →--type=rector-fixture;return new ApplicationConfig(...)).Tasks run
php vendor/bin/testofor a symbol / file / whole suite / config / kind.Changed: PHPUnit detection
*Testclass with noextendsis no longer treated as PHPUnit. PHPUnit tests always extendTestCase, so the name-based patterns now require abase_clause— this stops false buttons on non-PHPUnit*Testclasses (e.g. Testo tests).#[\PHPUnit\Framework\Attributes\Test]now gets a gutter button.#[Test]runs PHPUnit from the method-name row, but only when the class extends — PHPUnit tests always extendTestCase, so a bare#[Test]in a class with no parent is left to Testo (see the note below).Note: the ambiguous bare
#[Test]A bare
#[Test]is shared by Testo and PHPUnit, and the only exact way to tell them apart — the file'suseimport — requires aprogram-rooted query that exhausts tree-sitter's match limit and makes gutters vanish on real files (details in the comment below). So instead of correlating the import, the two frameworks are split by gutter position: the attribute row runs it as Testo, and the method-name row runs it as PHPUnit — the latter only when the classextendssomething (PHPUnit always inheritsTestCase). A bare#[Test]in a class with no parent stays Testo. Fully-qualified#[\Testo\Test]and free functions are unambiguously Testo.Tests
Runnable-query tests for this extension are proposed separately in #129. They cover the existing PHPUnit/Pest queries and document, as
#[ignore]d tests, the exact over-tagging cases this PR fixes (a*Testclass with no inheritance, and a bare#[Test]in such a class). If #129 is merged first, I'll add tests covering the Testo runnables introduced here and flip those documented-gap cases to passing.