02.10.04 — ActCounter + ThreadsBasedActCounter#
Compteur thread-local du nombre d’
actexécutés par tentative. Permet de reporter au rapport « ce test a fait 17 steps avant d’échouer au step 18 ».
Interface#
# src/ocarina/infra/act_counter.py
class ActCounter:
def get(self) -> int: ...
def reset(self) -> None: ...
def incr_act_call_count(self) -> None: ...Implémentation par défaut#
# src/ocarina/opinionated/infra/act_counter.py
from threading import local
_thread_local = local()
_COUNTER_KEY: Final[str] = "ocarina_counter"
class ActCounter(_ActCounter):
def get(self) -> int:
return getattr(_thread_local, _COUNTER_KEY, 0)
def reset(self) -> None:
setattr(_thread_local, _COUNTER_KEY, 0)
def incr_act_call_count(self) -> None:
if not hasattr(_thread_local, _COUNTER_KEY):
self.reset()
setattr(_thread_local, _COUNTER_KEY, getattr(_thread_local, _COUNTER_KEY) + 1)threading.local(): un namespace dont les attributs sont par thread. Chaque thread a sa propre valeur._COUNTER_KEY = "ocarina_counter": la clé d’attribut.- Pas de lock : chaque thread n’écrit que sa propre valeur, donc pas de race condition.
Pourquoi thread-local plutôt qu’un compteur partagé + lock ?#
Ocarina est architecturé de sorte qu’un thread = un test. Et aucune envie d’introduire toute une state monad ou quoi, autant placer cette « impureté » (qui reste un “état”) ici. C’est tout.
Usage dans create_act#
def run_action() -> Result[TPOM]:
try:
if act_counter_effect:
act_counter_effect()
else:
ThreadsBasedActCounter().incr_act_call_count()
...À chaque act(pom, action) exécuté, le compteur du thread courant est incrémenté.
Usage dans TestFlow.run#
for attempt in range(1, max_attempts + 1):
self._act_counter.reset() # ◄── reset AVANT chaque tentative
with self._drivers_pool.acquire() as driver:
outcome = self._executor.execute(...)
...Et à la fin :
self._act_counter.reset() # ◄── reset après la dernière tentative
return last_result, last_steps_count, test.test_idUsage dans TestExecutor.execute#
steps_count = self._act_counter.get()
return ExecutionOutcome(
result=result, skipped=False, setup_failed=False,
should_retry=should_retry, steps_count=steps_count,
)Le steps_count est lu à la fin d’une tentative. C’est ce qui apparaît dans le TestSuiteResult = (TestResult, steps_count, test_id), et dans le pretty_print_results (« ⫸ At step 3 » quand un test fail).
act_counter_effect — pourquoi un hook customisable#
Parce que c’est une impureté et qu’on veut garder un œil dessus en termes de testabilité. L’exposer ne coûte rien. C’est tout.
Le default reste « incrémente le compteur thread-local d’Ocarina », ce qui couvre 99% des cas.
Pourquoi deux fichiers (infra/ + opinionated/infra/)#
| Fichier | Rôle |
|---|---|
src/ocarina/infra/act_counter.py | Interface ActCounter. Décrit le contrat. Utilisable par n’importe qui. |
src/ocarina/opinionated/infra/act_counter.py | Implémentation ThreadsBasedActCounter. Choix opinionated d’utiliser threading.local. |