Skip to content

Commit 9409d7c

Browse files
committed
Describe Retry plugin
1 parent 0de5aa0 commit 9409d7c

3 files changed

Lines changed: 169 additions & 9 deletions

File tree

CLAUDE.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@ faqLevel: false # no collection — questions stay in place as inline spoilers
136136

137137
## API Signatures (`<signature>`)
138138

139-
For documenting methods and functions in API reference pages. Renders a highlighted PHP signature box with description, parameters, and examples.
139+
For documenting methods, functions, and PHP attributes in API reference pages. Renders a highlighted PHP signature box with description, parameters, and examples.
140140

141141
**Plugin:** `.vitepress/func-block.ts` — markdown-it block rule, Shiki highlighting.
142142

143143
**Styles:** `.vitepress/theme/style.css``.func-block`, `.func-sig` classes.
144144

145-
**Full syntax:**
145+
**Full syntax (function):**
146146
```html
147147
<signature h="3" name="\Testo\Assert::same(mixed $actual, mixed $expected, string $message = ''): void">
148148
<short>Checks strict equality of two values.</short>
@@ -157,9 +157,18 @@ Assert::same($user->role, 'admin');
157157
</signature>
158158
```
159159
160+
**Attribute signature syntax:**
161+
```html
162+
<signature h="2" name="#[\Testo\Retry(int $maxAttempts = 3, bool $markFlaky = true)]">
163+
<short>Declares a retry policy for a test on failure.</short>
164+
</signature>
165+
```
166+
167+
Attribute signatures are detected by the `#[` prefix in `name`. The `#[...]` wrapper is preserved in the rendered signature box. Headings display as `#[ShortName]` (e.g., `#[Retry]`). The inner FQN (without `#[...]`) is used for registry lookup and `<attr>` cross-references.
168+
160169
**Attributes:**
161-
- `name` (required) — full method signature with types and return type. Supports FQN (`\Testo\Assert::method`) — namespace is stripped for display, only short class name shown.
162-
- `h` — heading level for auto-generated heading (`"3"` → `<h3>Assert::same</h3>`). Default: `"0"` (bold text, no heading). Heading text is extracted automatically: `Class::method` from the signature.
170+
- `name` (required) — full signature. For functions: FQN with types and return type (`\Testo\Assert::method`). For attributes: `#[\Namespace\AttrName(params)]`. Namespace is stripped for display.
171+
- `h` — heading level for auto-generated heading (`"3"``<h3>Assert::same</h3>` or `<h2>#[Retry]</h2>`). Default: `"0"` (bold text, no heading).
163172
- `compact` — compact rendering mode: signature + short + description inline, no card/sections. Good for simple methods in lists.
164173

165174
**Inner tags (all optional):**
@@ -195,6 +204,25 @@ Renders as `Assert::blank()` with syntax highlighting. On hover, shows a tooltip
195204

196205
**Registry:** All `<signature>` blocks with FQN names (starting with `\`) are collected at build startup. The `<func>` tag content is matched by stripping arguments: `\Testo\Assert::blank()` matches `\Testo\Assert::blank(mixed $actual, string $message = ''): void`.
197206

207+
## Attribute References (`<attr>`)
208+
209+
For cross-referencing PHP attributes inline within text. Renders as `#[ShortName]` with Shiki highlighting and a hover tooltip showing the full signature and description.
210+
211+
**Plugin:** `.vitepress/func-block.ts` — markdown-it inline + block rules. **Registry:** `.vitepress/func-registry.ts` — separate attribute registry (parallel to function registry).
212+
213+
**Syntax:**
214+
```html
215+
<attr>\Testo\Retry</attr>
216+
```
217+
218+
Takes the plain FQN (without `#[...]`) and renders as `#[Retry]` with syntax highlighting. On hover, shows a tooltip with the full attribute signature and `<short>` description from the corresponding `<signature>` block.
219+
220+
**Behavior:**
221+
- Same linking/tooltip rules as `<func>`: link if `h > 0`, tooltip-only otherwise, plain `<code>` if not found
222+
- The `#[...]` wrapping is added automatically — always pass plain FQN in the tag
223+
- Locale-aware: EN pages reference EN signatures, RU pages reference RU signatures
224+
- Uses a separate registry from `<func>` to avoid FQN collisions
225+
198226
## Class References (`<class>`)
199227

200228
For referencing PHP classes inline. Renders the short class name (without namespace) with a hover tooltip showing the full FQN.

docs/plugins/retry.md

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,76 @@
11
---
2-
llms: false
2+
llms_description: "#[Retry] attribute for retrying failed tests, maxAttempts, markFlaky, class-level and method-level usage, flaky test detection"
33
---
44

55
# Retry
66

7-
<plugin-info class="\Testo\Retry\RetryPlugin" name="Retry" included="\Testo\Application\Config\Plugin\SuitePlugins" />
7+
The plugin provides the <attr>\Testo\Retry</attr> attribute and an interceptor that retry a failed test a specified number of times. If the test passes on retry, it's marked as flaky. The attribute can be placed on a method, function, or an entire class — in the latter case, the policy applies to all tests in the class.
88

9-
::: tip Coming Soon
9+
<plugin-info name="Retry" />
10+
11+
<signature h="2" name="#[\Testo\Retry(int $maxAttempts = 3, bool $markFlaky = true)]">
12+
<short>Declares a retry policy for a test on failure.</short>
13+
<description>
14+
Works with any test type: regular tests, inline tests, benchmarks. When placed on a class (Test Case), applies to all tests within it.
15+
</description>
16+
<param name="$maxAttempts">Maximum number of attempts, including the first run. For example, `3` means up to two retries after the initial failure.</param>
17+
<param name="$markFlaky">Whether to mark the test as flaky if it only passed on retry. Defaults to `true`.</param>
18+
<example>
19+
Retry a test up to 3 times:
20+
21+
```php
22+
#[Retry]
23+
public function flakyExternalService(): void
24+
{
25+
$response = HttpClient::get('https://api.example.com/health');
26+
Assert::same(200, $response->statusCode);
27+
}
28+
```
29+
</example>
30+
<example>
31+
On a class — all tests inside inherit the retry policy:
32+
33+
```php
34+
#[Retry(maxAttempts: 5, markFlaky: false)]
35+
final class ExternalApiTest
36+
{
37+
public function checkHealth(): void { /* ... */ }
38+
39+
public function checkStatus(): void { /* ... */ }
40+
}
41+
```
42+
</example>
43+
</signature>
44+
45+
## Suite-Level Policy
46+
47+
The attribute sets a retry policy for a specific test or class. To apply retries to an entire Test Suite, use the <class>\Testo\Retry\Interceptor\RetryPolicyRunInterceptor</class> directly — add it to the pipeline via a plugin:
48+
49+
```php
50+
return new ApplicationConfig(
51+
suites: [
52+
new SuiteConfig(
53+
name: 'Integration',
54+
location: ['tests/Integration'],
55+
plugins: [
56+
// ...
57+
new class implements PluginConfigurator {
58+
#[\Override]
59+
public function configure(Container $container): void
60+
{
61+
$container->get(InterceptorProvider::class)->addInterceptor(
62+
$container->make(RetryPolicyRunInterceptor::class, [new Retry(maxAttempts: 3)]),
63+
);
64+
}
65+
},
66+
],
67+
),
68+
],
69+
);
70+
```
71+
72+
This way all tests in the Integration Test Suite will be retried up to 3 times on failure, without placing the attribute on each test individually.
73+
74+
::: question What happens if a retry policy is defined at multiple levels?
75+
When multiple retry policies are defined, only the closest one to the test applies. For example, if the Test Suite has `maxAttempts: 3`, the class has `2`, and the method has `5`, the test will retry **up to 5 times**. Policies do not stack.
1076
:::

ru/docs/plugins/retry.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,72 @@
1-
# Retry
1+
# Повторный запуск
2+
3+
Плагин предоставляет атрибут <attr>\Testo\Retry</attr> и интерцептор, которые перезапускают упавший тест указанное количество раз. Если тест проходит при повторной попытке, он помечается как нестабильный (flaky). Атрибут можно повесить на метод, функцию или целый класс — в последнем случае политика применяется ко всем тестам в классе.
24

35
<plugin-info name="Retry" />
46

5-
::: tip Coming Soon
7+
<signature h="2" name="#[\Testo\Retry(int $maxAttempts = 3, bool $markFlaky = true)]">
8+
<short>Объявляет политику повторного запуска теста при падении.</short>
9+
<description>
10+
Можно использовать на любом типе тестов: обычных, встроенных, бенчмарках. При размещении на классе (Test Case) применяется ко всем тестам внутри.
11+
</description>
12+
<param name="$maxAttempts">Максимальное количество попыток, включая первый запуск. Например, `3` означает не более двух повторных попыток после первого падения.</param>
13+
<param name="$markFlaky">Помечать ли тест как нестабильный (flaky), если он прошёл только при повторной попытке. По умолчанию `true`.</param>
14+
<example>
15+
Перезапустить тест до 3 раз:
16+
17+
```php
18+
#[Retry]
19+
public function flakyExternalService(): void
20+
{
21+
$response = HttpClient::get('https://api.example.com/health');
22+
Assert::same(200, $response->statusCode);
23+
}
24+
```
25+
</example>
26+
<example>
27+
На классе — все тесты внутри получат политику повторного запуска:
28+
29+
```php
30+
#[Retry(maxAttempts: 5, markFlaky: false)]
31+
final class ExternalApiTest
32+
{
33+
public function checkHealth(): void { /* ... */ }
34+
35+
public function checkStatus(): void { /* ... */ }
36+
}
37+
```
38+
</example>
39+
</signature>
40+
41+
## Политика повторов на уровне Test Suite
42+
43+
Атрибут задаёт политику для конкретного теста или класса. Если нужно применить повторный запуск ко всему Test Suite, используйте интерцептор <class>\Testo\Retry\Interceptor\RetryPolicyRunInterceptor</class> напрямую — добавьте его в пайплайн через плагин:
44+
45+
```php
46+
return new ApplicationConfig(
47+
suites: [
48+
new SuiteConfig(
49+
name: 'Integration',
50+
location: ['tests/Integration'],
51+
plugins: [
52+
// ...
53+
new class implements PluginConfigurator {
54+
#[\Override]
55+
public function configure(Container $container): void
56+
{
57+
$container->get(InterceptorProvider::class)->addInterceptor(
58+
$container->make(RetryPolicyRunInterceptor::class, [new Retry(maxAttempts: 3)]),
59+
);
60+
}
61+
},
62+
],
63+
),
64+
],
65+
);
66+
```
67+
68+
В этом случае все тесты в Test Suite - Integration будут перезапускаться до 3 раз при падении, без необходимости расставлять атрибуты на каждом тесте.
69+
70+
::: question Что будет, если задать политику повторов на нескольких уровнях?
71+
При множественном определении политики повторов применяется только ближайшая к тесту. Например, если на Test Suite задано `maxAttempts: 3`, на классе — `2`, а на методе — `5`, тест будет повторяться **до 5 раз**. Политики не накапливаются.
672
:::

0 commit comments

Comments
 (0)