socx-cli 0.13.6__tar.gz → 0.13.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {socx_cli-0.13.6 → socx_cli-0.13.8}/PKG-INFO +2 -2
  2. {socx_cli-0.13.6 → socx_cli-0.13.8}/pyproject.toml +2 -2
  3. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/converters.py +7 -7
  4. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/enums.py +7 -2
  5. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/singleton/singleton.py +1 -1
  6. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/regression/progress.py +52 -53
  7. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/regression/regression.py +191 -237
  8. socx_cli-0.13.8/socx/regression/test.py +605 -0
  9. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/settings/cli.yaml +6 -1
  10. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/settings/console.yaml +6 -1
  11. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/settings/logging.yaml +1 -1
  12. socx_cli-0.13.8/socx/static/settings/regression.yaml +113 -0
  13. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/settings/rich_click.yaml +5 -5
  14. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/regression/_run.py +13 -16
  15. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/regression/callbacks.py +1 -1
  16. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/regression/tui.py +1 -1
  17. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/app.py +41 -29
  18. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/details.py +48 -24
  19. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/dialog.py +79 -3
  20. socx_cli-0.13.8/socx_tui/regression/mixins/configurable.py +22 -0
  21. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/widget.py +105 -47
  22. socx_cli-0.13.8/socx_tui/static/tcss/regression/app.tcss +224 -0
  23. socx_cli-0.13.6/socx/regression/test.py +0 -351
  24. socx_cli-0.13.6/socx/static/settings/regression.yaml +0 -21
  25. socx_cli-0.13.6/socx_tui/static/tcss/regression/app.tcss +0 -107
  26. {socx_cli-0.13.6 → socx_cli-0.13.8}/.gitignore +0 -0
  27. {socx_cli-0.13.6 → socx_cli-0.13.8}/LICENSE +0 -0
  28. {socx_cli-0.13.6 → socx_cli-0.13.8}/README.md +0 -0
  29. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/__init__.py +0 -0
  30. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/__main__.py +0 -0
  31. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/__init__.py +0 -0
  32. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/_cli.py +0 -0
  33. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/_jinja.py +0 -0
  34. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/callbacks.py +0 -0
  35. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/cfg.py +0 -0
  36. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/cli.py +0 -0
  37. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/params.py +0 -0
  38. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/cli/types.py +0 -0
  39. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/__init__.py +0 -0
  40. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/_config.py +0 -0
  41. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/_settings.py +0 -0
  42. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/encoders.py +0 -0
  43. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/formatters.py +0 -0
  44. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/serializers.py +0 -0
  45. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/config/validators.py +0 -0
  46. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/__init__.py +0 -0
  47. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/_paths.py +0 -0
  48. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/encoder.py +0 -0
  49. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/funcs.py +0 -0
  50. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/metadata.py +0 -0
  51. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/paths.py +0 -0
  52. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/schema/__init__.py +0 -0
  53. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/schema/git/__init__.py +0 -0
  54. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/schema/git/git.py +0 -0
  55. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/schema/git/manifest.py +0 -0
  56. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/schema/plugin.py +0 -0
  57. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/schema/types.py +0 -0
  58. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/core/serializer.py +0 -0
  59. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/git/__init__.py +0 -0
  60. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/git/_git.py +0 -0
  61. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/git/_manifest.py +0 -0
  62. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/git/_ssh.py +0 -0
  63. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/io/__init__.py +0 -0
  64. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/io/console.py +0 -0
  65. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/io/decorators.py +0 -0
  66. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/io/log.py +0 -0
  67. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/__init__.py +0 -0
  68. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/mixins/__init__.py +0 -0
  69. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/mixins/proxy.py +0 -0
  70. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/mixins/uid.py +0 -0
  71. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/singleton/__init__.py +0 -0
  72. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/visitor/__init__.py +0 -0
  73. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/visitor/protocol.py +0 -0
  74. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/patterns/visitor/traversal.py +0 -0
  75. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/regression/__init__.py +0 -0
  76. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/regression/status.py +0 -0
  77. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/regression/validator.py +0 -0
  78. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/regression/visitor.py +0 -0
  79. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/settings/git.yaml +0 -0
  80. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/settings/plugins.yaml +0 -0
  81. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/settings/settings.yaml +1 -1
  82. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/static/sql/socx.sql +0 -0
  83. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/utils/__init__.py +0 -0
  84. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx/utils/decorators.py +0 -0
  85. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/config/__init__.py +0 -0
  86. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/config/_config.py +0 -0
  87. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/config/edit.py +0 -0
  88. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/__init__.py +0 -0
  89. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/arguments.py +0 -0
  90. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/callbacks.py +0 -0
  91. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/cli.py +0 -0
  92. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/manifest.py +0 -0
  93. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/renderables.py +0 -0
  94. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/summary.py +0 -0
  95. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/git/utils.py +0 -0
  96. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/plugin/__init__.py +0 -0
  97. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/plugin/example.py +0 -0
  98. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/plugin/schema.py +0 -0
  99. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/regression/__init__.py +0 -0
  100. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/regression/cli.py +0 -0
  101. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/regression/run.py +0 -0
  102. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/version/__init__.py +0 -0
  103. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_plugins/version/__main__.py +0 -0
  104. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/__init__.py +0 -0
  105. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/__init__.py +0 -0
  106. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/__main__.py +0 -0
  107. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/bindings/__init__.py +0 -0
  108. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/bindings/vim/__init__.py +0 -0
  109. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/bindings/vim/mode.py +0 -0
  110. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/bindings/vim/vim.py +0 -0
  111. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/containers.py +0 -0
  112. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/mixins/__init__.py +0 -0
  113. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/mixins/composable.py +0 -0
  114. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/preview.py +0 -0
  115. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/table.py +0 -0
  116. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/regression/tree.py +0 -0
  117. {socx_cli-0.13.6 → socx_cli-0.13.8}/socx_tui/static/tcss/regression/preview.tcss +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socx-cli
3
- Version: 0.13.6
3
+ Version: 0.13.8
4
4
  Summary: System on chip verification and tooling infrastructure.
5
5
  Project-URL: Issues, https://github.com/sagikimhi/socx-cli/issues
6
6
  Project-URL: Homepage, https://sagikimhi.dev/socx-cli
@@ -28,7 +28,7 @@ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA
28
28
  Classifier: Topic :: Software Development
29
29
  Classifier: Topic :: Utilities
30
30
  Requires-Python: >=3.12
31
- Requires-Dist: anyio>=4.12.1
31
+ Requires-Dist: anyio[trio]>=4.12.1
32
32
  Requires-Dist: click
33
33
  Requires-Dist: copier~=9.11
34
34
  Requires-Dist: declare
@@ -29,7 +29,7 @@ socx = 'socx.__main__:main'
29
29
  [project]
30
30
  name = "socx-cli"
31
31
  readme = "README.md"
32
- version = "0.13.6"
32
+ version = "0.13.8"
33
33
  license = "Apache-2.0"
34
34
  authors = [{ name = "Sagi Kimhi", email = "sagi.kim5@gmail.com" }]
35
35
  maintainers = [{ name = "Sagi Kimhi", email = "sagi.kim5@gmail.com" }]
@@ -82,7 +82,7 @@ dependencies = [
82
82
  "pydantic-extra-types>=2.11.0",
83
83
  "pydantic-settings>=2.12.0",
84
84
  "sqlmodel>=0.0.31",
85
- "anyio>=4.12.1",
85
+ "anyio[trio]>=4.12.1",
86
86
  ]
87
87
 
88
88
  [project.urls]
@@ -310,8 +310,8 @@ class SymbolConverter(Converter[str | Lazy, Any]):
310
310
 
311
311
  class CommandConverter(
312
312
  Converter[
313
- str | Lazy | sh.Command | click.Command | PluginModel,
314
- str | Lazy | click.Command,
313
+ str | Lazy | BaseCommand | click.Command | PluginModel,
314
+ str | Lazy | click.Command | click.Group,
315
315
  ]
316
316
  ):
317
317
  """Turn module or script references into Rich Click commands."""
@@ -351,7 +351,7 @@ class CommandConverter(
351
351
  @overload
352
352
  def __call__(
353
353
  self,
354
- value: sh.Command,
354
+ value: BaseCommand,
355
355
  *args: Any,
356
356
  **kwargs: Any,
357
357
  ) -> click.Command: ...
@@ -374,10 +374,10 @@ class CommandConverter(
374
374
  @_validate
375
375
  def __call__(
376
376
  self,
377
- value: str | Lazy | sh.Command | click.Command | PluginModel,
377
+ value: str | Lazy | BaseCommand | click.Command | PluginModel,
378
378
  *args: Any,
379
379
  **kwargs: Any,
380
- ) -> Lazy | click.Command | click.Group:
380
+ ) -> str | Lazy | click.Command | click.Group:
381
381
  """Build a Click command from dotted paths or reuse existing ones."""
382
382
  if isinstance(value, Lazy):
383
383
  if value.casting is self:
@@ -401,9 +401,9 @@ class CommandConverter(
401
401
  nonlocal value
402
402
 
403
403
  if TYPE_CHECKING:
404
- value = cast(str | sh.Command | PluginModel, value)
404
+ value = cast(str | BaseCommand | PluginModel, value)
405
405
 
406
- if isinstance(value, sh.Command):
406
+ if isinstance(value, str | BaseCommand):
407
407
  return self._run_shell_script(value, *args)
408
408
 
409
409
  if isinstance(value, PluginModel) and value.is_script():
@@ -18,14 +18,19 @@ class AutoNumber(int, enum.ReprEnum):
18
18
 
19
19
 
20
20
  class SettingsFormat(AutoNumber):
21
- Ini = ".ini"
22
21
  Json = ".json"
23
22
  Yaml = ".yaml", ".yml"
24
23
  Toml = ".toml"
24
+ Ini = ".ini"
25
25
  Python = ".python"
26
26
 
27
27
  def __init__(self, extension: str, *extensions: str) -> None:
28
- self.extensions = [extension, *extensions]
28
+ self.extensions = {extension, *extensions}
29
+
30
+ @classmethod
31
+ @cache
32
+ def all_extensions(cls) -> set[str]:
33
+ return {extension for member in cls for extension in member.extensions}
29
34
 
30
35
  @classmethod
31
36
  @cache
@@ -18,7 +18,7 @@ class _SingletonMeta(type):
18
18
  return cls._instances[cls]
19
19
 
20
20
 
21
- class Singleton(_SingletonMeta("SingletonMeta", (object,), {})):
21
+ class Singleton(_SingletonMeta("_SingletonMeta", (object,), {})):
22
22
  """Mixin class for creating singleton classes.
23
23
 
24
24
  Extending this class enforces the singleton pattern on the subclass.
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- import anyio
4
3
  import logging
5
4
  from typing import Any
6
- from collections import ChainMap
7
5
 
6
+ import anyio
7
+ from anyio.abc import TaskStatus
8
8
  from rich.progress import (
9
9
  Progress as BaseProgress,
10
10
  ProgressColumn,
@@ -27,7 +27,6 @@ logger = logging.getLogger(__name__)
27
27
 
28
28
  class PipelineProgress(BaseProgress):
29
29
  def __init__(self, **kwargs: Any) -> None:
30
- kwargs = dict(ChainMap(kwargs, dict(speed_estimate_period=10)))
31
30
  super().__init__(*self.get_default_columns(), **kwargs)
32
31
 
33
32
  @classmethod
@@ -60,23 +59,10 @@ class RegressionProgress:
60
59
  self,
61
60
  include: set[str] | None = None,
62
61
  exclude: set[str] | None = None,
62
+ limiter: anyio.CapacityLimiter | None = None,
63
63
  ) -> None:
64
- """Update progress tasks and flush log messages while running."""
65
- with self.progress:
66
- if include is None and exclude is None:
67
- async with anyio.create_task_group() as tg:
68
- for obj in self.regression.tests:
69
- tg.start_soon(
70
- self.track_regression,
71
- obj,
72
- name=f"track_{obj.name}_progress",
73
- )
74
- tg.start_soon(
75
- self.regression.start,
76
- name=f"{self.regression.name}",
77
- )
78
- return
79
-
64
+ self.progress.start()
65
+ try:
80
66
  async with anyio.create_task_group() as tg:
81
67
  for obj in self.regression.tests:
82
68
  if exclude is not None and obj.name in exclude:
@@ -86,54 +72,62 @@ class RegressionProgress:
86
72
  continue
87
73
 
88
74
  if isinstance(obj, Regression) and not obj.started:
89
- tg.start_soon(obj.start, name=obj.name)
90
75
  tg.start_soon(
91
76
  self.track_regression,
92
77
  obj,
93
78
  name=f"track_{obj.name}_progress",
94
79
  )
80
+ finally:
81
+ self.progress.stop()
95
82
 
96
- async def track_regression(self, regression: Regression) -> None:
97
- finished = 0
98
- status = TestStatus.Idle
99
- progress = self.progress
83
+ async def track_regression(
84
+ self,
85
+ regression: Regression,
86
+ limiter: anyio.CapacityLimiter | None = None,
87
+ task_status: TaskStatus = anyio.TASK_STATUS_IGNORED,
88
+ ) -> None:
100
89
 
101
- if regression.id not in self.tasks:
102
- self.tasks[regression.id] = progress.add_task(
103
- total=len(regression),
104
- description=(
105
- f"[gray39]{self._get_task_tag(regression)}: "
106
- f"{regression.status.name}"
107
- ),
108
- )
90
+ async with anyio.create_task_group() as tg:
91
+ if regression.id not in self.tasks:
92
+ self.tasks[regression.id] = self.progress.add_task(
93
+ total=len(regression),
94
+ description=(
95
+ f"[gray39]{self._get_task_tag(regression)}: "
96
+ f"{regression.status.name}"
97
+ ),
98
+ )
99
+ tg.start_soon(self._track_regression, regression)
100
+ await tg.start(regression.start, limiter)
101
+ task_status.started()
109
102
 
110
- while not progress.finished:
111
- if regression.finished:
112
- self.update_regression(regression, len(regression))
113
- break
103
+ async def _track_regression(self, regression: Regression) -> None:
104
+ task = self.progress.tasks[self.tasks[regression.id]]
114
105
 
115
- prev_status = status
116
- prev_finished = finished
117
- status = regression.status
118
- finished = self._count_statuses(
106
+ while True:
107
+ finished = await self._count_statuses(
119
108
  regression,
120
109
  TestStatus.Finished,
121
110
  TestStatus.Terminated,
122
111
  )
123
112
 
124
- if prev_finished != finished or prev_status != status:
125
- self.update_regression(regression, finished)
113
+ if finished != task.completed:
114
+ await self.update_regression(regression, finished)
115
+
116
+ if finished == len(regression):
117
+ break
126
118
 
127
- await anyio.sleep(0.5)
119
+ await anyio.sleep(0.05)
128
120
 
129
- def advance_regression(self, regression: Regression, n: int) -> None:
121
+ async def advance_regression(
122
+ self, regression: Regression, n: int = 1
123
+ ) -> None:
130
124
  progress = self.progress
131
125
  tid = self.tasks.get(regression.id)
132
126
  if progress is not None and tid is not None:
133
127
  task = progress.tasks[tid]
134
- self.update_regression(regression, task.completed + n)
128
+ await self.update_regression(regression, task.completed + n)
135
129
 
136
- def update_regression(self, regression: Regression, n: int) -> None:
130
+ async def update_regression(self, regression: Regression, n: int) -> None:
137
131
  progress = self.progress
138
132
  tid = self.tasks.get(regression.id)
139
133
  if progress is not None and tid is not None:
@@ -144,32 +138,37 @@ class RegressionProgress:
144
138
  or regression.is_pending()
145
139
  or regression.is_suspended()
146
140
  ):
147
- task.description = (
141
+ description = (
148
142
  f"[gray39]{self._get_task_tag(regression)}: "
149
143
  f"{regression.status.name}"
150
144
  )
151
145
  elif regression.is_running():
152
- task.description = (
146
+ description = (
153
147
  f"[yellow]{self._get_task_tag(regression)}: "
154
148
  f"{regression.status.name}"
155
149
  )
156
150
  elif regression.finished:
157
- task.description = (
151
+ description = (
158
152
  f"[green]{self._get_task_tag(regression)}: "
159
153
  f"{regression.status.name}"
160
154
  )
161
155
  elif regression.terminated:
162
- task.description = (
156
+ description = (
163
157
  f"[red]{self._get_task_tag(regression)}: "
164
158
  f"{regression.status.name}"
165
159
  )
166
160
 
167
- task.completed = min(n, task.total)
161
+ completed = min(n, task.total)
162
+ progress.update(
163
+ task.id,
164
+ completed=completed,
165
+ description=description,
166
+ )
168
167
 
169
- def _count_statuses(
168
+ async def _count_statuses(
170
169
  self, regression: Regression, *statuses: TestStatus
171
170
  ) -> int:
172
- with self.regression.lock:
171
+ async with self.regression.mutex:
173
172
  return sum(
174
173
  1 for test in regression.tests if test.status in statuses
175
174
  )