cocotb 2.0.1__cp38-cp38-win32.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.
Files changed (152) hide show
  1. cocotb/_ANSI.py +65 -0
  2. cocotb/__init__.py +127 -0
  3. cocotb/_base_triggers.py +515 -0
  4. cocotb/_bridge.py +186 -0
  5. cocotb/_decorators.py +515 -0
  6. cocotb/_deprecation.py +36 -0
  7. cocotb/_exceptions.py +7 -0
  8. cocotb/_extended_awaitables.py +419 -0
  9. cocotb/_gpi_triggers.py +385 -0
  10. cocotb/_init.py +301 -0
  11. cocotb/_outcomes.py +54 -0
  12. cocotb/_profiling.py +46 -0
  13. cocotb/_py_compat.py +150 -0
  14. cocotb/_scheduler.py +448 -0
  15. cocotb/_test.py +248 -0
  16. cocotb/_test_factory.py +312 -0
  17. cocotb/_test_functions.py +42 -0
  18. cocotb/_typing.py +7 -0
  19. cocotb/_utils.py +274 -0
  20. cocotb/_version.py +4 -0
  21. cocotb/_xunit_reporter.py +103 -0
  22. cocotb/clock.py +419 -0
  23. cocotb/debug.py +24 -0
  24. cocotb/handle.py +1752 -0
  25. cocotb/libs/cocotb.dll +0 -0
  26. cocotb/libs/cocotb.exp +0 -0
  27. cocotb/libs/cocotb.lib +0 -0
  28. cocotb/libs/cocotbfli_modelsim.dll +0 -0
  29. cocotb/libs/cocotbfli_modelsim.exp +0 -0
  30. cocotb/libs/cocotbfli_modelsim.lib +0 -0
  31. cocotb/libs/cocotbutils.dll +0 -0
  32. cocotb/libs/cocotbutils.exp +0 -0
  33. cocotb/libs/cocotbutils.lib +0 -0
  34. cocotb/libs/cocotbvhpi_aldec.dll +0 -0
  35. cocotb/libs/cocotbvhpi_aldec.exp +0 -0
  36. cocotb/libs/cocotbvhpi_aldec.lib +0 -0
  37. cocotb/libs/cocotbvhpi_modelsim.dll +0 -0
  38. cocotb/libs/cocotbvhpi_modelsim.exp +0 -0
  39. cocotb/libs/cocotbvhpi_modelsim.lib +0 -0
  40. cocotb/libs/cocotbvhpi_nvc.dll +0 -0
  41. cocotb/libs/cocotbvhpi_nvc.exp +0 -0
  42. cocotb/libs/cocotbvhpi_nvc.lib +0 -0
  43. cocotb/libs/cocotbvpi_aldec.dll +0 -0
  44. cocotb/libs/cocotbvpi_aldec.exp +0 -0
  45. cocotb/libs/cocotbvpi_aldec.lib +0 -0
  46. cocotb/libs/cocotbvpi_ghdl.dll +0 -0
  47. cocotb/libs/cocotbvpi_ghdl.exp +0 -0
  48. cocotb/libs/cocotbvpi_ghdl.lib +0 -0
  49. cocotb/libs/cocotbvpi_icarus.exp +0 -0
  50. cocotb/libs/cocotbvpi_icarus.lib +0 -0
  51. cocotb/libs/cocotbvpi_icarus.vpl +0 -0
  52. cocotb/libs/cocotbvpi_modelsim.dll +0 -0
  53. cocotb/libs/cocotbvpi_modelsim.exp +0 -0
  54. cocotb/libs/cocotbvpi_modelsim.lib +0 -0
  55. cocotb/libs/embed.dll +0 -0
  56. cocotb/libs/embed.exp +0 -0
  57. cocotb/libs/embed.lib +0 -0
  58. cocotb/libs/gpi.dll +0 -0
  59. cocotb/libs/gpi.exp +0 -0
  60. cocotb/libs/gpi.lib +0 -0
  61. cocotb/libs/gpilog.dll +0 -0
  62. cocotb/libs/gpilog.exp +0 -0
  63. cocotb/libs/gpilog.lib +0 -0
  64. cocotb/libs/pygpilog.dll +0 -0
  65. cocotb/libs/pygpilog.exp +0 -0
  66. cocotb/libs/pygpilog.lib +0 -0
  67. cocotb/logging.py +417 -0
  68. cocotb/py.typed +0 -0
  69. cocotb/queue.py +235 -0
  70. cocotb/regression.py +900 -0
  71. cocotb/result.py +38 -0
  72. cocotb/share/def/.gitignore +2 -0
  73. cocotb/share/def/README.md +4 -0
  74. cocotb/share/def/aldec.def +61 -0
  75. cocotb/share/def/aldec.exp +0 -0
  76. cocotb/share/def/aldec.lib +0 -0
  77. cocotb/share/def/ghdl.def +43 -0
  78. cocotb/share/def/ghdl.exp +0 -0
  79. cocotb/share/def/ghdl.lib +0 -0
  80. cocotb/share/def/icarus.def +43 -0
  81. cocotb/share/def/icarus.exp +0 -0
  82. cocotb/share/def/icarus.lib +0 -0
  83. cocotb/share/def/modelsim.def +138 -0
  84. cocotb/share/def/modelsim.exp +0 -0
  85. cocotb/share/def/modelsim.lib +0 -0
  86. cocotb/share/def/nvcvhpi.def +18 -0
  87. cocotb/share/def/nvcvhpi.exp +0 -0
  88. cocotb/share/def/nvcvhpi.lib +0 -0
  89. cocotb/share/include/cocotb_utils.h +70 -0
  90. cocotb/share/include/embed.h +33 -0
  91. cocotb/share/include/exports.h +20 -0
  92. cocotb/share/include/gpi.h +459 -0
  93. cocotb/share/include/gpi_logging.h +291 -0
  94. cocotb/share/include/py_gpi_logging.h +33 -0
  95. cocotb/share/include/vhpi_user_ext.h +26 -0
  96. cocotb/share/include/vpi_user_ext.h +33 -0
  97. cocotb/share/lib/verilator/verilator.cpp +209 -0
  98. cocotb/simtime.py +238 -0
  99. cocotb/simulator.cp38-win32.exp +0 -0
  100. cocotb/simulator.cp38-win32.lib +0 -0
  101. cocotb/simulator.cp38-win32.pyd +0 -0
  102. cocotb/simulator.cp38-win32.pyd.2.config +8 -0
  103. cocotb/simulator.pyi +107 -0
  104. cocotb/task.py +590 -0
  105. cocotb/triggers.py +67 -0
  106. cocotb/types/__init__.py +31 -0
  107. cocotb/types/_abstract_array.py +151 -0
  108. cocotb/types/_array.py +297 -0
  109. cocotb/types/_indexing.py +17 -0
  110. cocotb/types/_logic.py +333 -0
  111. cocotb/types/_logic_array.py +884 -0
  112. cocotb/types/_range.py +197 -0
  113. cocotb/types/_resolve.py +76 -0
  114. cocotb/utils.py +110 -0
  115. cocotb-2.0.1.dist-info/LICENSE +29 -0
  116. cocotb-2.0.1.dist-info/METADATA +44 -0
  117. cocotb-2.0.1.dist-info/RECORD +152 -0
  118. cocotb-2.0.1.dist-info/WHEEL +5 -0
  119. cocotb-2.0.1.dist-info/entry_points.txt +2 -0
  120. cocotb-2.0.1.dist-info/top_level.txt +18 -0
  121. cocotb_tools/__init__.py +0 -0
  122. cocotb_tools/_coverage.py +33 -0
  123. cocotb_tools/_vendor/__init__.py +3 -0
  124. cocotb_tools/_vendor/distutils_version.py +346 -0
  125. cocotb_tools/check_results.py +65 -0
  126. cocotb_tools/combine_results.py +152 -0
  127. cocotb_tools/config.py +242 -0
  128. cocotb_tools/ipython_support.py +99 -0
  129. cocotb_tools/makefiles/Makefile.deprecations +27 -0
  130. cocotb_tools/makefiles/Makefile.inc +198 -0
  131. cocotb_tools/makefiles/Makefile.sim +96 -0
  132. cocotb_tools/makefiles/simulators/Makefile.activehdl +72 -0
  133. cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
  134. cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
  135. cocotb_tools/makefiles/simulators/Makefile.ghdl +84 -0
  136. cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
  137. cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
  138. cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
  139. cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
  140. cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
  141. cocotb_tools/makefiles/simulators/Makefile.questa-compat +143 -0
  142. cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
  143. cocotb_tools/makefiles/simulators/Makefile.riviera +144 -0
  144. cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
  145. cocotb_tools/makefiles/simulators/Makefile.verilator +79 -0
  146. cocotb_tools/makefiles/simulators/Makefile.xcelium +104 -0
  147. cocotb_tools/py.typed +0 -0
  148. cocotb_tools/runner.py +2001 -0
  149. cocotb_tools/sim_versions.py +140 -0
  150. pygpi/__init__.py +0 -0
  151. pygpi/entry.py +42 -0
  152. pygpi/py.typed +0 -0
cocotb/regression.py ADDED
@@ -0,0 +1,900 @@
1
+ # Copyright cocotb contributors
2
+ # Copyright (c) 2013, 2018 Potential Ventures Ltd
3
+ # Copyright (c) 2013 SolarFlare Communications Inc
4
+ # Licensed under the Revised BSD License, see LICENSE for details.
5
+ # SPDX-License-Identifier: BSD-3-Clause
6
+
7
+ """All things relating to regression capabilities."""
8
+
9
+ import functools
10
+ import hashlib
11
+ import inspect
12
+ import logging
13
+ import os
14
+ import random
15
+ import re
16
+ import time
17
+ import warnings
18
+ from enum import auto
19
+ from importlib import import_module
20
+ from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Union
21
+
22
+ import cocotb
23
+ import cocotb._gpi_triggers
24
+ import cocotb.handle
25
+ from cocotb import logging as cocotb_logging
26
+ from cocotb import simulator
27
+ from cocotb._decorators import Parameterized, Test
28
+ from cocotb._extended_awaitables import with_timeout
29
+ from cocotb._gpi_triggers import GPITrigger, Timer
30
+ from cocotb._outcomes import Error, Outcome
31
+ from cocotb._test import RunningTest
32
+ from cocotb._test_factory import TestFactory
33
+ from cocotb._test_functions import Failed
34
+ from cocotb._utils import (
35
+ DocEnum,
36
+ remove_traceback_frames,
37
+ safe_divide,
38
+ )
39
+ from cocotb._xunit_reporter import XUnitReporter, bin_xml_escape
40
+ from cocotb.logging import ANSI
41
+ from cocotb.simtime import get_sim_time
42
+ from cocotb.task import Task
43
+
44
+ if TYPE_CHECKING:
45
+ from cocotb._base_triggers import Trigger
46
+
47
+ __all__ = (
48
+ "Parameterized",
49
+ "RegressionManager",
50
+ "RegressionMode",
51
+ "SimFailure",
52
+ "Test",
53
+ "TestFactory",
54
+ )
55
+
56
+ # Set __module__ on re-exports
57
+ Parameterized.__module__ = __name__
58
+ Test.__module__ = __name__
59
+ TestFactory.__module__ = __name__
60
+
61
+
62
+ class SimFailure(BaseException):
63
+ """A Test failure due to simulator failure.
64
+
65
+ .. caution::
66
+ Not to be raised or caught within a test.
67
+ Only used for marking expected failure with ``expect_error`` in :func:`cocotb.test`.
68
+ """
69
+
70
+
71
+ _logger = logging.getLogger(__name__)
72
+
73
+
74
+ def _format_doc(docstring: Union[str, None]) -> str:
75
+ if docstring is None:
76
+ return ""
77
+ else:
78
+ brief = docstring.split("\n")[0]
79
+ return f"\n {brief}"
80
+
81
+
82
+ class RegressionMode(DocEnum):
83
+ """The mode of the :class:`RegressionManager`."""
84
+
85
+ REGRESSION = (
86
+ auto(),
87
+ """Tests are run if included. Skipped tests are skipped, expected failures and errors are respected.""",
88
+ )
89
+
90
+ TESTCASE = (
91
+ auto(),
92
+ """Like :attr:`REGRESSION`, but skipped tests are *not* skipped if included.""",
93
+ )
94
+
95
+
96
+ class _TestResults:
97
+ # TODO Replace with dataclass in Python 3.7+
98
+
99
+ def __init__(
100
+ self,
101
+ test_fullname: str,
102
+ passed: Union[None, bool],
103
+ wall_time_s: float,
104
+ sim_time_ns: float,
105
+ ) -> None:
106
+ self.test_fullname = test_fullname
107
+ self.passed = passed
108
+ self.wall_time_s = wall_time_s
109
+ self.sim_time_ns = sim_time_ns
110
+
111
+ @property
112
+ def ratio(self) -> float:
113
+ return safe_divide(self.sim_time_ns, self.wall_time_s)
114
+
115
+
116
+ class RegressionManager:
117
+ """Object which manages tests.
118
+
119
+ This object uses the builder pattern to build up a regression.
120
+ Tests are added using :meth:`register_test` or :meth:`discover_tests`.
121
+ Inclusion filters for tests can be added using :meth:`add_filters`.
122
+ The "mode" of the regression can be controlled using :meth:`set_mode`.
123
+ These methods can be called in any order any number of times before :meth:`start_regression` is called,
124
+ and should not be called again after that.
125
+
126
+ Once all the tests, filters, and regression behavior configuration is done,
127
+ the user starts the regression with :meth:`start_regression`.
128
+ This method must be called exactly once.
129
+
130
+ Until the regression is started, :attr:`total_tests`, :attr:`count`, :attr:`passed`,
131
+ :attr:`skipped`, and :attr:`failures` hold placeholder values.
132
+ """
133
+
134
+ COLOR_TEST = ANSI.BLUE_FG
135
+ COLOR_PASSED = ANSI.GREEN_FG
136
+ COLOR_SKIPPED = ANSI.YELLOW_FG
137
+ COLOR_FAILED = ANSI.RED_FG
138
+
139
+ _timer1 = Timer(1)
140
+
141
+ def __init__(self) -> None:
142
+ self._test: Test
143
+ self._running_test: RunningTest
144
+ self.log = _logger
145
+ self._regression_start_time: float
146
+ self._test_results: List[_TestResults] = []
147
+ self.total_tests = 0
148
+ """Total number of tests that will be run or skipped."""
149
+ self.count = 0
150
+ """The current test count."""
151
+ self.passed = 0
152
+ """The current number of passed tests."""
153
+ self.skipped = 0
154
+ """The current number of skipped tests."""
155
+ self.failures = 0
156
+ """The current number of failed tests."""
157
+ self._tearing_down = False
158
+ self._test_queue: List[Test] = []
159
+ self._filters: List[re.Pattern[str]] = []
160
+ self._mode = RegressionMode.REGRESSION
161
+ self._included: List[bool]
162
+ self._sim_failure: Union[Error[None], None] = None
163
+ self._regression_seed = cocotb.RANDOM_SEED
164
+ self._random_state: Any
165
+
166
+ # Setup XUnit
167
+ ###################
168
+
169
+ results_filename = os.getenv("COCOTB_RESULTS_FILE", "results.xml")
170
+ suite_name = os.getenv("COCOTB_RESULT_TESTSUITE", "all")
171
+ package_name = os.getenv("COCOTB_RESULT_TESTPACKAGE", "all")
172
+
173
+ self.xunit = XUnitReporter(filename=results_filename)
174
+ self.xunit.add_testsuite(name=suite_name, package=package_name)
175
+ self.xunit.add_property(name="random_seed", value=str(cocotb.RANDOM_SEED))
176
+
177
+ def discover_tests(self, *modules: str) -> None:
178
+ """Discover tests in files automatically.
179
+
180
+ Should be called before :meth:`start_regression` is called.
181
+
182
+ Args:
183
+ modules: Each argument given is the name of a module where tests are found.
184
+ """
185
+ for module_name in modules:
186
+ mod = import_module(module_name)
187
+
188
+ found_test: bool = False
189
+ for obj_name, obj in vars(mod).items():
190
+ if isinstance(obj, Test):
191
+ found_test = True
192
+ self.register_test(obj)
193
+ elif isinstance(obj, Parameterized):
194
+ found_test = True
195
+ generated_tests: bool = False
196
+ for test in obj.generate_tests():
197
+ generated_tests = True
198
+ self.register_test(test)
199
+ if not generated_tests:
200
+ warnings.warn(
201
+ f"Parametrize object generated no tests: {module_name}.{obj_name}",
202
+ stacklevel=2,
203
+ )
204
+
205
+ if not found_test:
206
+ warnings.warn(
207
+ f"No tests were discovered in module: {module_name}", stacklevel=2
208
+ )
209
+
210
+ # error if no tests were discovered
211
+ if not self._test_queue:
212
+ modules_str = ", ".join(repr(m) for m in modules)
213
+ raise RuntimeError(f"No tests were discovered in any module: {modules_str}")
214
+
215
+ def add_filters(self, *filters: str) -> None:
216
+ """Add regular expressions to filter-in registered tests.
217
+
218
+ Only those tests which match at least one of the given filters are included;
219
+ the rest are excluded.
220
+
221
+ Should be called before :meth:`start_regression` is called.
222
+
223
+ Args:
224
+ filters: Each argument given is a regex pattern for test names.
225
+ A match *includes* the test.
226
+ """
227
+ for filter in filters:
228
+ compiled_filter = re.compile(filter)
229
+ self._filters.append(compiled_filter)
230
+
231
+ def set_mode(self, mode: RegressionMode) -> None:
232
+ """Set the regression mode.
233
+
234
+ See :class:`RegressionMode` for more details on how each mode affects :class:`RegressionManager` behavior.
235
+ Should be called before :meth:`start_regression` is called.
236
+
237
+ Args:
238
+ mode: The regression mode to set.
239
+ """
240
+ self._mode = mode
241
+
242
+ def register_test(self, test: Test) -> None:
243
+ """Register a test with the :class:`RegressionManager`.
244
+
245
+ Should be called before :meth:`start_regression` is called.
246
+
247
+ Args:
248
+ test: The test object to register.
249
+ """
250
+ self.log.debug("Registered test %r", test.fullname)
251
+ self._test_queue.append(test)
252
+
253
+ @classmethod
254
+ def setup_pytest_assertion_rewriting(cls) -> None:
255
+ """Configure pytest to rewrite assertions for better failure messages.
256
+
257
+ Must be called before all modules containing tests are imported.
258
+ """
259
+ try:
260
+ import pytest # noqa: PLC0415
261
+ except ImportError:
262
+ _logger.info(
263
+ "pytest not found, install it to enable better AssertionError messages"
264
+ )
265
+ return
266
+ try:
267
+ # Install the assertion rewriting hook, which must be done before we
268
+ # import the test modules.
269
+ from _pytest.assertion import install_importhook # noqa: PLC0415
270
+ from _pytest.config import Config # noqa: PLC0415
271
+
272
+ python_files = os.getenv("COCOTB_REWRITE_ASSERTION_FILES", "*.py").strip()
273
+ if not python_files:
274
+ # Even running the hook causes exceptions in some cases, so if the user
275
+ # selects nothing, don't install the hook at all.
276
+ return
277
+
278
+ pytest_conf = Config.fromdictargs(
279
+ {}, ["--capture=no", "-o", f"python_files={python_files}"]
280
+ )
281
+ install_importhook(pytest_conf)
282
+ except Exception:
283
+ _logger.exception(
284
+ "Configuring the assertion rewrite hook using pytest %s failed. "
285
+ "Please file a bug report!",
286
+ pytest.__version__,
287
+ )
288
+
289
+ def start_regression(self) -> None:
290
+ """Start the regression."""
291
+
292
+ # sort tests into stages
293
+ self._test_queue.sort(key=lambda test: test.stage)
294
+
295
+ # mark tests for running
296
+ if self._filters:
297
+ self._included = [False] * len(self._test_queue)
298
+ for i, test in enumerate(self._test_queue):
299
+ for filter in self._filters:
300
+ if filter.search(test.fullname):
301
+ self._included[i] = True
302
+ else:
303
+ self._included = [True] * len(self._test_queue)
304
+
305
+ # compute counts
306
+ self.count = 1
307
+ self.total_tests = sum(self._included)
308
+ if self.total_tests == 0:
309
+ self.log.warning(
310
+ "No tests left after filtering with: %s",
311
+ ", ".join(f.pattern for f in self._filters),
312
+ )
313
+
314
+ # start write scheduler
315
+ cocotb.handle._start_write_scheduler()
316
+
317
+ # start test loop
318
+ self._regression_start_time = time.time()
319
+ self._first_test = True
320
+ self._execute()
321
+
322
+ def _execute(self) -> None:
323
+ """Run the main regression loop.
324
+
325
+ Used by :meth:`start_regression` and :meth:`_test_complete` to continue to the main test running loop,
326
+ and by :meth:`_fail_regression` to shutdown the regression when a simulation failure occurs.
327
+ """
328
+
329
+ while self._test_queue:
330
+ self._test = self._test_queue.pop(0)
331
+ included = self._included.pop(0)
332
+
333
+ # if the test is not included, record and continue
334
+ if not included:
335
+ self._record_test_excluded()
336
+ continue
337
+
338
+ # if the test is skipped, record and continue
339
+ if self._test.skip and self._mode != RegressionMode.TESTCASE:
340
+ self._record_test_skipped()
341
+ continue
342
+
343
+ # if the test should be run, but the simulator has failed, record and continue
344
+ if self._sim_failure is not None:
345
+ self._score_test(
346
+ self._sim_failure,
347
+ 0,
348
+ 0,
349
+ )
350
+ continue
351
+
352
+ # initialize the test, if it fails, record and continue
353
+ try:
354
+ self._running_test = self._init_test()
355
+ except Exception:
356
+ self._record_test_init_failed()
357
+ continue
358
+
359
+ self._log_test_start()
360
+
361
+ if self._first_test:
362
+ self._first_test = False
363
+ return self._schedule_next_test()
364
+ else:
365
+ return self._timer1._prime(self._schedule_next_test)
366
+
367
+ return self._tear_down()
368
+
369
+ def _init_test(self) -> RunningTest:
370
+ # wrap test function in timeout
371
+ func: Callable[..., Coroutine[Trigger, None, None]]
372
+ timeout = self._test.timeout_time
373
+ if timeout is not None:
374
+ f = self._test.func
375
+
376
+ @functools.wraps(f)
377
+ async def func(*args: object, **kwargs: object) -> None:
378
+ await with_timeout(f(*args, **kwargs), timeout, self._test.timeout_unit)
379
+ else:
380
+ func = self._test.func
381
+
382
+ main_task = Task(func(cocotb.top), name=f"Test {self._test.name}")
383
+ return RunningTest(self._test_complete, main_task)
384
+
385
+ def _schedule_next_test(self, trigger: Union[GPITrigger, None] = None) -> None:
386
+ if trigger is not None:
387
+ # TODO move to Trigger object
388
+ cocotb._gpi_triggers._current_gpi_trigger = trigger
389
+ trigger._cleanup()
390
+
391
+ # seed random number generator based on test module, name, and COCOTB_RANDOM_SEED
392
+ hasher = hashlib.sha1()
393
+ hasher.update(self._test.fullname.encode())
394
+ test_seed = self._regression_seed + int(hasher.hexdigest(), 16)
395
+
396
+ cocotb.RANDOM_SEED = test_seed
397
+ self._random_state = random.getstate()
398
+ random.seed(test_seed)
399
+
400
+ self._start_sim_time = get_sim_time("ns")
401
+ self._start_time = time.time()
402
+
403
+ self._running_test.start()
404
+
405
+ def _tear_down(self) -> None:
406
+ """Called by :meth:`_execute` when there are no more tests to run to finalize the regression."""
407
+ # prevent re-entering the tear down procedure
408
+ if not self._tearing_down:
409
+ self._tearing_down = True
410
+ else:
411
+ return
412
+
413
+ assert not self._test_queue
414
+
415
+ # stop the write scheduler
416
+ cocotb.handle._stop_write_scheduler()
417
+
418
+ # Write out final log messages
419
+ self._log_test_summary()
420
+
421
+ # Generate output reports
422
+ self.xunit.write()
423
+
424
+ # TODO refactor initialization and finalization into their own module
425
+ # to prevent circular imports requiring local imports
426
+ from cocotb._init import _shutdown_testbench # noqa: PLC0415
427
+
428
+ _shutdown_testbench()
429
+
430
+ # Setup simulator finalization
431
+ simulator.stop_simulator()
432
+
433
+ def _test_complete(self) -> None:
434
+ """Callback given to the test to be called when the test finished."""
435
+
436
+ # compute wall time
437
+ wall_time = time.time() - self._start_time
438
+ sim_time_ns = get_sim_time("ns") - self._start_sim_time
439
+
440
+ cocotb.RANDOM_SEED = self._regression_seed
441
+ random.setstate(self._random_state)
442
+
443
+ # Judge and record pass/fail.
444
+ self._score_test(
445
+ self._running_test.result(),
446
+ wall_time,
447
+ sim_time_ns,
448
+ )
449
+
450
+ # Run next test.
451
+ return self._execute()
452
+
453
+ def _score_test(
454
+ self,
455
+ outcome: Outcome[None],
456
+ wall_time_s: float,
457
+ sim_time_ns: float,
458
+ ) -> None:
459
+ test = self._test
460
+
461
+ # score test
462
+ passed: bool
463
+ msg: Union[str, None]
464
+ exc: Union[BaseException, None]
465
+ try:
466
+ outcome.get()
467
+ except BaseException as e:
468
+ passed, msg = False, None
469
+ exc = remove_traceback_frames(e, ["_score_test", "get"])
470
+ else:
471
+ passed, msg, exc = True, None, None
472
+
473
+ if passed:
474
+ if test.expect_error:
475
+ self._record_test_failed(
476
+ wall_time_s=wall_time_s,
477
+ sim_time_ns=sim_time_ns,
478
+ result=exc,
479
+ msg="passed but we expected an error",
480
+ )
481
+ passed = False
482
+
483
+ elif test.expect_fail:
484
+ self._record_test_failed(
485
+ wall_time_s=wall_time_s,
486
+ sim_time_ns=sim_time_ns,
487
+ result=exc,
488
+ msg="passed but we expected a failure",
489
+ )
490
+ passed = False
491
+
492
+ else:
493
+ self._record_test_passed(
494
+ wall_time_s=wall_time_s,
495
+ sim_time_ns=sim_time_ns,
496
+ result=None,
497
+ msg=msg,
498
+ )
499
+
500
+ elif test.expect_fail:
501
+ if isinstance(exc, (AssertionError, Failed)):
502
+ self._record_test_passed(
503
+ wall_time_s=wall_time_s,
504
+ sim_time_ns=sim_time_ns,
505
+ result=None,
506
+ msg="failed as expected",
507
+ )
508
+
509
+ else:
510
+ self._record_test_failed(
511
+ wall_time_s=wall_time_s,
512
+ sim_time_ns=sim_time_ns,
513
+ result=exc,
514
+ msg="expected failure, but errored with unexpected type",
515
+ )
516
+ passed = False
517
+
518
+ elif test.expect_error:
519
+ if isinstance(exc, test.expect_error):
520
+ self._record_test_passed(
521
+ wall_time_s=wall_time_s,
522
+ sim_time_ns=sim_time_ns,
523
+ result=None,
524
+ msg="errored as expected",
525
+ )
526
+
527
+ else:
528
+ self._record_test_failed(
529
+ wall_time_s=wall_time_s,
530
+ sim_time_ns=sim_time_ns,
531
+ result=exc,
532
+ msg="errored with unexpected type",
533
+ )
534
+ passed = False
535
+
536
+ else:
537
+ self._record_test_failed(
538
+ wall_time_s=wall_time_s,
539
+ sim_time_ns=sim_time_ns,
540
+ result=exc,
541
+ msg=msg,
542
+ )
543
+
544
+ def _get_lineno(self, test: Test) -> int:
545
+ try:
546
+ return inspect.getsourcelines(test.func)[1]
547
+ except OSError:
548
+ return 1
549
+
550
+ def _log_test_start(self) -> None:
551
+ """Called by :meth:`_execute` to log that a test is starting."""
552
+ hilight_start = "" if cocotb_logging.strip_ansi else self.COLOR_TEST
553
+ hilight_end = "" if cocotb_logging.strip_ansi else ANSI.DEFAULT
554
+ self.log.info(
555
+ "%srunning%s %s (%d/%d)%s",
556
+ hilight_start,
557
+ hilight_end,
558
+ self._test.fullname,
559
+ self.count,
560
+ self.total_tests,
561
+ _format_doc(self._test.doc),
562
+ )
563
+
564
+ def _record_test_excluded(self) -> None:
565
+ """Called by :meth:`_execute` when a test is excluded by filters."""
566
+
567
+ # write out xunit results
568
+ lineno = self._get_lineno(self._test)
569
+ self.xunit.add_testcase(
570
+ name=self._test.name,
571
+ classname=self._test.module,
572
+ file=inspect.getfile(self._test.func),
573
+ lineno=repr(lineno),
574
+ time=repr(0),
575
+ sim_time_ns=repr(0),
576
+ ratio_time=repr(0),
577
+ )
578
+ self.xunit.add_skipped()
579
+
580
+ # do not log anything, nor save details for the summary
581
+
582
+ def _record_test_skipped(self) -> None:
583
+ """Called by :meth:`_execute` when a test is skipped."""
584
+
585
+ # log test results
586
+ hilight_start = "" if cocotb_logging.strip_ansi else self.COLOR_SKIPPED
587
+ hilight_end = "" if cocotb_logging.strip_ansi else ANSI.DEFAULT
588
+ self.log.info(
589
+ "%sskipping%s %s (%d/%d)%s",
590
+ hilight_start,
591
+ hilight_end,
592
+ self._test.fullname,
593
+ self.count,
594
+ self.total_tests,
595
+ _format_doc(self._test.doc),
596
+ )
597
+
598
+ # write out xunit results
599
+ lineno = self._get_lineno(self._test)
600
+ self.xunit.add_testcase(
601
+ name=self._test.name,
602
+ classname=self._test.module,
603
+ file=inspect.getfile(self._test.func),
604
+ lineno=repr(lineno),
605
+ time=repr(0),
606
+ sim_time_ns=repr(0),
607
+ ratio_time=repr(0),
608
+ )
609
+ self.xunit.add_skipped()
610
+
611
+ # save details for summary
612
+ self._test_results.append(
613
+ _TestResults(
614
+ test_fullname=self._test.fullname,
615
+ passed=None,
616
+ sim_time_ns=0,
617
+ wall_time_s=0,
618
+ )
619
+ )
620
+
621
+ # update running passed/failed/skipped counts
622
+ self.skipped += 1
623
+ self.count += 1
624
+
625
+ def _record_test_init_failed(self) -> None:
626
+ """Called by :meth:`_execute` when a test initialization fails."""
627
+
628
+ # log test results
629
+ hilight_start = "" if cocotb_logging.strip_ansi else self.COLOR_FAILED
630
+ hilight_end = "" if cocotb_logging.strip_ansi else ANSI.DEFAULT
631
+ self.log.exception(
632
+ "%sFailed to initialize%s %s! (%d/%d)%s",
633
+ hilight_start,
634
+ hilight_end,
635
+ self._test.fullname,
636
+ self.count,
637
+ self.total_tests,
638
+ _format_doc(self._test.doc),
639
+ )
640
+
641
+ # write out xunit results
642
+ lineno = self._get_lineno(self._test)
643
+ self.xunit.add_testcase(
644
+ name=self._test.name,
645
+ classname=self._test.module,
646
+ file=inspect.getfile(self._test.func),
647
+ lineno=repr(lineno),
648
+ time=repr(0),
649
+ sim_time_ns=repr(0),
650
+ ratio_time=repr(0),
651
+ )
652
+ self.xunit.add_failure(msg="Test initialization failed")
653
+
654
+ # save details for summary
655
+ self._test_results.append(
656
+ _TestResults(
657
+ test_fullname=self._test.fullname,
658
+ passed=False,
659
+ sim_time_ns=0,
660
+ wall_time_s=0,
661
+ )
662
+ )
663
+
664
+ # update running passed/failed/skipped counts
665
+ self.failures += 1
666
+ self.count += 1
667
+
668
+ def _record_test_passed(
669
+ self,
670
+ wall_time_s: float,
671
+ sim_time_ns: float,
672
+ result: Union[Exception, None],
673
+ msg: Union[str, None],
674
+ ) -> None:
675
+ start_hilight = "" if cocotb_logging.strip_ansi else self.COLOR_PASSED
676
+ stop_hilight = "" if cocotb_logging.strip_ansi else ANSI.DEFAULT
677
+ if msg is None:
678
+ rest = ""
679
+ else:
680
+ rest = f": {msg}"
681
+ if result is None:
682
+ result_was = ""
683
+ else:
684
+ result_was = f" (result was {type(result).__qualname__})"
685
+ self.log.info(
686
+ "%s %spassed%s%s%s",
687
+ self._test.fullname,
688
+ start_hilight,
689
+ stop_hilight,
690
+ rest,
691
+ result_was,
692
+ )
693
+
694
+ # write out xunit results
695
+ ratio_time = safe_divide(sim_time_ns, wall_time_s)
696
+ lineno = self._get_lineno(self._test)
697
+ self.xunit.add_testcase(
698
+ name=self._test.name,
699
+ classname=self._test.module,
700
+ file=inspect.getfile(self._test.func),
701
+ lineno=repr(lineno),
702
+ time=repr(wall_time_s),
703
+ sim_time_ns=repr(sim_time_ns),
704
+ ratio_time=repr(ratio_time),
705
+ )
706
+
707
+ # update running passed/failed/skipped counts
708
+ self.passed += 1
709
+ self.count += 1
710
+
711
+ # save details for summary
712
+ self._test_results.append(
713
+ _TestResults(
714
+ test_fullname=self._test.fullname,
715
+ passed=True,
716
+ sim_time_ns=sim_time_ns,
717
+ wall_time_s=wall_time_s,
718
+ )
719
+ )
720
+
721
+ def _record_test_failed(
722
+ self,
723
+ wall_time_s: float,
724
+ sim_time_ns: float,
725
+ result: Union[BaseException, None],
726
+ msg: Union[str, None],
727
+ ) -> None:
728
+ start_hilight = "" if cocotb_logging.strip_ansi else self.COLOR_FAILED
729
+ stop_hilight = "" if cocotb_logging.strip_ansi else ANSI.DEFAULT
730
+ if msg is None:
731
+ rest = ""
732
+ else:
733
+ rest = f": {msg}"
734
+ self.log.warning(
735
+ "%s%s %sfailed%s%s",
736
+ stop_hilight,
737
+ self._test.fullname,
738
+ start_hilight,
739
+ stop_hilight,
740
+ rest,
741
+ )
742
+
743
+ # write out xunit results
744
+ ratio_time = safe_divide(sim_time_ns, wall_time_s)
745
+ lineno = self._get_lineno(self._test)
746
+ self.xunit.add_testcase(
747
+ name=self._test.name,
748
+ classname=self._test.module,
749
+ file=inspect.getfile(self._test.func),
750
+ lineno=repr(lineno),
751
+ time=repr(wall_time_s),
752
+ sim_time_ns=repr(sim_time_ns),
753
+ ratio_time=repr(ratio_time),
754
+ )
755
+ self.xunit.add_failure(
756
+ error_type=type(result).__name__, error_msg=bin_xml_escape(result)
757
+ )
758
+
759
+ # update running passed/failed/skipped counts
760
+ self.failures += 1
761
+ self.count += 1
762
+
763
+ # save details for summary
764
+ self._test_results.append(
765
+ _TestResults(
766
+ test_fullname=self._test.fullname,
767
+ passed=False,
768
+ sim_time_ns=sim_time_ns,
769
+ wall_time_s=wall_time_s,
770
+ )
771
+ )
772
+
773
+ def _log_test_summary(self) -> None:
774
+ """Called by :meth:`_tear_down` to log the test summary."""
775
+ real_time = time.time() - self._regression_start_time
776
+ sim_time_ns = get_sim_time("ns")
777
+ ratio_time = safe_divide(sim_time_ns, real_time)
778
+
779
+ if len(self._test_results) == 0:
780
+ return
781
+
782
+ TEST_FIELD = "TEST"
783
+ RESULT_FIELD = "STATUS"
784
+ SIM_FIELD = "SIM TIME (ns)"
785
+ REAL_FIELD = "REAL TIME (s)"
786
+ RATIO_FIELD = "RATIO (ns/s)"
787
+ TOTAL_NAME = f"TESTS={self.total_tests} PASS={self.passed} FAIL={self.failures} SKIP={self.skipped}"
788
+
789
+ TEST_FIELD_LEN = max(
790
+ len(TEST_FIELD),
791
+ len(TOTAL_NAME),
792
+ len(max([x.test_fullname for x in self._test_results], key=len)),
793
+ )
794
+ RESULT_FIELD_LEN = len(RESULT_FIELD)
795
+ SIM_FIELD_LEN = len(SIM_FIELD)
796
+ REAL_FIELD_LEN = len(REAL_FIELD)
797
+ RATIO_FIELD_LEN = len(RATIO_FIELD)
798
+
799
+ header_dict = {
800
+ "a": TEST_FIELD,
801
+ "b": RESULT_FIELD,
802
+ "c": SIM_FIELD,
803
+ "d": REAL_FIELD,
804
+ "e": RATIO_FIELD,
805
+ "a_len": TEST_FIELD_LEN,
806
+ "b_len": RESULT_FIELD_LEN,
807
+ "c_len": SIM_FIELD_LEN,
808
+ "d_len": REAL_FIELD_LEN,
809
+ "e_len": RATIO_FIELD_LEN,
810
+ }
811
+
812
+ LINE_LEN = (
813
+ 3
814
+ + TEST_FIELD_LEN
815
+ + 2
816
+ + RESULT_FIELD_LEN
817
+ + 2
818
+ + SIM_FIELD_LEN
819
+ + 2
820
+ + REAL_FIELD_LEN
821
+ + 2
822
+ + RATIO_FIELD_LEN
823
+ + 3
824
+ )
825
+
826
+ LINE_SEP = "*" * LINE_LEN + "\n"
827
+
828
+ summary = ""
829
+ summary += LINE_SEP
830
+ summary += "** {a:<{a_len}} {b:^{b_len}} {c:>{c_len}} {d:>{d_len}} {e:>{e_len}} **\n".format(
831
+ **header_dict
832
+ )
833
+ summary += LINE_SEP
834
+
835
+ test_line = "** {a:<{a_len}} {start}{b:^{b_len}}{end} {c:>{c_len}.2f} {d:>{d_len}.2f} {e:>{e_len}} **\n"
836
+ hilite: str
837
+ lolite: str
838
+ for result in self._test_results:
839
+ if result.passed is None:
840
+ ratio = "-.--"
841
+ pass_fail_str = "SKIP"
842
+ hilite = self.COLOR_SKIPPED
843
+ lolite = ANSI.DEFAULT
844
+ elif result.passed:
845
+ ratio = format(result.ratio, "0.2f")
846
+ pass_fail_str = "PASS"
847
+ hilite = self.COLOR_PASSED
848
+ lolite = ANSI.DEFAULT
849
+ else:
850
+ ratio = format(result.ratio, "0.2f")
851
+ pass_fail_str = "FAIL"
852
+ hilite = self.COLOR_FAILED
853
+ lolite = ANSI.DEFAULT
854
+
855
+ if cocotb_logging.strip_ansi:
856
+ hilite = ""
857
+ lolite = ""
858
+
859
+ test_dict = {
860
+ "a": result.test_fullname,
861
+ "b": pass_fail_str,
862
+ "c": result.sim_time_ns,
863
+ "d": result.wall_time_s,
864
+ "e": ratio,
865
+ "a_len": TEST_FIELD_LEN,
866
+ "b_len": RESULT_FIELD_LEN,
867
+ "c_len": SIM_FIELD_LEN - 1,
868
+ "d_len": REAL_FIELD_LEN - 1,
869
+ "e_len": RATIO_FIELD_LEN - 1,
870
+ "start": hilite,
871
+ "end": lolite,
872
+ }
873
+
874
+ summary += test_line.format(**test_dict)
875
+
876
+ summary += LINE_SEP
877
+
878
+ summary += test_line.format(
879
+ a=TOTAL_NAME,
880
+ b="",
881
+ c=sim_time_ns,
882
+ d=real_time,
883
+ e=format(ratio_time, "0.2f"),
884
+ a_len=TEST_FIELD_LEN,
885
+ b_len=RESULT_FIELD_LEN,
886
+ c_len=SIM_FIELD_LEN - 1,
887
+ d_len=REAL_FIELD_LEN - 1,
888
+ e_len=RATIO_FIELD_LEN - 1,
889
+ start="",
890
+ end="",
891
+ )
892
+
893
+ summary += LINE_SEP
894
+
895
+ self.log.info(summary)
896
+
897
+ def _fail_simulation(self, msg: str) -> None:
898
+ self._sim_failure = Error(SimFailure(msg))
899
+ self._running_test.abort(self._sim_failure)
900
+ cocotb._scheduler_inst._event_loop()