socx-cli 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,129 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TextIO
4
+ from pathlib import Path
5
+ from subprocess import Popen
6
+ from dataclasses import dataclass
7
+
8
+ from dynaconf.utils.boxing import DynaBox
9
+
10
+ # from .. import visitor
11
+ from ..config import settings
12
+ from .test import Test
13
+
14
+
15
+ __all__ = ("Status", "Regression")
16
+
17
+
18
+ from .test import Status as Status
19
+
20
+
21
+ @dataclass(init=False)
22
+ class Regression:
23
+ uid: int
24
+ name: str
25
+ log: Path
26
+ tests: list[Test]
27
+ status: list[Status]
28
+ options: DynaBox
29
+ test_logs: list[Path]
30
+
31
+ def start(self) -> None:
32
+ """Start a test in a subprocess."""
33
+ pass
34
+
35
+ def stop(self) -> None:
36
+ """Send a keyboard interrupt (SIGINT) to stop a running test."""
37
+ pass
38
+
39
+ def pause(self) -> None:
40
+ """Pause the process if it is running."""
41
+ pass
42
+
43
+ def resume(self) -> None:
44
+ """Resume the process if it is paused."""
45
+ pass
46
+
47
+ def wait(self, timeout: float | None = None) -> None:
48
+ """Wait for a test to terminate if it is running."""
49
+ pass
50
+
51
+ def kill(self) -> None:
52
+ """See `subprocess.Popen.kill`."""
53
+ pass
54
+
55
+ def export(self, fmt: str = "csv") -> None:
56
+ """Export regression sessions to the specified format `fmt`."""
57
+ pass
58
+
59
+ def terminate(self) -> None:
60
+ """See `subprocess.Popen.terminate`."""
61
+ pass
62
+
63
+ @property
64
+ def log(self) -> Path:
65
+ pass
66
+
67
+ @property
68
+ def options(self) -> DynaBox:
69
+ return settings.regression
70
+
71
+ @property
72
+ def process(self) -> Popen | None:
73
+ """The active process of the running test or None if not running."""
74
+ pass
75
+
76
+ @property
77
+ def stdin(self) -> TextIO | None:
78
+ """The standard input of the test's process or None if not running."""
79
+ pass
80
+
81
+ @property
82
+ def stdout(self) -> TextIO | None:
83
+ """The standard output of the test's process or None if not running."""
84
+ pass
85
+
86
+ @property
87
+ def stderr(self) -> TextIO | None:
88
+ """The standard error of the test's process or None if not running."""
89
+ pass
90
+
91
+ @property
92
+ def status(self) -> Status:
93
+ """A `TestStatus` representing the state/status of the test."""
94
+ pass
95
+
96
+ @property
97
+ def test_logs(self) -> dict[str, Path]:
98
+ """Return a mapping between test names and their log paths."""
99
+ pass
100
+
101
+ @property
102
+ def idle(self) -> bool:
103
+ """True if test has no active process and has not yet started."""
104
+ pass
105
+
106
+ @property
107
+ def passed(self) -> bool:
108
+ """True if test has finished running and no errors occured."""
109
+ pass
110
+
111
+ @property
112
+ def failed(self) -> bool:
113
+ """True if test finished running and at least one error occured."""
114
+ pass
115
+
116
+ @property
117
+ def running(self) -> bool:
118
+ """True if test is currently running in a dedicated process."""
119
+ pass
120
+
121
+ @property
122
+ def finished(self) -> bool:
123
+ """True if test finished running without normally interruption."""
124
+ pass
125
+
126
+ @property
127
+ def returncode(self) -> int | None:
128
+ """The return code from the test process or None if running or idle."""
129
+ pass
@@ -0,0 +1,244 @@
1
+ from __future__ import annotations
2
+
3
+ import signal
4
+ from contextlib import suppress
5
+ from typing import TextIO
6
+ from enum import auto
7
+ from enum import IntEnum
8
+ from subprocess import Popen
9
+ from subprocess import PIPE
10
+ from dataclasses import field
11
+ from dataclasses import dataclass
12
+
13
+
14
+ class Status(IntEnum):
15
+ """
16
+ Status representation of a test process as an `IntEnum`.
17
+
18
+ Members
19
+ -------
20
+ Idle: IntEnum
21
+ Idle state, awaiting to be started.
22
+
23
+ Running: IntEnum
24
+ Test is currently running on a seperate process.
25
+
26
+ Passed: IntEnum
27
+ Test has ended with no errors and a clean exit code.
28
+
29
+ Failed: IntEnum
30
+ Test had failed due to an error or an invalid exit code.
31
+
32
+ Killed: IntEnum
33
+ Test was intentionally killed by a signal.
34
+ """
35
+
36
+ Idle = auto(0)
37
+ Running = auto()
38
+ Passed = auto()
39
+ Failed = auto()
40
+ Killed = auto()
41
+
42
+
43
+ @dataclass
44
+ class Command:
45
+ """
46
+ Representation of a 'run test' command-line as an object.
47
+
48
+ Members
49
+ -------
50
+ line: str
51
+ Full commandline string of the command represented by this object.
52
+
53
+ name: str
54
+ Name of the command represented by this object, i.e. sys.argv[0].
55
+
56
+ args: list[str]
57
+ Arguments of the command represented by this object split by
58
+ whitespace.
59
+ """
60
+
61
+ name: str = field(init=False)
62
+ args: list[str] = field(init=False)
63
+ line: str
64
+
65
+ def __getattr__(self, attr: str) -> str:
66
+ v = self.extract_argv(f"--{attr}")
67
+ if v:
68
+ return v
69
+ else:
70
+ raise AttributeError
71
+
72
+ def extract_argv(self, arg: str) -> str:
73
+ with suppress(ValueError):
74
+ index = self.args.index(f"{arg}")
75
+ return self.args[index + 1] if index + 1 < len(self.args) else ""
76
+ return ""
77
+
78
+ def __post_init__(self) -> None:
79
+ self.line = self.line.strip()
80
+ self.args = [arg.strip() for arg in self.line.split()]
81
+ self.name = self.args[0] if self.args else ""
82
+
83
+ def __hash__(self) -> int:
84
+ return hash(self.line)
85
+
86
+
87
+ @dataclass(init=False)
88
+ class Test:
89
+ """Representation of a test as a python object."""
90
+
91
+ name: str
92
+ flow: str
93
+ seed: int
94
+ status: Status
95
+ command: Command
96
+ process: Popen | None
97
+
98
+ def __init__(self, command: str | Command) -> None:
99
+ self._proc = None
100
+ self._command = (
101
+ command
102
+ if isinstance(command, Command)
103
+ else Command(command)
104
+ )
105
+
106
+ @property
107
+ def pid(self) -> int | None:
108
+ """Process id of the test's active process or None if inactive."""
109
+ return self.process.pid if self.process else None
110
+
111
+ @property
112
+ def name(self) -> str:
113
+ """The test name or empty string if command does not specify a test."""
114
+ test = self.command.test
115
+ if "/" in test:
116
+ test = test.partition("/")[-1]
117
+ return test
118
+
119
+ @property
120
+ def flow(self):
121
+ """The flow specified in the test command."""
122
+ return self.command.flow
123
+
124
+ @property
125
+ def seed(self):
126
+ """The test seed."""
127
+ with suppress(AttributeError):
128
+ rv = int(self.command.seed)
129
+ return rv
130
+ return 0
131
+
132
+ def start(self) -> None:
133
+ """Start a test in a subprocess."""
134
+ if self.idle:
135
+ self._proc = Popen(
136
+ args=self.command.args,
137
+ text=True,
138
+ stdout=PIPE,
139
+ stderr=PIPE,
140
+ shell=True,
141
+ )
142
+ else:
143
+ err = "Test is already running."
144
+ raise OSError(err)
145
+
146
+ def stop(self) -> None:
147
+ """Send a keyboard interrupt (SIGINT) to stop a running test."""
148
+ if self.running:
149
+ self.process.send_signal(signal.SIGINT)
150
+
151
+ def pause(self) -> None:
152
+ """Pause the process if it is running."""
153
+ if self.running:
154
+ self.process.send_signal(signal.SIGSTOP)
155
+
156
+ def resume(self) -> None:
157
+ """Resume the process if it is paused."""
158
+ if self.running:
159
+ self.process.send_signal(signal.SIGCONT)
160
+
161
+ def wait(self, timeout: float | None = None) -> None:
162
+ """Wait for a test to terminate if it is running."""
163
+ if self.running:
164
+ self.process.wait()
165
+
166
+ def kill(self) -> None:
167
+ """See `subprocess.Popen.kill`."""
168
+ if self.running:
169
+ self.process.kill()
170
+
171
+ def terminate(self) -> None:
172
+ """See `subprocess.Popen.terminate`."""
173
+ if self.running:
174
+ self.process.terminate()
175
+
176
+ @property
177
+ def process(self) -> Popen | None:
178
+ """The active process of the running test or None if not running."""
179
+ return self._proc
180
+
181
+ @property
182
+ def stdin(self) -> TextIO | None:
183
+ """The standard input of the test's process or None if not running."""
184
+ return self.process.stdin if self.process else None
185
+
186
+ @property
187
+ def stdout(self) -> TextIO | None:
188
+ """The standard output of the test's process or None if not running."""
189
+ return self.process.stdout if self.process else None
190
+
191
+ @property
192
+ def stderr(self) -> TextIO | None:
193
+ """The standard error of the test's process or None if not running."""
194
+ return self.process.stderr if self.process else None
195
+
196
+ @property
197
+ def status(self) -> Status:
198
+ """A `TestStatus` representing the state/status of the test."""
199
+ return self._status()
200
+
201
+ @property
202
+ def command(self) -> Command:
203
+ """A `TestCommand` represeinting the test's command line invocation."""
204
+ return self._command
205
+
206
+ @property
207
+ def returncode(self) -> int | None:
208
+ """The return code from the test process or None if running or idle."""
209
+ return self.process.returncode if self.finished else None
210
+
211
+ @property
212
+ def idle(self) -> bool:
213
+ """True if test has no active process and has not yet started."""
214
+ return self.status is Status.Idle
215
+
216
+ @property
217
+ def passed(self) -> bool:
218
+ """True if test has finished running and no errors occured."""
219
+ return not self.finished and self.status is Status.Passed
220
+
221
+ @property
222
+ def failed(self) -> bool:
223
+ """True if test finished running and at least one error occured."""
224
+ return self.finished and self.status is Status.Failed
225
+
226
+ @property
227
+ def running(self) -> bool:
228
+ """True if test is currently running in a dedicated process."""
229
+ return self.process is not None and self.process.poll() is None
230
+
231
+ @property
232
+ def finished(self) -> bool:
233
+ """True if test finished running without normally interruption."""
234
+ return self.process is not None and self.process.poll() is not None
235
+
236
+ def _status(self) -> Status:
237
+ if self.process is None:
238
+ return Status.Idle
239
+ elif self.stdout.strip().startswith("PPPP"):
240
+ return Status.Passed
241
+ elif self.stdout.strip().startswith("FFFF"):
242
+ return Status.Running
243
+ else:
244
+ return Status.Failed
@@ -0,0 +1,2 @@
1
+ # convert__lst__source = "@path @format {env[TOP_VERIF]}/assets/inputs"
2
+ convert__lst__target = "@path @format {env[TOP_VERIF]}/scripts/socx/assets/lst/outputs"
@@ -0,0 +1,119 @@
1
+ [convert]
2
+
3
+
4
+ [convert.lst]
5
+
6
+ # Argument: type
7
+ #
8
+ # Typename of the convert class for performing the conversion.
9
+ #
10
+ type = "LstParser"
11
+
12
+ # Argument: source
13
+ #
14
+ # source directory from which names and patterns defined in include will
15
+ # be searched.
16
+ #
17
+ source = """
18
+ @path @format {env[WAREA]}/Pixie_ROM_FW/PixieROMApp/TapeOutRelease/PIXIE_E4/PxieParserOut
19
+ """
20
+
21
+ # Argument: target
22
+ #
23
+ # target directory to which generated outputs will be written.
24
+ #
25
+ target = """
26
+ @path @format {env[TOP_VERIF]}/scripts/socx/assets/lst/outputs
27
+ """
28
+
29
+ # Argument: base_addr_map
30
+ #
31
+ # Name of the json adress map file of ROM base addresses.
32
+ #
33
+ base_addr_map = "memLd.json"
34
+
35
+ # Argument: base_addr_base
36
+ #
37
+ # The address value's numeric base (hex/octal/decimal/etc.)
38
+ #
39
+ base_addr_base = 16
40
+
41
+ # Argument: include
42
+ #
43
+ # Specifies file names to include in the convert's list of sources to
44
+ # convert.
45
+ #
46
+ # Values can either be an exact filename, a glob pattern, or a
47
+ # combination of both.
48
+ #
49
+ includes = [ "*.lst", "SPUList.list" ]
50
+
51
+ # Argument: exclude
52
+ #
53
+ # Same as include, but excludes files from the list of sources.
54
+ #
55
+ excludes = []
56
+
57
+ # Argument: mappings
58
+ #
59
+ # Mappings between source file names to target file names.
60
+ #
61
+ # Both source and target can either be an exact file name or a
62
+ # 'match' pattern specified by the character %.
63
+ #
64
+ # A match pattern will simply match the missing part of the target's name with
65
+ # the matching missing part in the source name.
66
+ #
67
+ mappings = [
68
+ {input="%.lst", output="%.svh"},
69
+ {input="%.list", output="%.sv"},
70
+ {input="hwsList.lst", output="pixie_hws_cgs.svh"},
71
+ ]
72
+
73
+ # -----------------------------------------------------------------------------
74
+ # Path Options
75
+ # -----------------------------------------------------------------------------
76
+ #
77
+ [convert.lst.options]
78
+
79
+ # Option: collision
80
+ #
81
+ # How name collisions should be handled. In other words, what should the
82
+ # convert do when an older version of the output file already exists
83
+ # in the target directory.
84
+ #
85
+ # Options
86
+ # -------
87
+ # skip:
88
+ # filename collisions will be skipped, and the original file is
89
+ # kept.
90
+ #
91
+ # backup:
92
+ # same as overwrite, but creates a copy of the original file with
93
+ # with a '.backup.<timestamp>' file where <timestamp> is replaced
94
+ # with the date and time of when the back up was created.
95
+ #
96
+ # overwrite:
97
+ # filename collisions are handled by overwriting any existing file
98
+ # with the new one upon name collision.
99
+ #
100
+ collision = "overwrite"
101
+
102
+
103
+ # -------------------------------------------------
104
+ # Uncomment below to add another path configuration
105
+ # -------------------------------------------------
106
+ #
107
+ # [[convert]]
108
+ # source = """
109
+ # """
110
+ # target = """
111
+ # """
112
+ # include = []
113
+ # exclude = []
114
+ # mappings = [{}]
115
+ #
116
+ # [convert.options]
117
+ #
118
+ # ...
119
+ #
@@ -0,0 +1,104 @@
1
+ [lang]
2
+
3
+ # -----------------------------------------------------------------------------
4
+ # Category: Filetype
5
+ # -----------------------------------------------------------------------------
6
+ #
7
+ # An array of language element tables where each table specifies a small
8
+ # subset of language features.
9
+ #
10
+ # Each language is identified by name, and is linked by the configured set of
11
+ # possible filetype extensions.
12
+ #
13
+ [lang.systemverilog]
14
+ name = "systemverilog"
15
+ extensions = [".sv", ".svh"]
16
+
17
+
18
+ # -----------------------------------------------------------------------------
19
+ # Group: Syntax
20
+ # -----------------------------------------------------------------------------
21
+ #
22
+ # Provides example definitions of various lexical language options
23
+ # supported through configuration.
24
+ #
25
+ [lang.systemverilog.syntax]
26
+
27
+ # Option: filetype.indent
28
+ #
29
+ # 4 or 2, these are your options.
30
+ #
31
+ # It has to be a multiple of 2. always has been. enough with the 3
32
+ # spaces madness.
33
+ #
34
+ # 3 doesnt even make any sense! "meh.. but UVM is so big and it also uses 3
35
+ # spaces and i read online tha.." NOOOOO! Shut up! I dont care!
36
+ #
37
+ # 4 or 2! thse are your only options! from now on the mad 3 people get
38
+ # an angry Exception with a recommendation to see a psychologist.
39
+ #
40
+ indent = 4
41
+
42
+
43
+ [lang.systemverilog.syntax.comments]
44
+
45
+ # Option: filetype.syntax.comments.line
46
+ #
47
+ # Line comment tokens
48
+ #
49
+ line = "//"
50
+
51
+ # Option: filetype.syntax.comments.block
52
+ #
53
+ # Block comment token specification.
54
+ #
55
+ # Attributes
56
+ # ----------
57
+ # begin:
58
+ # The token that starts the block comment.
59
+ #
60
+ # end:
61
+ # The token that ends the block comment.
62
+ #
63
+ block = {begin="/*", end="*/"}
64
+
65
+
66
+ [lang.lst]
67
+ name = "lst"
68
+ extensions = [".lst", ".list"]
69
+
70
+
71
+ [[lang.lst.tokens]]
72
+ name = 'comment'
73
+ expr = '([#])(?!([A-Z]{2,}))(?P<content>.*)'
74
+ subst = '''@format {this.lang.systemverilog.syntax.comments.line} \g<content>
75
+ '''
76
+ starts_scope = false
77
+
78
+
79
+ [[lang.lst.tokens]]
80
+ name = 'group'
81
+ expr = '([#])(?P<mem>NVM|ROM)(\W+)(?P<device>[A-Z]{2,})'
82
+ subst = '''
83
+ covergroup cg__\g<device>_\g<mem>_access;
84
+
85
+ cp__\g<device>_\g<mem>_func: coverpoint { vif.\g<device>_\g<mem>_addr }
86
+ {
87
+ '''
88
+ starts_scope = true
89
+ scope_ender = '''
90
+ bins NoFunc = default;
91
+ }
92
+ endgroup: cg__\g<device>_\g<mem>_access
93
+
94
+ '''
95
+
96
+
97
+ [[lang.lst.tokens]]
98
+ name = 'statement'
99
+ expr='0x(?P<addr>[0-9a-fA-F]+)\W+(?P<func>\w+)\W+(?P<len>\d+)(?P<other>.*)'
100
+ subst = '''
101
+ bins \g<func> = { [ (('h\g<addr> - 'h{{ base }}) >> 1) : ((('h\g<addr> - 'h{{base}} + 'd\g<len>) >> 1) - 1) ] };
102
+ '''
103
+ starts_scope = false
104
+
@@ -0,0 +1,5 @@
1
+ [plugins]
2
+ rgr = "@path @format src/plugins/rgr.py"
3
+ config = "@path @format src/plugins/config.py"
4
+ example = "@path @format src/plugins/example.py"
5
+ convert = "@path @format src/plugins/convert/lst.py"
@@ -0,0 +1,47 @@
1
+ [regression]
2
+ root = "@path @format /space/users/ci_wiliot/vw_e0_nightly_regression/regressions"
3
+
4
+ [regression.user.options]
5
+ test_timeout = 7200
6
+ max_runs_in_parallel = 10
7
+
8
+ [regression.user.options.logs]
9
+ path = "@format {env[RAREA]}/regressions"
10
+ enabled = true
11
+
12
+ [regression.logs]
13
+ path = "@path @format {this.regression.root}/logs"
14
+ prev = "@path @format {this.regression.logs.path}/prev"
15
+ latest = "@path @format {this.regression.logs.path}/latest"
16
+
17
+ [regression.nightly]
18
+ path = "@path @format {this.regression.root}/nightly"
19
+
20
+ [regression.files.socrgr_db]
21
+ name = "socRgrDB.pyclass"
22
+ parent = "@path @format {this.regression.nightly}"
23
+
24
+ [regression.files.rerun_cfg]
25
+ name = "rerun_list.cfg"
26
+ parent = "@path @format {this.regression.logs.latest}"
27
+
28
+ [regression.files.test_summary]
29
+ name = "regression_summary.log"
30
+ parent = "@path @format {this.regression.logs.latest}"
31
+ columns = [
32
+ {name="test_name", index=0, type="path"},
33
+ {name="Iter_num", index=1, type="int"},
34
+ {name="testStaus", index=2, type="str"},
35
+ {name="NumErrors", index=3, type="int"},
36
+ {name="SimTime[ns]", index=4, type="time"},
37
+ {name="seed", index=5, type="int"},
38
+ ]
39
+
40
+ [regression.files.error_summary]
41
+ name = "errors_summary.log"
42
+ parent = "@path @format {this.regression.logs.latest}"
43
+
44
+ [regression.files.failure_history]
45
+ name = "failed_tests_history.log"
46
+ parent = "@path @format {this.regression.logs.latest}"
47
+
@@ -0,0 +1,6 @@
1
+ dynaconf_include = [
2
+ "plugins.toml",
3
+ "filetypes.toml",
4
+ "converter.toml",
5
+ "regression.toml",
6
+ ]
@@ -0,0 +1,9 @@
1
+ [test]
2
+ key = ''
3
+ index = ''
4
+
5
+ [[tests]]
6
+ name = ''
7
+ seed = ''
8
+ time = {start = '', stop = ''}
9
+ status = ''