cocotb 2.0.0rc2__cp310-cp310-macosx_11_0_arm64.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.

Potentially problematic release.


This version of cocotb might be problematic. Click here for more details.

Files changed (115) hide show
  1. cocotb/_ANSI.py +65 -0
  2. cocotb/__init__.py +125 -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 +148 -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 +66 -0
  22. cocotb/clock.py +419 -0
  23. cocotb/debug.py +24 -0
  24. cocotb/handle.py +1752 -0
  25. cocotb/libs/libcocotb.so +0 -0
  26. cocotb/libs/libcocotbfli_modelsim.so +0 -0
  27. cocotb/libs/libcocotbutils.so +0 -0
  28. cocotb/libs/libcocotbvhpi_aldec.so +0 -0
  29. cocotb/libs/libcocotbvhpi_ius.so +0 -0
  30. cocotb/libs/libcocotbvhpi_modelsim.so +0 -0
  31. cocotb/libs/libcocotbvhpi_nvc.so +0 -0
  32. cocotb/libs/libcocotbvpi_aldec.so +0 -0
  33. cocotb/libs/libcocotbvpi_dsim.so +0 -0
  34. cocotb/libs/libcocotbvpi_ghdl.so +0 -0
  35. cocotb/libs/libcocotbvpi_icarus.vpl +0 -0
  36. cocotb/libs/libcocotbvpi_ius.so +0 -0
  37. cocotb/libs/libcocotbvpi_modelsim.so +0 -0
  38. cocotb/libs/libcocotbvpi_vcs.so +0 -0
  39. cocotb/libs/libcocotbvpi_verilator.so +0 -0
  40. cocotb/libs/libembed.so +0 -0
  41. cocotb/libs/libgpi.so +0 -0
  42. cocotb/libs/libgpilog.so +0 -0
  43. cocotb/libs/libpygpilog.so +0 -0
  44. cocotb/logging.py +424 -0
  45. cocotb/py.typed +0 -0
  46. cocotb/queue.py +225 -0
  47. cocotb/regression.py +896 -0
  48. cocotb/result.py +38 -0
  49. cocotb/share/def/.gitignore +2 -0
  50. cocotb/share/def/README.md +4 -0
  51. cocotb/share/def/aldec.def +61 -0
  52. cocotb/share/def/ghdl.def +43 -0
  53. cocotb/share/def/icarus.def +43 -0
  54. cocotb/share/def/modelsim.def +138 -0
  55. cocotb/share/include/cocotb_utils.h +70 -0
  56. cocotb/share/include/embed.h +33 -0
  57. cocotb/share/include/exports.h +20 -0
  58. cocotb/share/include/gpi.h +459 -0
  59. cocotb/share/include/gpi_logging.h +291 -0
  60. cocotb/share/include/py_gpi_logging.h +33 -0
  61. cocotb/share/include/vhpi_user_ext.h +26 -0
  62. cocotb/share/include/vpi_user_ext.h +33 -0
  63. cocotb/share/lib/verilator/verilator.cpp +209 -0
  64. cocotb/simtime.py +230 -0
  65. cocotb/simulator.cpython-310-darwin.so +0 -0
  66. cocotb/simulator.pyi +107 -0
  67. cocotb/task.py +590 -0
  68. cocotb/triggers.py +67 -0
  69. cocotb/types/__init__.py +31 -0
  70. cocotb/types/_abstract_array.py +151 -0
  71. cocotb/types/_array.py +295 -0
  72. cocotb/types/_indexing.py +17 -0
  73. cocotb/types/_logic.py +333 -0
  74. cocotb/types/_logic_array.py +868 -0
  75. cocotb/types/_range.py +197 -0
  76. cocotb/types/_resolve.py +76 -0
  77. cocotb/utils.py +110 -0
  78. cocotb-2.0.0rc2.dist-info/METADATA +60 -0
  79. cocotb-2.0.0rc2.dist-info/RECORD +115 -0
  80. cocotb-2.0.0rc2.dist-info/WHEEL +5 -0
  81. cocotb-2.0.0rc2.dist-info/entry_points.txt +2 -0
  82. cocotb-2.0.0rc2.dist-info/licenses/LICENSE +29 -0
  83. cocotb-2.0.0rc2.dist-info/top_level.txt +23 -0
  84. cocotb_tools/__init__.py +0 -0
  85. cocotb_tools/_coverage.py +33 -0
  86. cocotb_tools/_vendor/__init__.py +3 -0
  87. cocotb_tools/_vendor/distutils_version.py +346 -0
  88. cocotb_tools/check_results.py +65 -0
  89. cocotb_tools/combine_results.py +152 -0
  90. cocotb_tools/config.py +241 -0
  91. cocotb_tools/ipython_support.py +99 -0
  92. cocotb_tools/makefiles/Makefile.deprecations +27 -0
  93. cocotb_tools/makefiles/Makefile.inc +198 -0
  94. cocotb_tools/makefiles/Makefile.sim +96 -0
  95. cocotb_tools/makefiles/simulators/Makefile.activehdl +72 -0
  96. cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
  97. cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
  98. cocotb_tools/makefiles/simulators/Makefile.ghdl +84 -0
  99. cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
  100. cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
  101. cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
  102. cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
  103. cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
  104. cocotb_tools/makefiles/simulators/Makefile.questa-compat +143 -0
  105. cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
  106. cocotb_tools/makefiles/simulators/Makefile.riviera +144 -0
  107. cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
  108. cocotb_tools/makefiles/simulators/Makefile.verilator +79 -0
  109. cocotb_tools/makefiles/simulators/Makefile.xcelium +104 -0
  110. cocotb_tools/py.typed +0 -0
  111. cocotb_tools/runner.py +1868 -0
  112. cocotb_tools/sim_versions.py +140 -0
  113. pygpi/__init__.py +0 -0
  114. pygpi/entry.py +42 -0
  115. pygpi/py.typed +0 -0
cocotb/_test.py ADDED
@@ -0,0 +1,248 @@
1
+ # Copyright cocotb contributors
2
+ # Licensed under the Revised BSD License, see LICENSE for details.
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+ import inspect
5
+ import os
6
+ import pdb
7
+ from typing import (
8
+ Any,
9
+ Callable,
10
+ Coroutine,
11
+ List,
12
+ Optional,
13
+ Union,
14
+ )
15
+
16
+ import cocotb
17
+ from cocotb._base_triggers import NullTrigger, Trigger
18
+ from cocotb._deprecation import deprecated
19
+ from cocotb._exceptions import InternalError
20
+ from cocotb._outcomes import Error, Outcome, Value
21
+ from cocotb._test_functions import TestSuccess
22
+ from cocotb.task import ResultType, Task
23
+
24
+ _pdb_on_exception = "COCOTB_PDB_ON_EXCEPTION" in os.environ
25
+
26
+
27
+ class RunningTest:
28
+ """State of the currently executing Test."""
29
+
30
+ # TODO
31
+ # Make the tasks list a TaskManager.
32
+ # Make shutdown errors and outcome be an ExceptionGroup from that TaskManager.
33
+ # Replace result() with passing the outcome to the done callback.
34
+ # Make this and Task the same object which is a Coroutine.
35
+ # Reimplement the logic in the body of an async function.
36
+ # Make RunningTest a normal Task that the RegressionManager runs and registers a
37
+ # done callback with.
38
+
39
+ def __init__(
40
+ self, test_complete_cb: Callable[[], None], main_task: Task[None]
41
+ ) -> None:
42
+ self._test_complete_cb: Callable[[], None] = test_complete_cb
43
+ self._main_task: Task[None] = main_task
44
+ self._main_task._add_done_callback(self._test_done_callback)
45
+
46
+ self.tasks: List[Task[Any]] = [main_task]
47
+
48
+ self._outcome: Union[None, Outcome[None]] = None
49
+ self._shutdown_errors: list[Outcome[None]] = []
50
+
51
+ def _test_done_callback(self, task: Task[None]) -> None:
52
+ self.tasks.remove(task)
53
+ # If cancelled, end the Test without additional error. This case would only
54
+ # occur if a child threw a CancelledError or if the Test was forced to shutdown.
55
+ if task.cancelled():
56
+ self.abort(Value(None))
57
+ return
58
+ # Handle outcome appropriately and shut down the Test.
59
+ e = task.exception()
60
+ if e is None:
61
+ self.abort(Value(task.result()))
62
+ elif isinstance(e, TestSuccess):
63
+ task._log.info("Test stopped early by this task")
64
+ self.abort(Value(None))
65
+ else:
66
+ task._log.warning(e, exc_info=e)
67
+ self.abort(Error(e))
68
+
69
+ def start(self) -> None:
70
+ cocotb._scheduler_inst._schedule_task_internal(self._main_task)
71
+ cocotb._scheduler_inst._event_loop()
72
+
73
+ def result(self) -> Outcome[None]:
74
+ if self._outcome is None: # pragma: no cover
75
+ raise InternalError("Getting result before test is completed")
76
+
77
+ if not isinstance(self._outcome, Error) and self._shutdown_errors:
78
+ return self._shutdown_errors[0]
79
+ return self._outcome
80
+
81
+ def abort(self, outcome: Outcome[None]) -> None:
82
+ """Force this test to end early."""
83
+
84
+ # If we are shutting down, save any errors
85
+ if self._outcome is not None:
86
+ if isinstance(outcome, Error):
87
+ self._shutdown_errors.append(outcome)
88
+ return
89
+
90
+ # Break into pdb on test end before all Tasks are killed.
91
+ if _pdb_on_exception and isinstance(outcome, Error):
92
+ try:
93
+ pdb.post_mortem(outcome.error.__traceback__)
94
+ except BaseException:
95
+ pdb.set_trace()
96
+
97
+ # Set outcome and cancel Tasks.
98
+ self._outcome = outcome
99
+ for task in self.tasks[:]:
100
+ task._cancel_now()
101
+
102
+ self._test_complete_cb()
103
+
104
+ def add_task(self, task: Task[Any]) -> None:
105
+ self.tasks.append(task)
106
+ task._add_done_callback(self._task_done_callback)
107
+
108
+ def _task_done_callback(self, task: Task[Any]) -> None:
109
+ self.tasks.remove(task)
110
+ # if cancelled, do nothing
111
+ if task.cancelled():
112
+ return
113
+ # if there's a Task awaiting this one, don't fail
114
+ if task.complete in cocotb._scheduler_inst._trigger2tasks:
115
+ return
116
+ # if no failure, do nothing
117
+ e = task.exception()
118
+ if e is None:
119
+ return
120
+ # there was a failure and no one is watching, fail test
121
+ elif isinstance(e, TestSuccess):
122
+ task._log.info("Test stopped early by this task")
123
+ self.abort(Value(None))
124
+ else:
125
+ task._log.warning(e, exc_info=e)
126
+ self.abort(Error(e))
127
+
128
+
129
+ def start_soon(
130
+ coro: Union[Task[ResultType], Coroutine[Trigger, None, ResultType]],
131
+ *,
132
+ name: Optional[str] = None,
133
+ ) -> Task[ResultType]:
134
+ """
135
+ Schedule a :term:`coroutine` to be run concurrently in a :class:`~cocotb.task.Task`.
136
+
137
+ Note that this is not an :keyword:`async` function,
138
+ and the new task will not execute until the calling task yields control.
139
+
140
+ Args:
141
+ coro: A :class:`!Task` or :term:`!coroutine` to be run concurrently.
142
+ name:
143
+ The task's name.
144
+
145
+ .. versionadded:: 2.0
146
+
147
+ Returns:
148
+ The :class:`~cocotb.task.Task` that is scheduled to be run.
149
+
150
+ .. versionadded:: 1.6
151
+ """
152
+ task = create_task(coro, name=name)
153
+ cocotb._scheduler_inst._schedule_task(task)
154
+ return task
155
+
156
+
157
+ @deprecated("Use ``cocotb.start_soon`` instead.")
158
+ async def start(
159
+ coro: Union[Task[ResultType], Coroutine[Trigger, None, ResultType]],
160
+ *,
161
+ name: Optional[str] = None,
162
+ ) -> Task[ResultType]:
163
+ """
164
+ Schedule a :term:`coroutine` to be run concurrently, then yield control to allow pending tasks to execute.
165
+
166
+ The calling task will resume execution before control is returned to the simulator.
167
+
168
+ When the calling task resumes, the newly scheduled task may have completed,
169
+ raised an Exception, or be pending on a :class:`~cocotb.triggers.Trigger`.
170
+
171
+ Args:
172
+ coro: A :class:`!Task` or :term:`!coroutine` to be run concurrently.
173
+ name:
174
+ The task's name.
175
+
176
+ .. versionadded:: 2.0
177
+
178
+ Returns:
179
+ The :class:`~cocotb.task.Task` that has been scheduled and allowed to execute.
180
+
181
+ .. versionadded:: 1.6
182
+
183
+ .. deprecated:: 2.0
184
+ Use :func:`cocotb.start_soon` instead.
185
+ If you need the scheduled Task to start before continuing the current Task,
186
+ use an :class:`.Event` to block the current Task until the scheduled Task starts,
187
+ like so:
188
+
189
+ .. code-block:: python
190
+
191
+ async def coro(started: Event) -> None:
192
+ started.set()
193
+ # Do stuff...
194
+
195
+
196
+ task_started = Event()
197
+ task = cocotb.start_soon(coro(task_started))
198
+ await task_started.wait()
199
+ """
200
+ task = start_soon(coro, name=name)
201
+ await NullTrigger()
202
+ return task
203
+
204
+
205
+ def create_task(
206
+ coro: Union[Task[ResultType], Coroutine[Trigger, None, ResultType]],
207
+ *,
208
+ name: Optional[str] = None,
209
+ ) -> Task[ResultType]:
210
+ """
211
+ Construct a :term:`!coroutine` into a :class:`~cocotb.task.Task` without scheduling the task.
212
+
213
+ The task can later be scheduled with :func:`cocotb.start` or :func:`cocotb.start_soon`.
214
+
215
+ Args:
216
+ coro: A :class:`!Task` or a :term:`!coroutine` to be turned into a :class:`!Task`.
217
+ name:
218
+ The task's name.
219
+
220
+ .. versionadded:: 2.0
221
+
222
+ Returns:
223
+ Either the provided :class:`~cocotb.task.Task` or a new Task wrapping the coroutine.
224
+
225
+ .. versionadded:: 1.6
226
+ """
227
+ if isinstance(coro, Task):
228
+ if name is not None:
229
+ coro.set_name(name)
230
+ return coro
231
+ elif isinstance(coro, Coroutine):
232
+ task = Task[ResultType](coro, name=name)
233
+ cocotb._regression_manager._running_test.add_task(task)
234
+ return task
235
+ elif inspect.iscoroutinefunction(coro):
236
+ raise TypeError(
237
+ f"Coroutine function {coro} should be called prior to being scheduled."
238
+ )
239
+ elif inspect.isasyncgen(coro):
240
+ raise TypeError(
241
+ f"{coro.__qualname__} is an async generator, not a coroutine. "
242
+ "You likely used the yield keyword instead of await."
243
+ )
244
+ else:
245
+ raise TypeError(
246
+ f"Attempt to add an object of type {type(coro)} to the scheduler, "
247
+ f"which isn't a coroutine: {coro!r}\n"
248
+ )
@@ -0,0 +1,312 @@
1
+ # Copyright cocotb contributors
2
+ # Licensed under the Revised BSD License, see LICENSE for details.
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+
5
+ import functools
6
+ import inspect
7
+ import logging
8
+ import warnings
9
+ from itertools import product
10
+ from typing import (
11
+ TYPE_CHECKING,
12
+ Callable,
13
+ Coroutine,
14
+ Dict,
15
+ Optional,
16
+ Sequence,
17
+ Tuple,
18
+ Type,
19
+ Union,
20
+ cast,
21
+ overload,
22
+ )
23
+
24
+ from cocotb._base_triggers import Trigger
25
+ from cocotb._decorators import Test
26
+ from cocotb._typing import TimeUnit
27
+
28
+ if TYPE_CHECKING:
29
+ from types import FrameType, FunctionType
30
+
31
+
32
+ class TestFactory:
33
+ """Factory to automatically generate tests.
34
+
35
+ Args:
36
+ test_function: A Callable that returns the test Coroutine.
37
+ Must take *dut* as the first argument.
38
+ *args: Remaining arguments are passed directly to the test function.
39
+ Note that these arguments are not varied. An argument that
40
+ varies with each test must be a keyword argument to the
41
+ test function.
42
+ **kwargs: Remaining keyword arguments are passed directly to the test function.
43
+ Note that these arguments are not varied. An argument that
44
+ varies with each test must be a keyword argument to the
45
+ test function.
46
+
47
+ Assuming we have a common test function that will run a test. This test
48
+ function will take keyword arguments (for example generators for each of
49
+ the input interfaces) and generate tests that call the supplied function.
50
+
51
+ This Factory allows us to generate sets of tests based on the different
52
+ permutations of the possible arguments to the test function.
53
+
54
+ For example, if we have a module that takes backpressure, has two configurable
55
+ features where enabling ``feature_b`` requires ``feature_a`` to be active, and
56
+ need to test against data generation routines ``gen_a`` and ``gen_b``:
57
+
58
+ >>> tf = TestFactory(test_function=run_test)
59
+ >>> tf.add_option(name="data_in", optionlist=[gen_a, gen_b])
60
+ >>> tf.add_option("backpressure", [None, random_backpressure])
61
+ >>> tf.add_option(
62
+ ... ("feature_a", "feature_b"), [(False, False), (True, False), (True, True)]
63
+ ... )
64
+ >>> tf.generate_tests()
65
+
66
+ We would get the following tests:
67
+
68
+ * ``gen_a`` with no backpressure and both features disabled
69
+ * ``gen_a`` with no backpressure and only ``feature_a`` enabled
70
+ * ``gen_a`` with no backpressure and both features enabled
71
+ * ``gen_a`` with ``random_backpressure`` and both features disabled
72
+ * ``gen_a`` with ``random_backpressure`` and only ``feature_a`` enabled
73
+ * ``gen_a`` with ``random_backpressure`` and both features enabled
74
+ * ``gen_b`` with no backpressure and both features disabled
75
+ * ``gen_b`` with no backpressure and only ``feature_a`` enabled
76
+ * ``gen_b`` with no backpressure and both features enabled
77
+ * ``gen_b`` with ``random_backpressure`` and both features disabled
78
+ * ``gen_b`` with ``random_backpressure`` and only ``feature_a`` enabled
79
+ * ``gen_b`` with ``random_backpressure`` and both features enabled
80
+
81
+ The tests are appended to the calling module for auto-discovery.
82
+
83
+ Tests are simply named ``test_function_N``. The docstring for the test (hence
84
+ the test description) includes the name and description of each generator.
85
+
86
+ .. versionchanged:: 1.5
87
+ Groups of options are now supported
88
+
89
+ .. versionchanged:: 2.0
90
+ You can now pass :func:`cocotb.test` decorator arguments when generating tests.
91
+
92
+ .. deprecated:: 2.0
93
+ Use :func:`cocotb.parametrize` instead.
94
+ """
95
+
96
+ def __init__(
97
+ self,
98
+ test_function: Callable[..., Coroutine[Trigger, None, None]],
99
+ *args: object,
100
+ **kwargs: object,
101
+ ) -> None:
102
+ warnings.warn(
103
+ "TestFactory is deprecated, use `@cocotb.parametrize` instead",
104
+ DeprecationWarning,
105
+ stacklevel=2,
106
+ )
107
+ self.test_function = test_function
108
+ self.args = args
109
+ self.kwargs_constant = kwargs
110
+ self.kwargs: Dict[
111
+ Union[str, Sequence[str]],
112
+ Union[Sequence[object], Sequence[Sequence[object]]],
113
+ ] = {}
114
+ self._log = logging.getLogger(f"TestFactory({self.test_function.__name__})")
115
+
116
+ @overload
117
+ def add_option(self, name: str, optionlist: Sequence[object]) -> None: ...
118
+
119
+ @overload
120
+ def add_option(
121
+ self, name: Sequence[str], optionlist: Sequence[Sequence[object]]
122
+ ) -> None: ...
123
+
124
+ def add_option(
125
+ self,
126
+ name: Union[str, Sequence[str]],
127
+ optionlist: Union[Sequence[object], Sequence[Sequence[object]]],
128
+ ) -> None:
129
+ """Add a named option to the test.
130
+
131
+ Args:
132
+ name:
133
+ An option name, or an iterable of several option names. Passed to test as keyword arguments.
134
+
135
+ optionlist:
136
+ A list of possible options for this test knob.
137
+ If N names were specified, this must be a list of N-tuples or
138
+ lists, where each element specifies a value for its respective
139
+ option.
140
+
141
+ .. versionchanged:: 1.5
142
+ Groups of options are now supported
143
+ """
144
+ if not isinstance(name, str):
145
+ optionlist = cast("Sequence[Sequence[object]]", optionlist)
146
+ for opt in optionlist:
147
+ if len(name) != len(opt):
148
+ raise ValueError(
149
+ "Mismatch between number of options and number of option values in group"
150
+ )
151
+ self.kwargs[name] = optionlist
152
+
153
+ def generate_tests(
154
+ self,
155
+ *,
156
+ prefix: Optional[str] = None,
157
+ postfix: Optional[str] = None,
158
+ stacklevel: int = 0,
159
+ name: Optional[str] = None,
160
+ timeout_time: Optional[float] = None,
161
+ timeout_unit: TimeUnit = "step",
162
+ expect_fail: bool = False,
163
+ expect_error: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = (),
164
+ skip: bool = False,
165
+ stage: int = 0,
166
+ ) -> None:
167
+ """
168
+ Generate an exhaustive set of tests using the cartesian product of the
169
+ possible keyword arguments.
170
+
171
+ The generated tests are appended to the namespace of the calling
172
+ module.
173
+
174
+ Args:
175
+ prefix:
176
+ Text string to append to start of ``test_function`` name when naming generated test cases.
177
+ This allows reuse of a single ``test_function`` with multiple :class:`TestFactories <.TestFactory>` without name clashes.
178
+
179
+ .. deprecated:: 2.0
180
+ Use the more flexible ``name`` field instead.
181
+
182
+ postfix:
183
+ Text string to append to end of ``test_function`` name when naming generated test cases.
184
+ This allows reuse of a single ``test_function`` with multiple :class:`TestFactories <.TestFactory>` without name clashes.
185
+
186
+ .. deprecated:: 2.0
187
+ Use the more flexible ``name`` field instead.
188
+ stacklevel:
189
+ Which stack level to add the generated tests to. This can be used to make a custom TestFactory wrapper.
190
+
191
+ name:
192
+ Passed as ``name`` argument to :func:`cocotb.test`.
193
+
194
+ .. versionadded:: 2.0
195
+
196
+ timeout_time:
197
+ Passed as ``timeout_time`` argument to :func:`cocotb.test`.
198
+
199
+ .. versionadded:: 2.0
200
+
201
+ timeout_unit:
202
+ Passed as ``timeout_unit`` argument to :func:`cocotb.test`.
203
+
204
+ .. versionadded:: 2.0
205
+
206
+ expect_fail:
207
+ Passed as ``expect_fail`` argument to :func:`cocotb.test`.
208
+
209
+ .. versionadded:: 2.0
210
+
211
+ expect_error:
212
+ Passed as ``expect_error`` argument to :func:`cocotb.test`.
213
+
214
+ .. versionadded:: 2.0
215
+
216
+ skip:
217
+ Passed as ``skip`` argument to :func:`cocotb.test`.
218
+
219
+ .. versionadded:: 2.0
220
+
221
+ stage:
222
+ Passed as ``stage`` argument to :func:`cocotb.test`.
223
+
224
+ .. versionadded:: 2.0
225
+ """
226
+
227
+ if prefix is not None:
228
+ warnings.warn(
229
+ "``prefix`` argument is deprecated. Use the more flexible ``name`` field instead.",
230
+ DeprecationWarning,
231
+ stacklevel=2,
232
+ )
233
+ else:
234
+ prefix = ""
235
+
236
+ if postfix is not None:
237
+ warnings.warn(
238
+ "``postfix`` argument is deprecated. Use the more flexible ``name`` field instead.",
239
+ DeprecationWarning,
240
+ stacklevel=2,
241
+ )
242
+ else:
243
+ postfix = ""
244
+
245
+ # trust the user puts a reasonable stacklevel in
246
+ glbs = cast("FrameType", inspect.stack()[stacklevel][0].f_back).f_globals
247
+
248
+ test_func_name = self.test_function.__qualname__ if name is None else name
249
+
250
+ for index, testoptions in enumerate(
251
+ dict(zip(self.kwargs, v)) for v in product(*self.kwargs.values())
252
+ ):
253
+ name = f"{prefix}{test_func_name}{postfix}_{(index + 1):03d}"
254
+ doc: str = "Automatically generated test\n\n"
255
+
256
+ # preprocess testoptions to split tuples
257
+ testoptions_split: Dict[str, Sequence[object]] = {}
258
+ for optname, optvalue in testoptions.items():
259
+ if isinstance(optname, str):
260
+ optvalue = cast("Sequence[object]", optvalue)
261
+ testoptions_split[optname] = optvalue
262
+ else:
263
+ # previously checked in add_option; ensure nothing has changed
264
+ optvalue = cast("Sequence[Sequence[object]]", optvalue)
265
+ assert len(optname) == len(optvalue)
266
+ for n, v in zip(optname, optvalue):
267
+ testoptions_split[n] = v
268
+
269
+ for optname, optvalue in testoptions_split.items():
270
+ if callable(optvalue):
271
+ optvalue = cast("FunctionType", optvalue)
272
+ if optvalue.__doc__ is None:
273
+ desc = "No docstring supplied"
274
+ else:
275
+ desc = optvalue.__doc__.split("\n")[0]
276
+ doc += f"\t{optname}: {optvalue.__qualname__} ({desc})\n"
277
+ else:
278
+ doc += f"\t{optname}: {optvalue!r}\n"
279
+
280
+ kwargs = self.kwargs_constant.copy()
281
+ kwargs.update(testoptions_split)
282
+
283
+ @functools.wraps(self.test_function)
284
+ async def _my_test(dut: object, kwargs: Dict[str, object] = kwargs) -> None:
285
+ await self.test_function(dut, *self.args, **kwargs)
286
+
287
+ _my_test.__doc__ = doc
288
+ _my_test.__name__ = name
289
+ _my_test.__qualname__ = name
290
+
291
+ if name in glbs:
292
+ self._log.error(
293
+ "Overwriting %s in module %s. "
294
+ "This causes a previously defined testcase not to be run. "
295
+ "Consider using the `name`, `prefix`, or `postfix` arguments to augment the name.",
296
+ name,
297
+ glbs["__name__"],
298
+ )
299
+
300
+ test = Test(
301
+ func=_my_test,
302
+ name=name,
303
+ module=glbs["__name__"],
304
+ timeout_time=timeout_time,
305
+ timeout_unit=timeout_unit,
306
+ expect_fail=expect_fail,
307
+ expect_error=expect_error,
308
+ skip=skip,
309
+ stage=stage,
310
+ )
311
+
312
+ glbs[test.name] = test
@@ -0,0 +1,42 @@
1
+ # Copyright cocotb contributors
2
+ # Licensed under the Revised BSD License, see LICENSE for details.
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+ """Collection of functions to control a running test and related exceptions."""
5
+
6
+ from typing import NoReturn, Type, Union
7
+
8
+ Failed: Type[BaseException]
9
+ try:
10
+ import pytest
11
+ except ModuleNotFoundError:
12
+ Failed = AssertionError
13
+ else:
14
+ try:
15
+ with pytest.raises(Exception):
16
+ pass
17
+ except BaseException as _raises_e:
18
+ Failed = type(_raises_e)
19
+ else:
20
+ assert False, "pytest.raises doesn't raise an exception when it fails"
21
+
22
+
23
+ class TestSuccess(BaseException):
24
+ """Implementation of :func:`pass_test`.
25
+
26
+ Users are *not* intended to catch this exception type.
27
+ """
28
+
29
+ def __init__(self, msg: Union[str, None]) -> None:
30
+ super().__init__(msg)
31
+ self.msg = msg
32
+
33
+
34
+ def pass_test(msg: Union[str, None] = None) -> NoReturn:
35
+ """Force a test to pass.
36
+
37
+ The test will end and enter termination phase when this is called.
38
+
39
+ Args:
40
+ msg: The message to display when the test passes.
41
+ """
42
+ raise TestSuccess(msg)
cocotb/_typing.py ADDED
@@ -0,0 +1,7 @@
1
+ # Copyright cocotb contributors
2
+ # Licensed under the Revised BSD License, see LICENSE for details.
3
+ # SPDX-License-Identifier: BSD-3-Clause
4
+ from cocotb._py_compat import Literal, TypeAlias
5
+
6
+ RoundMode: TypeAlias = Literal["error", "round", "ceil", "floor"]
7
+ TimeUnit: TypeAlias = Literal["step", "fs", "ps", "ns", "us", "ms", "sec"]