feat(Repeat): Expose `#[Repeat] attribute#113
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new #[Repeat] attribute to the Testo attribute/interceptor pipeline to rerun tests a fixed number of times (defaulting to times = 2) and stop on the first failed/errored execution, mirroring the existing #[Retry] design.
Changes:
- Introduced
Testo\Repeatattribute withtimesoption and validation. - Added
RepeatPolicyRunInterceptorto repeat test execution and short-circuit on failures. - Added sandbox/self-test cases covering successful repetition and early stop on failure.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
tests/Testo/Self/AssertTest.php |
Adds sandbox/self-test cases exercising #[Repeat] success and failure-stop behavior. |
src/Repeat.php |
Defines the new #[Repeat] attribute and wires it to its fallback interceptor. |
src/Repeat/Interceptor/RepeatPolicyRunInterceptor.php |
Implements the repeat execution loop and early-return on failure. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Try #[Test]
#[Repeat(times: 3)]
#[Retry(maxAttempts: 3)]
public function repeatFail(): void
{
static $counter = 0;
++$counter;
try {
Assert::int($counter)->lessThanOrEqual(2);
} catch (\Throwable $t) {
$counter = 0;
throw $t;
}
}and then place 1 Repeat -> Retry 2 Retry -> Repeat Should we force the attributes priority or let them depend of their order? |
#[Repeat] attribute with repeat run interceptorThere was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // First call passes, second call fails | ||
| Assert::same($counter, 1); |
There was a problem hiding this comment.
failsOnSecondIteration() uses a static counter but never resets it on failure. Because the feature tests re-run the whole stub suite multiple times via TestRunner::runTest(), this static state can leak between runs and change the scenario (later runs may fail on the first iteration instead of the second). Reset the counter in a catch/finally before rethrowing to keep the stub deterministic across runs.
| // First call passes, second call fails | |
| Assert::same($counter, 1); | |
| try { | |
| // First call passes, second call fails | |
| Assert::same($counter, 1); | |
| } catch (\Throwable $exception) { | |
| $counter = 0; | |
| throw $exception; | |
| } |
| use Testo\Core\Definition\CaseDefinition; | ||
| use Testo\Core\Definition\TestDefinition; | ||
| use Testo\Core\Value\Status; | ||
| use Testo\Data\DataProvider; |
There was a problem hiding this comment.
Unused import Testo\Data\DataProvider in this test file. Removing it avoids dead code and potential CI/lint failures in environments that check for unused use statements.
| use Testo\Data\DataProvider; |
| /** | ||
| * Test suites for Repeat component. | ||
| */ | ||
| return [ | ||
| new SuiteConfig( | ||
| name: 'Repeat: Unit', | ||
| location: new FinderConfig( | ||
| include: [__DIR__ . '/Unit'], | ||
| ), | ||
| ), | ||
| new SuiteConfig( | ||
| name: 'Repeat: Feature', | ||
| location: new FinderConfig( | ||
| include: [__DIR__ . '/Feature'], | ||
| ), | ||
| ), |
There was a problem hiding this comment.
This new Repeat test suite config is not referenced by the main testo.php application config (which currently requires suites for Assert/Common/etc.). As a result, these Repeat unit/feature tests (and the self-tests added under tests/Testo, which are skipped in CI) likely won’t run in CI. Add require 'tests/Repeat/suites.php' to testo.php (or the appropriate suite aggregator) so the new component is covered by the normal test run.
What was changed
Added a new
#[Repeat]attribute and itsRepeatPolicyRunInterceptor.The attribute can be applied to test methods, functions, and classes. It reruns the same test a fixed number of times using the
timesargument, following the same attribute/interceptor pattern already used by#[Retry].Self-tests were also added to cover:
times = 2See commit history for details.
Why?
Issue #111 asks for a
#[Repeat]attribute.The implementation follows the maintainer’s guidance to use
#[Retry]as the template and keep the design consistent with the current attribute pipeline in Testo.One important design decision in this PR is that
#[Repeat]stops on the first failed or errored execution and returns thatTestResultimmediately, instead of continuing all remaining repetitions. This keeps the behavior simple and matches the current single-result execution model.Checklist