socx-cli 0.13.0__tar.gz → 0.13.2__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.
- {socx_cli-0.13.0 → socx_cli-0.13.2}/PKG-INFO +3 -2
- {socx_cli-0.13.0 → socx_cli-0.13.2}/pyproject.toml +6 -2
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/__init__.py +8 -6
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/_paths.py +1 -1
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/__init__.py +2 -2
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/visitor/__init__.py +2 -2
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/visitor/protocol.py +13 -8
- socx_cli-0.13.2/socx/patterns/visitor/traversal.py +64 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/regression/__init__.py +2 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/regression/progress.py +2 -2
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/regression/regression.py +85 -62
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/regression/test.py +35 -32
- socx_cli-0.13.2/socx/regression/visitor.py +25 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/settings/regression.yaml +1 -1
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/regression/_run.py +79 -68
- socx_cli-0.13.0/socx/patterns/visitor/traversal.py +0 -49
- {socx_cli-0.13.0 → socx_cli-0.13.2}/.gitignore +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/LICENSE +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/README.md +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/__main__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/_cli.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/_jinja.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/callbacks.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/cfg.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/cli.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/params.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/cli/types.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/_config.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/_settings.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/converters.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/encoders.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/formatters.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/serializers.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/config/validators.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/encoder.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/enums.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/funcs.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/metadata.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/paths.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/schema/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/schema/git/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/schema/git/git.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/schema/git/manifest.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/schema/plugin.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/schema/types.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/core/serializer.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/git/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/git/_git.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/git/_manifest.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/git/_ssh.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/io/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/io/console.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/io/decorators.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/io/log.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/mixins/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/mixins/proxy.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/mixins/uid.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/singleton/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/patterns/singleton/singleton.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/regression/status.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/regression/validator.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/settings/cli.yaml +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/settings/console.yaml +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/settings/git.yaml +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/settings/plugins.yaml +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/settings/rich_click.yaml +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/settings/settings.yaml +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/static/sql/socx.sql +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/utils/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx/utils/decorators.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/config/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/config/_config.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/config/edit.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/arguments.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/callbacks.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/cli.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/manifest.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/renderables.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/summary.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/git/utils.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/plugin/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/plugin/example.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/plugin/schema.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/regression/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/regression/callbacks.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/regression/cli.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/regression/run.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/regression/tui.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/version/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_plugins/version/__main__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/__main__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/app.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/bindings/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/bindings/vim/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/bindings/vim/mode.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/bindings/vim/vim.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/containers.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/details.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/dialog.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/mixins/__init__.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/mixins/composable.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/preview.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/table.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/tree.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/regression/widget.py +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/socx_tui/static/tcss/regression/app.tcss +0 -0
- {socx_cli-0.13.0 → socx_cli-0.13.2}/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.
|
|
3
|
+
Version: 0.13.2
|
|
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,9 +28,10 @@ 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
32
|
Requires-Dist: click
|
|
32
33
|
Requires-Dist: copier~=9.11
|
|
33
|
-
Requires-Dist: dynaconf
|
|
34
|
+
Requires-Dist: dynaconf<3.2.12
|
|
34
35
|
Requires-Dist: gitpython
|
|
35
36
|
Requires-Dist: hoptex
|
|
36
37
|
Requires-Dist: jinja2
|
|
@@ -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.
|
|
32
|
+
version = "0.13.2"
|
|
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" }]
|
|
@@ -67,7 +67,7 @@ dependencies = [
|
|
|
67
67
|
"textual",
|
|
68
68
|
"plumbum",
|
|
69
69
|
"pydantic",
|
|
70
|
-
"dynaconf",
|
|
70
|
+
"dynaconf<3.2.12",
|
|
71
71
|
"paramiko",
|
|
72
72
|
"werkzeug>=3.1.4",
|
|
73
73
|
"gitpython",
|
|
@@ -81,6 +81,7 @@ dependencies = [
|
|
|
81
81
|
"pydantic-extra-types>=2.11.0",
|
|
82
82
|
"pydantic-settings>=2.12.0",
|
|
83
83
|
"sqlmodel>=0.0.31",
|
|
84
|
+
"anyio>=4.12.1",
|
|
84
85
|
]
|
|
85
86
|
|
|
86
87
|
[project.urls]
|
|
@@ -164,6 +165,9 @@ packages = ["src/socx", "src/socx_tui", "src/socx_plugins"]
|
|
|
164
165
|
[tool.hatch.build.targets.wheel]
|
|
165
166
|
packages = ["src/socx", "src/socx_tui", "src/socx_plugins"]
|
|
166
167
|
|
|
168
|
+
[tool.hatch.build.targets.binary]
|
|
169
|
+
packages = ["src/socx", "src/socx_tui", "src/socx_plugins"]
|
|
170
|
+
|
|
167
171
|
# -----------------------------------------------------------------------------
|
|
168
172
|
# Tool Configurations - pytest
|
|
169
173
|
# -----------------------------------------------------------------------------
|
|
@@ -123,9 +123,9 @@ __all__ = (
|
|
|
123
123
|
# Patterns
|
|
124
124
|
"Node",
|
|
125
125
|
"Visitor",
|
|
126
|
-
"Structure",
|
|
127
126
|
"Traversal",
|
|
128
127
|
"Singleton",
|
|
128
|
+
"StructureProxy",
|
|
129
129
|
"ByLevelTraversal",
|
|
130
130
|
"TopDownTraversal",
|
|
131
131
|
"BottomUpTraversal",
|
|
@@ -135,6 +135,7 @@ __all__ = (
|
|
|
135
135
|
"Regression",
|
|
136
136
|
"TestStatus",
|
|
137
137
|
"TestResult",
|
|
138
|
+
"RegressionTree",
|
|
138
139
|
"RegressionProgress",
|
|
139
140
|
)
|
|
140
141
|
|
|
@@ -248,21 +249,22 @@ from socx.config import validate_all as validate_all
|
|
|
248
249
|
|
|
249
250
|
from socx.patterns import Node as Node
|
|
250
251
|
from socx.patterns import Visitor as Visitor
|
|
251
|
-
from socx.patterns import Structure as Structure
|
|
252
252
|
from socx.patterns import Traversal as Traversal
|
|
253
|
-
from socx.patterns import
|
|
254
|
-
from socx.patterns import PtrMixin as PtrMixin
|
|
255
|
-
from socx.patterns import ProxyMixin as ProxyMixin
|
|
256
|
-
from socx.patterns import Singleton as Singleton
|
|
253
|
+
from socx.patterns import StructureProxy as StructureProxy
|
|
257
254
|
from socx.patterns import TopDownTraversal as TopDownTraversal
|
|
258
255
|
from socx.patterns import BottomUpTraversal as BottomUpTraversal
|
|
259
256
|
from socx.patterns import ByLevelTraversal as ByLevelTraversal
|
|
257
|
+
from socx.patterns import UIDMixin as UIDMixin
|
|
258
|
+
from socx.patterns import PtrMixin as PtrMixin
|
|
259
|
+
from socx.patterns import Singleton as Singleton
|
|
260
|
+
from socx.patterns import ProxyMixin as ProxyMixin
|
|
260
261
|
|
|
261
262
|
from socx.regression import Test as Test
|
|
262
263
|
from socx.regression import TestBase as TestBase
|
|
263
264
|
from socx.regression import Regression as Regression
|
|
264
265
|
from socx.regression import TestStatus as TestStatus
|
|
265
266
|
from socx.regression import TestResult as TestResult
|
|
267
|
+
from socx.regression import RegressionTree as RegressionTree
|
|
266
268
|
from socx.regression import RegressionProgress as RegressionProgress
|
|
267
269
|
|
|
268
270
|
from socx.cli import FuncType as FuncType
|
|
@@ -55,7 +55,7 @@ def find_project_root(start_path: Path) -> Path:
|
|
|
55
55
|
# Application Paths
|
|
56
56
|
# -----------------------------------------------------------------------------
|
|
57
57
|
|
|
58
|
-
APP_ROOT_DIR: Path = Path(getfile(lambda: None)).parents[
|
|
58
|
+
APP_ROOT_DIR: Path = Path(getfile(lambda: None)).parents[1].resolve()
|
|
59
59
|
"""Absolute path to directory where application is installed."""
|
|
60
60
|
|
|
61
61
|
APP_STATIC_DIR: Path = __directory__ / "static"
|
|
@@ -6,7 +6,7 @@ __all__ = (
|
|
|
6
6
|
"PtrMixin",
|
|
7
7
|
"UIDMixin",
|
|
8
8
|
"ProxyMixin",
|
|
9
|
-
"
|
|
9
|
+
"StructureProxy",
|
|
10
10
|
"Traversal",
|
|
11
11
|
"Singleton",
|
|
12
12
|
"ByLevelTraversal",
|
|
@@ -21,8 +21,8 @@ from socx.patterns.mixins import ProxyMixin as ProxyMixin
|
|
|
21
21
|
|
|
22
22
|
from socx.patterns.visitor import Node as Node
|
|
23
23
|
from socx.patterns.visitor import Visitor as Visitor
|
|
24
|
-
from socx.patterns.visitor import Structure as Structure
|
|
25
24
|
from socx.patterns.visitor import Traversal as Traversal
|
|
25
|
+
from socx.patterns.visitor import StructureProxy as StructureProxy
|
|
26
26
|
from socx.patterns.visitor import ByLevelTraversal as ByLevelTraversal
|
|
27
27
|
from socx.patterns.visitor import TopDownTraversal as TopDownTraversal
|
|
28
28
|
from socx.patterns.visitor import BottomUpTraversal as BottomUpTraversal
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
__all__ = (
|
|
4
4
|
"Node",
|
|
5
5
|
"Visitor",
|
|
6
|
-
"
|
|
6
|
+
"StructureProxy",
|
|
7
7
|
"Traversal",
|
|
8
8
|
"ByLevelTraversal",
|
|
9
9
|
"TopDownTraversal",
|
|
@@ -13,8 +13,8 @@ __all__ = (
|
|
|
13
13
|
|
|
14
14
|
from socx.patterns.visitor.protocol import Node as Node
|
|
15
15
|
from socx.patterns.visitor.protocol import Visitor as Visitor
|
|
16
|
-
from socx.patterns.visitor.protocol import Structure as Structure
|
|
17
16
|
from socx.patterns.visitor.protocol import Traversal as Traversal
|
|
17
|
+
from socx.patterns.visitor.protocol import StructureProxy as StructureProxy
|
|
18
18
|
|
|
19
19
|
from socx.patterns.visitor.traversal import (
|
|
20
20
|
ByLevelTraversal as ByLevelTraversal,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from typing import Protocol
|
|
6
|
-
from collections.abc import
|
|
6
|
+
from collections.abc import Generator
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Visitor[NODE](Protocol):
|
|
@@ -11,7 +11,7 @@ class Visitor[NODE](Protocol):
|
|
|
11
11
|
|
|
12
12
|
__slots__ = ()
|
|
13
13
|
|
|
14
|
-
def visit(self,
|
|
14
|
+
def visit(self, node: NODE) -> None:
|
|
15
15
|
"""Visit a node of a structure."""
|
|
16
16
|
...
|
|
17
17
|
|
|
@@ -21,19 +21,19 @@ class Node[NODE](Protocol):
|
|
|
21
21
|
|
|
22
22
|
__slots__ = ()
|
|
23
23
|
|
|
24
|
-
def accept(self,
|
|
24
|
+
def accept(self, visitor: Visitor[NODE]) -> None:
|
|
25
25
|
"""Accept a visit from a `Visitor`."""
|
|
26
26
|
...
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
class
|
|
29
|
+
class StructureProxy[STRUCTURE](Protocol):
|
|
30
30
|
"""Protocol for structures exposing child relationships."""
|
|
31
31
|
|
|
32
32
|
__slots__ = ()
|
|
33
33
|
|
|
34
34
|
@classmethod
|
|
35
|
-
def children(cls,
|
|
36
|
-
"""Return the immediate children of ``
|
|
35
|
+
def children(cls, structure: STRUCTURE) -> Generator[STRUCTURE]:
|
|
36
|
+
"""Return the immediate children of ``structure``."""
|
|
37
37
|
...
|
|
38
38
|
|
|
39
39
|
|
|
@@ -43,6 +43,11 @@ class Traversal[NODE](Protocol):
|
|
|
43
43
|
__slots__ = ()
|
|
44
44
|
|
|
45
45
|
@classmethod
|
|
46
|
-
def accept(
|
|
47
|
-
|
|
46
|
+
def accept(
|
|
47
|
+
cls,
|
|
48
|
+
structure: NODE,
|
|
49
|
+
visitor: Visitor[NODE],
|
|
50
|
+
proxy: StructureProxy[NODE],
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Accept visits of a `NODE` node from a `Visitor` visitor."""
|
|
48
53
|
...
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Traversal strategies compatible with the visitor protocol."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from socx.patterns.visitor.protocol import Visitor
|
|
6
|
+
from socx.patterns.visitor.protocol import StructureProxy
|
|
7
|
+
from socx.patterns.visitor.protocol import Traversal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TopDownTraversal[NODE](Traversal[NODE]):
|
|
11
|
+
"""Pre-order traversal that visits parents before descendants."""
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def accept(
|
|
15
|
+
cls,
|
|
16
|
+
structure: NODE,
|
|
17
|
+
visitor: Visitor[NODE],
|
|
18
|
+
proxy: StructureProxy[NODE],
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Visit ``structure`` before recursively traversing its children."""
|
|
21
|
+
visitor.visit(structure)
|
|
22
|
+
|
|
23
|
+
for c in proxy.children(structure):
|
|
24
|
+
cls.accept(c, visitor, proxy)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BottomUpTraversal[NODE](Traversal[NODE]):
|
|
28
|
+
"""Post-order traversal that visits descendants before parents."""
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def accept(
|
|
32
|
+
cls,
|
|
33
|
+
structure: NODE,
|
|
34
|
+
visitor: Visitor[NODE],
|
|
35
|
+
proxy: StructureProxy[NODE],
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Traverse child subtrees prior to visiting ``structure``."""
|
|
38
|
+
for c in proxy.children(structure):
|
|
39
|
+
cls.accept(c, visitor, proxy)
|
|
40
|
+
|
|
41
|
+
visitor.visit(structure)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ByLevelTraversal[NODE](Traversal[NODE]):
|
|
45
|
+
"""Breadth-first traversal that visits nodes one level at a time."""
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def accept(
|
|
49
|
+
cls,
|
|
50
|
+
structure: NODE,
|
|
51
|
+
visitor: Visitor[NODE],
|
|
52
|
+
proxy: StructureProxy[NODE],
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Walk the structure level-by-level starting from ``structure``."""
|
|
55
|
+
q: list[NODE] = [structure]
|
|
56
|
+
|
|
57
|
+
while q:
|
|
58
|
+
t: list[NODE] = []
|
|
59
|
+
|
|
60
|
+
for n_ in q:
|
|
61
|
+
visitor.visit(n_)
|
|
62
|
+
t.extend(proxy.children(n_))
|
|
63
|
+
|
|
64
|
+
q = t
|
|
@@ -6,6 +6,7 @@ __all__ = (
|
|
|
6
6
|
"TestStatus",
|
|
7
7
|
"TestResult",
|
|
8
8
|
"Regression",
|
|
9
|
+
"RegressionTree",
|
|
9
10
|
"RegressionProgress",
|
|
10
11
|
)
|
|
11
12
|
|
|
@@ -14,4 +15,5 @@ from socx.regression.test import TestBase as TestBase
|
|
|
14
15
|
from socx.regression.test import TestStatus as TestStatus
|
|
15
16
|
from socx.regression.test import TestResult as TestResult
|
|
16
17
|
from socx.regression.regression import Regression as Regression
|
|
18
|
+
from socx.regression.regression import RegressionTree as RegressionTree
|
|
17
19
|
from socx.regression.progress import RegressionProgress as RegressionProgress
|
|
@@ -126,7 +126,7 @@ class RegressionProgress:
|
|
|
126
126
|
prev_status = status
|
|
127
127
|
prev_finished = finished
|
|
128
128
|
status = regression.status
|
|
129
|
-
finished =
|
|
129
|
+
finished = self._count_statuses(
|
|
130
130
|
regression,
|
|
131
131
|
TestStatus.Finished,
|
|
132
132
|
TestStatus.Terminated,
|
|
@@ -167,7 +167,7 @@ class RegressionProgress:
|
|
|
167
167
|
description=description,
|
|
168
168
|
)
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
def _count_statuses(
|
|
171
171
|
self, regression: Regression, *statuses: TestStatus
|
|
172
172
|
) -> int:
|
|
173
173
|
with self.regression.lock:
|
|
@@ -10,15 +10,20 @@ from collections import OrderedDict
|
|
|
10
10
|
from collections.abc import AsyncGenerator, Iterable
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from threading import RLock
|
|
13
|
-
from typing import Self, Any
|
|
13
|
+
from typing import Self, Any, Annotated
|
|
14
14
|
|
|
15
|
+
import box
|
|
15
16
|
from pydantic import (
|
|
17
|
+
Field,
|
|
16
18
|
ConfigDict,
|
|
19
|
+
BaseModel,
|
|
17
20
|
PrivateAttr,
|
|
18
21
|
TypeAdapter,
|
|
19
22
|
UUID4,
|
|
20
23
|
computed_field,
|
|
21
24
|
validate_call,
|
|
25
|
+
SerializeAsAny,
|
|
26
|
+
PlainValidator,
|
|
22
27
|
)
|
|
23
28
|
|
|
24
29
|
from socx.config import settings
|
|
@@ -28,26 +33,28 @@ from socx.regression.test import Test, TestBase, TestResult, TestStatus
|
|
|
28
33
|
|
|
29
34
|
logger = logging.getLogger(__name__)
|
|
30
35
|
|
|
36
|
+
semaphore = anyio.Semaphore(max(1, settings.regression.max_runs_in_parallel))
|
|
37
|
+
|
|
31
38
|
|
|
32
39
|
class Regression(TestBase):
|
|
33
40
|
"""Manage and execute a collection of tests with concurrency control."""
|
|
34
41
|
|
|
42
|
+
test_map: OrderedDict[UUID4, SerializeAsAny[TestBase]] = Field(
|
|
43
|
+
default_factory=OrderedDict, repr=True, title="Test Map"
|
|
44
|
+
)
|
|
35
45
|
model_config = ConfigDict(
|
|
46
|
+
title="Regression",
|
|
36
47
|
from_attributes=True,
|
|
37
48
|
arbitrary_types_allowed=True,
|
|
38
49
|
)
|
|
39
|
-
|
|
40
50
|
_lock: RLock = PrivateAttr(default_factory=RLock)
|
|
41
51
|
_done: aio.Queue[TestBase] = PrivateAttr(default_factory=aio.Queue)
|
|
42
|
-
_pending: aio.Queue[TestBase |
|
|
52
|
+
_pending: aio.Queue[TestBase | None] = PrivateAttr(
|
|
43
53
|
default_factory=aio.Queue
|
|
44
54
|
)
|
|
45
55
|
_running: set[UUID4] = PrivateAttr(default_factory=set)
|
|
46
|
-
_test_map: OrderedDict[UUID4, TestBase] = PrivateAttr(
|
|
47
|
-
default_factory=OrderedDict
|
|
48
|
-
)
|
|
49
|
-
_stop_requested: bool = PrivateAttr(default=False)
|
|
50
56
|
_pause_event: aio.Event = PrivateAttr(default_factory=aio.Event)
|
|
57
|
+
_stop_requested: bool = PrivateAttr(default=False)
|
|
51
58
|
|
|
52
59
|
def __init__(
|
|
53
60
|
self,
|
|
@@ -58,10 +65,9 @@ class Regression(TestBase):
|
|
|
58
65
|
**kwargs: Any,
|
|
59
66
|
) -> None:
|
|
60
67
|
super().__init__(*args, name=name, **kwargs)
|
|
61
|
-
tests = tests or []
|
|
62
68
|
test_map = test_map or {}
|
|
63
|
-
|
|
64
|
-
self.
|
|
69
|
+
tests = [*list(test_map.values()), *(tests or [])]
|
|
70
|
+
self.test_map = OrderedDict({test.id: test for test in tests})
|
|
65
71
|
|
|
66
72
|
@classmethod
|
|
67
73
|
@validate_call()
|
|
@@ -74,7 +80,7 @@ class Regression(TestBase):
|
|
|
74
80
|
) -> Self:
|
|
75
81
|
return cls._from_file(path, test_cls=test_cls, **kwargs)
|
|
76
82
|
|
|
77
|
-
@computed_field
|
|
83
|
+
@computed_field
|
|
78
84
|
@property
|
|
79
85
|
def result(self) -> TestResult:
|
|
80
86
|
with self.lock:
|
|
@@ -94,7 +100,7 @@ class Regression(TestBase):
|
|
|
94
100
|
else:
|
|
95
101
|
return super().result
|
|
96
102
|
|
|
97
|
-
@computed_field
|
|
103
|
+
@computed_field
|
|
98
104
|
@property
|
|
99
105
|
def status(self) -> TestStatus:
|
|
100
106
|
terminated_statuses = [
|
|
@@ -102,50 +108,36 @@ class Regression(TestBase):
|
|
|
102
108
|
TestStatus.Terminated,
|
|
103
109
|
TestStatus.Finished,
|
|
104
110
|
]
|
|
105
|
-
running_statuses = [TestStatus.
|
|
111
|
+
running_statuses = [TestStatus.Running]
|
|
106
112
|
tests = self.tests
|
|
107
113
|
if all(test.status is TestStatus.Idle for test in tests):
|
|
108
114
|
return TestStatus.Idle
|
|
109
115
|
if all(test.status is TestStatus.Finished for test in tests):
|
|
110
116
|
return TestStatus.Finished
|
|
111
|
-
if all(test.status in terminated_statuses for test in tests):
|
|
112
|
-
return TestStatus.Terminated
|
|
113
117
|
if any(test.status in running_statuses for test in tests):
|
|
114
118
|
return TestStatus.Running
|
|
115
|
-
|
|
119
|
+
if any(test.status is TestStatus.Paused for test in tests):
|
|
120
|
+
return TestStatus.Paused
|
|
121
|
+
if all(test.status in terminated_statuses for test in tests):
|
|
122
|
+
return TestStatus.Terminated
|
|
123
|
+
return TestStatus.Pending
|
|
116
124
|
|
|
117
125
|
@computed_field
|
|
118
126
|
@property
|
|
119
127
|
def tests(self) -> list[TestBase]:
|
|
120
128
|
with self.lock:
|
|
121
|
-
return list(self.
|
|
129
|
+
return list(self.test_map.values())
|
|
122
130
|
|
|
123
131
|
@tests.setter
|
|
124
132
|
def tests(self, other: list[TestBase]) -> None:
|
|
125
133
|
with self.lock:
|
|
126
|
-
self.
|
|
127
|
-
|
|
128
|
-
@computed_field(repr=True)
|
|
129
|
-
@property
|
|
130
|
-
def test_map(self) -> dict[UUID4, TestBase]:
|
|
131
|
-
with self.lock:
|
|
132
|
-
return self._test_map
|
|
133
|
-
|
|
134
|
-
@test_map.setter
|
|
135
|
-
def test_map(self, other: dict[UUID4, TestBase]) -> None:
|
|
136
|
-
with self.lock:
|
|
137
|
-
if isinstance(other, OrderedDict):
|
|
138
|
-
self._test_map = other
|
|
139
|
-
else:
|
|
140
|
-
self._test_map = OrderedDict(other)
|
|
134
|
+
self.test_map = OrderedDict({test.id: test for test in other})
|
|
141
135
|
|
|
142
|
-
@computed_field
|
|
136
|
+
@computed_field
|
|
143
137
|
@property
|
|
144
138
|
def run_limit(self) -> int:
|
|
145
139
|
"""Return the maximum number of tests that may run concurrently."""
|
|
146
|
-
|
|
147
|
-
run_limit = settings.regression.max_runs_in_parallel
|
|
148
|
-
return max(1, min(run_limit, len(self) or 1))
|
|
140
|
+
return int(max(1, int(settings.regression.max_runs_in_parallel)))
|
|
149
141
|
|
|
150
142
|
@property
|
|
151
143
|
def lock(self) -> RLock:
|
|
@@ -275,13 +267,13 @@ class Regression(TestBase):
|
|
|
275
267
|
if test.status in (TestStatus.Finished, TestStatus.Terminated):
|
|
276
268
|
test.reset()
|
|
277
269
|
await self.pending.put(test)
|
|
278
|
-
|
|
279
270
|
for _ in range(self.run_limit):
|
|
280
271
|
await self.pending.put(None)
|
|
281
272
|
|
|
282
273
|
async def _runner(self) -> None:
|
|
283
274
|
"""Consume queued tests and execute them sequentially."""
|
|
284
275
|
while True:
|
|
276
|
+
await semaphore.acquire()
|
|
285
277
|
test = await self.pending.get()
|
|
286
278
|
try:
|
|
287
279
|
if test is None:
|
|
@@ -294,13 +286,28 @@ class Regression(TestBase):
|
|
|
294
286
|
return
|
|
295
287
|
|
|
296
288
|
self._running.add(test.id)
|
|
297
|
-
|
|
298
|
-
await test.start()
|
|
289
|
+
await test.start()
|
|
299
290
|
await self.done.put(test)
|
|
300
291
|
finally:
|
|
301
292
|
if test is not None:
|
|
302
293
|
self._running.discard(test.id)
|
|
303
294
|
self.pending.task_done()
|
|
295
|
+
semaphore.release()
|
|
296
|
+
|
|
297
|
+
def dump_state(self, output_dir: Path) -> None:
|
|
298
|
+
"""Write the regression command results to their respective files."""
|
|
299
|
+
logger.info("saving regression state and results to disk...")
|
|
300
|
+
file = output_dir / self.name / "state.yaml"
|
|
301
|
+
state = self.model_dump(
|
|
302
|
+
mode="json",
|
|
303
|
+
round_trip=True,
|
|
304
|
+
serialize_as_any=True,
|
|
305
|
+
include={"result", "status"},
|
|
306
|
+
)
|
|
307
|
+
file.parent.mkdir(parents=True, exist_ok=True)
|
|
308
|
+
file.touch(exist_ok=False)
|
|
309
|
+
box.DDBox(state).to_yaml(str(file))
|
|
310
|
+
logger.info(f"state and results saved to: '{output_dir}'.")
|
|
304
311
|
|
|
305
312
|
def _active_tests(self) -> list[TestBase]:
|
|
306
313
|
return [test for test in self.tests if test.id in self._running]
|
|
@@ -359,26 +366,42 @@ class Regression(TestBase):
|
|
|
359
366
|
regressions.append(regression)
|
|
360
367
|
return cls(name=name, tests=regressions)
|
|
361
368
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
369
|
+
|
|
370
|
+
TreeNode = Annotated[
|
|
371
|
+
TestBase,
|
|
372
|
+
PlainValidator(lambda x: x, TestBase),
|
|
373
|
+
SerializeAsAny[Test | Regression],
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class RegressionTree(BaseModel):
|
|
378
|
+
root_node: TreeNode = Field(..., title="Root Node", repr=True)
|
|
379
|
+
model_config = ConfigDict(
|
|
380
|
+
from_attributes=True,
|
|
381
|
+
arbitrary_types_allowed=True,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
# @field_validator("test_map", mode="before")
|
|
386
|
+
# @classmethod
|
|
387
|
+
# def _test_map_validator(
|
|
388
|
+
# cls,
|
|
389
|
+
# tests: set[TestBase]
|
|
390
|
+
# | list[TestBase]
|
|
391
|
+
# | tuple[TestBase, ...]
|
|
392
|
+
# | dict[str, TestBase],
|
|
393
|
+
# ) -> OrderedDict[UUID4, TestBase]:
|
|
394
|
+
# if tests is None:
|
|
395
|
+
# err = "must not be none"
|
|
396
|
+
# raise ValueError(err)
|
|
397
|
+
|
|
398
|
+
# rv = OrderedDict()
|
|
399
|
+
# it: Iterator[TestBase] = (
|
|
400
|
+
# iter(list(tests.values()))
|
|
401
|
+
# if isinstance(tests, dict)
|
|
402
|
+
# else iter(tests)
|
|
403
|
+
# )
|
|
404
|
+
|
|
405
|
+
# for test in it:
|
|
406
|
+
# rv[test.id] = test
|
|
407
|
+
# return rv
|