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.
- cocotb/_ANSI.py +65 -0
- cocotb/__init__.py +127 -0
- cocotb/_base_triggers.py +515 -0
- cocotb/_bridge.py +186 -0
- cocotb/_decorators.py +515 -0
- cocotb/_deprecation.py +36 -0
- cocotb/_exceptions.py +7 -0
- cocotb/_extended_awaitables.py +419 -0
- cocotb/_gpi_triggers.py +385 -0
- cocotb/_init.py +301 -0
- cocotb/_outcomes.py +54 -0
- cocotb/_profiling.py +46 -0
- cocotb/_py_compat.py +150 -0
- cocotb/_scheduler.py +448 -0
- cocotb/_test.py +248 -0
- cocotb/_test_factory.py +312 -0
- cocotb/_test_functions.py +42 -0
- cocotb/_typing.py +7 -0
- cocotb/_utils.py +274 -0
- cocotb/_version.py +4 -0
- cocotb/_xunit_reporter.py +103 -0
- cocotb/clock.py +419 -0
- cocotb/debug.py +24 -0
- cocotb/handle.py +1752 -0
- cocotb/libs/cocotb.dll +0 -0
- cocotb/libs/cocotb.exp +0 -0
- cocotb/libs/cocotb.lib +0 -0
- cocotb/libs/cocotbfli_modelsim.dll +0 -0
- cocotb/libs/cocotbfli_modelsim.exp +0 -0
- cocotb/libs/cocotbfli_modelsim.lib +0 -0
- cocotb/libs/cocotbutils.dll +0 -0
- cocotb/libs/cocotbutils.exp +0 -0
- cocotb/libs/cocotbutils.lib +0 -0
- cocotb/libs/cocotbvhpi_aldec.dll +0 -0
- cocotb/libs/cocotbvhpi_aldec.exp +0 -0
- cocotb/libs/cocotbvhpi_aldec.lib +0 -0
- cocotb/libs/cocotbvhpi_modelsim.dll +0 -0
- cocotb/libs/cocotbvhpi_modelsim.exp +0 -0
- cocotb/libs/cocotbvhpi_modelsim.lib +0 -0
- cocotb/libs/cocotbvhpi_nvc.dll +0 -0
- cocotb/libs/cocotbvhpi_nvc.exp +0 -0
- cocotb/libs/cocotbvhpi_nvc.lib +0 -0
- cocotb/libs/cocotbvpi_aldec.dll +0 -0
- cocotb/libs/cocotbvpi_aldec.exp +0 -0
- cocotb/libs/cocotbvpi_aldec.lib +0 -0
- cocotb/libs/cocotbvpi_ghdl.dll +0 -0
- cocotb/libs/cocotbvpi_ghdl.exp +0 -0
- cocotb/libs/cocotbvpi_ghdl.lib +0 -0
- cocotb/libs/cocotbvpi_icarus.exp +0 -0
- cocotb/libs/cocotbvpi_icarus.lib +0 -0
- cocotb/libs/cocotbvpi_icarus.vpl +0 -0
- cocotb/libs/cocotbvpi_modelsim.dll +0 -0
- cocotb/libs/cocotbvpi_modelsim.exp +0 -0
- cocotb/libs/cocotbvpi_modelsim.lib +0 -0
- cocotb/libs/embed.dll +0 -0
- cocotb/libs/embed.exp +0 -0
- cocotb/libs/embed.lib +0 -0
- cocotb/libs/gpi.dll +0 -0
- cocotb/libs/gpi.exp +0 -0
- cocotb/libs/gpi.lib +0 -0
- cocotb/libs/gpilog.dll +0 -0
- cocotb/libs/gpilog.exp +0 -0
- cocotb/libs/gpilog.lib +0 -0
- cocotb/libs/pygpilog.dll +0 -0
- cocotb/libs/pygpilog.exp +0 -0
- cocotb/libs/pygpilog.lib +0 -0
- cocotb/logging.py +417 -0
- cocotb/py.typed +0 -0
- cocotb/queue.py +235 -0
- cocotb/regression.py +900 -0
- cocotb/result.py +38 -0
- cocotb/share/def/.gitignore +2 -0
- cocotb/share/def/README.md +4 -0
- cocotb/share/def/aldec.def +61 -0
- cocotb/share/def/aldec.exp +0 -0
- cocotb/share/def/aldec.lib +0 -0
- cocotb/share/def/ghdl.def +43 -0
- cocotb/share/def/ghdl.exp +0 -0
- cocotb/share/def/ghdl.lib +0 -0
- cocotb/share/def/icarus.def +43 -0
- cocotb/share/def/icarus.exp +0 -0
- cocotb/share/def/icarus.lib +0 -0
- cocotb/share/def/modelsim.def +138 -0
- cocotb/share/def/modelsim.exp +0 -0
- cocotb/share/def/modelsim.lib +0 -0
- cocotb/share/def/nvcvhpi.def +18 -0
- cocotb/share/def/nvcvhpi.exp +0 -0
- cocotb/share/def/nvcvhpi.lib +0 -0
- cocotb/share/include/cocotb_utils.h +70 -0
- cocotb/share/include/embed.h +33 -0
- cocotb/share/include/exports.h +20 -0
- cocotb/share/include/gpi.h +459 -0
- cocotb/share/include/gpi_logging.h +291 -0
- cocotb/share/include/py_gpi_logging.h +33 -0
- cocotb/share/include/vhpi_user_ext.h +26 -0
- cocotb/share/include/vpi_user_ext.h +33 -0
- cocotb/share/lib/verilator/verilator.cpp +209 -0
- cocotb/simtime.py +238 -0
- cocotb/simulator.cp38-win32.exp +0 -0
- cocotb/simulator.cp38-win32.lib +0 -0
- cocotb/simulator.cp38-win32.pyd +0 -0
- cocotb/simulator.cp38-win32.pyd.2.config +8 -0
- cocotb/simulator.pyi +107 -0
- cocotb/task.py +590 -0
- cocotb/triggers.py +67 -0
- cocotb/types/__init__.py +31 -0
- cocotb/types/_abstract_array.py +151 -0
- cocotb/types/_array.py +297 -0
- cocotb/types/_indexing.py +17 -0
- cocotb/types/_logic.py +333 -0
- cocotb/types/_logic_array.py +884 -0
- cocotb/types/_range.py +197 -0
- cocotb/types/_resolve.py +76 -0
- cocotb/utils.py +110 -0
- cocotb-2.0.1.dist-info/LICENSE +29 -0
- cocotb-2.0.1.dist-info/METADATA +44 -0
- cocotb-2.0.1.dist-info/RECORD +152 -0
- cocotb-2.0.1.dist-info/WHEEL +5 -0
- cocotb-2.0.1.dist-info/entry_points.txt +2 -0
- cocotb-2.0.1.dist-info/top_level.txt +18 -0
- cocotb_tools/__init__.py +0 -0
- cocotb_tools/_coverage.py +33 -0
- cocotb_tools/_vendor/__init__.py +3 -0
- cocotb_tools/_vendor/distutils_version.py +346 -0
- cocotb_tools/check_results.py +65 -0
- cocotb_tools/combine_results.py +152 -0
- cocotb_tools/config.py +242 -0
- cocotb_tools/ipython_support.py +99 -0
- cocotb_tools/makefiles/Makefile.deprecations +27 -0
- cocotb_tools/makefiles/Makefile.inc +198 -0
- cocotb_tools/makefiles/Makefile.sim +96 -0
- cocotb_tools/makefiles/simulators/Makefile.activehdl +72 -0
- cocotb_tools/makefiles/simulators/Makefile.cvc +61 -0
- cocotb_tools/makefiles/simulators/Makefile.dsim +39 -0
- cocotb_tools/makefiles/simulators/Makefile.ghdl +84 -0
- cocotb_tools/makefiles/simulators/Makefile.icarus +80 -0
- cocotb_tools/makefiles/simulators/Makefile.ius +93 -0
- cocotb_tools/makefiles/simulators/Makefile.modelsim +9 -0
- cocotb_tools/makefiles/simulators/Makefile.nvc +60 -0
- cocotb_tools/makefiles/simulators/Makefile.questa +29 -0
- cocotb_tools/makefiles/simulators/Makefile.questa-compat +143 -0
- cocotb_tools/makefiles/simulators/Makefile.questa-qisqrun +149 -0
- cocotb_tools/makefiles/simulators/Makefile.riviera +144 -0
- cocotb_tools/makefiles/simulators/Makefile.vcs +65 -0
- cocotb_tools/makefiles/simulators/Makefile.verilator +79 -0
- cocotb_tools/makefiles/simulators/Makefile.xcelium +104 -0
- cocotb_tools/py.typed +0 -0
- cocotb_tools/runner.py +2001 -0
- cocotb_tools/sim_versions.py +140 -0
- pygpi/__init__.py +0 -0
- pygpi/entry.py +42 -0
- pygpi/py.typed +0 -0
cocotb/_utils.py
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Copyright (c) 2013 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
|
+
"""Utilities for implementors."""
|
|
8
|
+
|
|
9
|
+
import traceback
|
|
10
|
+
import types
|
|
11
|
+
from enum import Enum, IntEnum
|
|
12
|
+
from functools import update_wrapper, wraps
|
|
13
|
+
from types import TracebackType
|
|
14
|
+
from typing import (
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
Any,
|
|
17
|
+
Iterable,
|
|
18
|
+
List,
|
|
19
|
+
Optional,
|
|
20
|
+
Tuple,
|
|
21
|
+
Type,
|
|
22
|
+
TypeVar,
|
|
23
|
+
Union,
|
|
24
|
+
cast,
|
|
25
|
+
overload,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
ExceptionTuple = Tuple[
|
|
29
|
+
Type[BaseException], BaseException, TracebackType
|
|
30
|
+
] # TypeAlias in Python 3.10
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@overload
|
|
34
|
+
def remove_traceback_frames(
|
|
35
|
+
tb_or_exc: ExceptionTuple, frame_names: List[str]
|
|
36
|
+
) -> ExceptionTuple: ...
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def remove_traceback_frames(
|
|
41
|
+
tb_or_exc: BaseException, frame_names: List[str]
|
|
42
|
+
) -> BaseException: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def remove_traceback_frames(
|
|
47
|
+
tb_or_exc: TracebackType, frame_names: List[str]
|
|
48
|
+
) -> TracebackType: ...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def remove_traceback_frames(
|
|
52
|
+
tb_or_exc: Union[ExceptionTuple, BaseException, TracebackType],
|
|
53
|
+
frame_names: List[str],
|
|
54
|
+
) -> Union[ExceptionTuple, BaseException, TracebackType]:
|
|
55
|
+
"""
|
|
56
|
+
Strip leading frames from a traceback
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
tb_or_exc:
|
|
60
|
+
Object to strip frames from. If an exception is passed, creates
|
|
61
|
+
a copy of the exception with a new shorter traceback. If a tuple
|
|
62
|
+
from `sys.exc_info` is passed, returns the same tuple with the
|
|
63
|
+
traceback shortened
|
|
64
|
+
frame_names:
|
|
65
|
+
Names of the frames to strip, which must be present at the top of the Traceback or Exception.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Traceback or Exception passed to the function with the *frame_names* stripped out.
|
|
69
|
+
"""
|
|
70
|
+
# self-invoking overloads
|
|
71
|
+
if isinstance(tb_or_exc, BaseException):
|
|
72
|
+
exc: BaseException = tb_or_exc
|
|
73
|
+
return exc.with_traceback(
|
|
74
|
+
remove_traceback_frames(
|
|
75
|
+
cast("TracebackType", exc.__traceback__), frame_names
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
elif isinstance(tb_or_exc, tuple):
|
|
79
|
+
exc_type, exc_value, exc_tb = tb_or_exc
|
|
80
|
+
exc_tb = remove_traceback_frames(exc_tb, frame_names)
|
|
81
|
+
return exc_type, exc_value, exc_tb
|
|
82
|
+
# base case
|
|
83
|
+
else:
|
|
84
|
+
tb: TracebackType = tb_or_exc
|
|
85
|
+
for frame_name in frame_names:
|
|
86
|
+
# the assert and cast are there assuming the frame_names being removed are correct
|
|
87
|
+
assert tb.tb_frame.f_code.co_name == frame_name
|
|
88
|
+
tb = cast("TracebackType", tb.tb_next)
|
|
89
|
+
return tb
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def walk_coro_stack(
|
|
93
|
+
coro: "types.CoroutineType[Any, Any, Any]",
|
|
94
|
+
) -> Iterable[Tuple[types.FrameType, int]]:
|
|
95
|
+
"""Walk down the coroutine stack, starting at *coro*.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
coro: The :class:`coroutine` object to traverse.
|
|
99
|
+
|
|
100
|
+
Yields:
|
|
101
|
+
Frame and line number of each frame in the coroutine.
|
|
102
|
+
"""
|
|
103
|
+
c: Optional[types.CoroutineType[Any, Any, Any]] = coro
|
|
104
|
+
while c is not None:
|
|
105
|
+
try:
|
|
106
|
+
f = c.cr_frame
|
|
107
|
+
except AttributeError:
|
|
108
|
+
break
|
|
109
|
+
else:
|
|
110
|
+
c = c.cr_await
|
|
111
|
+
if f is not None:
|
|
112
|
+
yield (f, f.f_lineno)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def extract_coro_stack(
|
|
116
|
+
coro: "types.CoroutineType[Any, Any, Any]", limit: Optional[int] = None
|
|
117
|
+
) -> traceback.StackSummary:
|
|
118
|
+
r"""Create a list of pre-processed entries from the coroutine stack.
|
|
119
|
+
|
|
120
|
+
This is based on :func:`traceback.extract_tb`.
|
|
121
|
+
|
|
122
|
+
If *limit* is omitted or ``None``, all entries are extracted.
|
|
123
|
+
The list is a :class:`traceback.StackSummary` object, and
|
|
124
|
+
each entry in the list is a :class:`traceback.FrameSummary` object
|
|
125
|
+
containing attributes ``filename``, ``lineno``, ``name``, and ``line``
|
|
126
|
+
representing the information that is usually printed for a stack
|
|
127
|
+
trace. The line is a string with leading and trailing
|
|
128
|
+
whitespace stripped; if the source is not available it is ``None``.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
coro: The :class:`coroutine` object from which to extract a stack.
|
|
132
|
+
level: The maximum number of frames from *coro*\ s stack to extract.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The stack of *coro*.
|
|
136
|
+
"""
|
|
137
|
+
return traceback.StackSummary.extract(walk_coro_stack(coro), limit=limit)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
EnumT = TypeVar("EnumT", bound=Enum)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class DocEnum(Enum):
|
|
144
|
+
"""Like :class:`enum.Enum`, but allows documenting enum values.
|
|
145
|
+
|
|
146
|
+
Documentation for enum members can be optionally added by setting enum values to a tuple of the intended value and the docstring.
|
|
147
|
+
This adds the provided docstring to the ``__doc__`` field of the enum value.
|
|
148
|
+
|
|
149
|
+
.. code-block:: python
|
|
150
|
+
|
|
151
|
+
class MyEnum(DocEnum):
|
|
152
|
+
\"\"\"Class documentation\"\"\"
|
|
153
|
+
|
|
154
|
+
VALUE1 = 1, "Value documentation"
|
|
155
|
+
VALUE2 = 2 # no documentation
|
|
156
|
+
|
|
157
|
+
Taken from :ref:`this StackOverflow answer <https://stackoverflow.com/questions/50473951/how-can-i-attach-documentation-to-members-of-a-python-enum/50473952#50473952>`
|
|
158
|
+
by :ref:`Eric Wieser <https://stackoverflow.com/users/102441/eric>`,
|
|
159
|
+
as recommended by the ``enum_tools`` documentation.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __new__(cls: Type[EnumT], value: object, doc: Optional[str] = None) -> EnumT:
|
|
163
|
+
# super().__new__() assumes the value is already an enum value
|
|
164
|
+
# so we side step that and create a raw object and fill in _value_
|
|
165
|
+
self = object.__new__(cls)
|
|
166
|
+
self._value_ = value
|
|
167
|
+
if doc is not None:
|
|
168
|
+
self.__doc__ = doc
|
|
169
|
+
return self
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
IntEnumT = TypeVar("IntEnumT", bound=IntEnum)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class DocIntEnum(IntEnum):
|
|
176
|
+
"""Like DocEnum but for :class:`IntEnum` enum types."""
|
|
177
|
+
|
|
178
|
+
def __new__(cls: Type[IntEnumT], value: int, doc: Optional[str] = None) -> IntEnumT:
|
|
179
|
+
self = int.__new__(cls, value)
|
|
180
|
+
self._value_ = value
|
|
181
|
+
if doc is not None:
|
|
182
|
+
self.__doc__ = doc
|
|
183
|
+
return self
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if TYPE_CHECKING:
|
|
187
|
+
F = TypeVar("F")
|
|
188
|
+
|
|
189
|
+
def cached_method(f: F) -> F: ...
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
|
|
193
|
+
class cached_method:
|
|
194
|
+
def __init__(self, method):
|
|
195
|
+
self._method = method
|
|
196
|
+
update_wrapper(self, method)
|
|
197
|
+
|
|
198
|
+
def __get__(self, instance, objtype=None):
|
|
199
|
+
if instance is None:
|
|
200
|
+
return self
|
|
201
|
+
|
|
202
|
+
cache = {}
|
|
203
|
+
|
|
204
|
+
@wraps(self._method)
|
|
205
|
+
def lookup(*args, **kwargs):
|
|
206
|
+
key = (args, tuple(kwargs.items()))
|
|
207
|
+
try:
|
|
208
|
+
return cache[key]
|
|
209
|
+
except KeyError:
|
|
210
|
+
res = self._method(instance, *args, **kwargs)
|
|
211
|
+
cache[key] = res
|
|
212
|
+
return res
|
|
213
|
+
|
|
214
|
+
lookup.cache = cache
|
|
215
|
+
|
|
216
|
+
setattr(instance, self._method.__name__, lookup)
|
|
217
|
+
return lookup
|
|
218
|
+
|
|
219
|
+
def __call__(self, instance, *args, **kwargs):
|
|
220
|
+
func = getattr(instance, self._method.__name__)
|
|
221
|
+
return func(*args, **kwargs)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
T = TypeVar("T")
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if TYPE_CHECKING:
|
|
228
|
+
|
|
229
|
+
def singleton(orig_cls: T) -> T: ...
|
|
230
|
+
|
|
231
|
+
else:
|
|
232
|
+
|
|
233
|
+
def singleton(orig_cls):
|
|
234
|
+
"""Class decorator which turns a type into a Singleton type."""
|
|
235
|
+
orig_new = orig_cls.__new__
|
|
236
|
+
orig_init = orig_cls.__init__
|
|
237
|
+
instance = None
|
|
238
|
+
|
|
239
|
+
@wraps(orig_cls.__new__)
|
|
240
|
+
def __new__(cls, *args, **kwargs):
|
|
241
|
+
nonlocal instance
|
|
242
|
+
if instance is None:
|
|
243
|
+
instance = orig_new(cls, *args, **kwargs)
|
|
244
|
+
orig_init(instance, *args, **kwargs)
|
|
245
|
+
return instance
|
|
246
|
+
|
|
247
|
+
@wraps(orig_cls.__init__)
|
|
248
|
+
def __init__(self, *args, **kwargs):
|
|
249
|
+
pass
|
|
250
|
+
|
|
251
|
+
orig_cls.__new__ = __new__
|
|
252
|
+
orig_cls.__init__ = __init__
|
|
253
|
+
return orig_cls
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def pointer_str(obj: object) -> str:
|
|
257
|
+
"""Get the memory address of *obj* as used in :meth:`object.__repr__`.
|
|
258
|
+
|
|
259
|
+
This is equivalent to ``sprintf("%p", id(obj))``, but Python does not
|
|
260
|
+
support ``%p``.
|
|
261
|
+
"""
|
|
262
|
+
full_repr = object.__repr__(obj) # gives "<{type} object at {address}>"
|
|
263
|
+
return full_repr.rsplit(" ", 1)[1][:-1]
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def safe_divide(a: float, b: float) -> float:
|
|
267
|
+
"""Used when computing time ratios to ensure no exception is raised if either time is 0."""
|
|
268
|
+
try:
|
|
269
|
+
return a / b
|
|
270
|
+
except ZeroDivisionError:
|
|
271
|
+
if a == 0:
|
|
272
|
+
return float("nan")
|
|
273
|
+
else:
|
|
274
|
+
return float("inf")
|
cocotb/_version.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Copyright cocotb contributors
|
|
2
|
+
# Copyright (c) 2013 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
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import xml.etree.ElementTree as ET
|
|
10
|
+
from typing import Union
|
|
11
|
+
from xml.etree.ElementTree import Element, SubElement
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Shamelessly ripped from pytest source code.
|
|
15
|
+
# https://github.com/pytest-dev/pytest/blob/d036b12bb6fa09f9a8a3b690cc7336113c93fa44/src/_pytest/junitxml.py#L37C1-L61C50
|
|
16
|
+
def bin_xml_escape(arg: object) -> str:
|
|
17
|
+
r"""Visually escape invalid XML characters.
|
|
18
|
+
|
|
19
|
+
For example, transforms
|
|
20
|
+
'hello\aworld\b'
|
|
21
|
+
into
|
|
22
|
+
'hello#x07world#x08'
|
|
23
|
+
Note that the #xABs are *not* XML escapes - missing the ampersand «.
|
|
24
|
+
The idea is to escape visually for the user rather than for XML itself.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def repl(matchobj: "re.Match[str]") -> str:
|
|
28
|
+
i = ord(matchobj.group())
|
|
29
|
+
if i <= 0xFF:
|
|
30
|
+
return f"#x{i:02X}"
|
|
31
|
+
else:
|
|
32
|
+
return f"#x{i:04X}"
|
|
33
|
+
|
|
34
|
+
# The spec range of valid chars is:
|
|
35
|
+
# Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
|
|
36
|
+
# For an unknown(?) reason, we disallow #x7F (DEL) as well.
|
|
37
|
+
illegal_xml_re = (
|
|
38
|
+
"[^\u0009\u000a\u000d\u0020-\u007e\u0080-\ud7ff\ue000-\ufffd\u10000-\u10ffff]"
|
|
39
|
+
)
|
|
40
|
+
return re.sub(illegal_xml_re, repl, str(arg))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if sys.version_info < (3, 9):
|
|
44
|
+
|
|
45
|
+
def indent(elem: Element, level: int = 0) -> None:
|
|
46
|
+
i = "\n" + level * " "
|
|
47
|
+
if len(elem):
|
|
48
|
+
if not elem.text or not elem.text.strip():
|
|
49
|
+
elem.text = i + " "
|
|
50
|
+
if not elem.tail or not elem.tail.strip():
|
|
51
|
+
elem.tail = i
|
|
52
|
+
for sub_elem in elem:
|
|
53
|
+
indent(sub_elem, level + 1)
|
|
54
|
+
if not sub_elem.tail or not sub_elem.tail.strip():
|
|
55
|
+
sub_elem.tail = i
|
|
56
|
+
elif level and (not elem.tail or not elem.tail.strip()):
|
|
57
|
+
elem.tail = i
|
|
58
|
+
|
|
59
|
+
else:
|
|
60
|
+
from xml.etree.ElementTree import indent
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class XUnitReporter:
|
|
64
|
+
last_testsuite: Element
|
|
65
|
+
last_testcase: Element
|
|
66
|
+
|
|
67
|
+
def __init__(self, filename: str = "results.xml") -> None:
|
|
68
|
+
self.results = Element("testsuites", name="results")
|
|
69
|
+
self.filename = filename
|
|
70
|
+
|
|
71
|
+
def add_testsuite(self, **kwargs: str) -> Element:
|
|
72
|
+
self.last_testsuite = SubElement(self.results, "testsuite", kwargs)
|
|
73
|
+
return self.last_testsuite
|
|
74
|
+
|
|
75
|
+
def add_testcase(
|
|
76
|
+
self, testsuite: Union[Element, None] = None, **kwargs: str
|
|
77
|
+
) -> Element:
|
|
78
|
+
if testsuite is None:
|
|
79
|
+
testsuite = self.last_testsuite
|
|
80
|
+
self.last_testcase = SubElement(testsuite, "testcase", kwargs)
|
|
81
|
+
return self.last_testcase
|
|
82
|
+
|
|
83
|
+
def add_property(
|
|
84
|
+
self, testsuite: Union[Element, None] = None, **kwargs: str
|
|
85
|
+
) -> Element:
|
|
86
|
+
if testsuite is None:
|
|
87
|
+
testsuite = self.last_testsuite
|
|
88
|
+
self.last_property = SubElement(testsuite, "property", kwargs)
|
|
89
|
+
return self.last_property
|
|
90
|
+
|
|
91
|
+
def add_failure(self, testcase: Union[Element, None] = None, **kwargs: str) -> None:
|
|
92
|
+
if testcase is None:
|
|
93
|
+
testcase = self.last_testcase
|
|
94
|
+
SubElement(testcase, "failure", kwargs)
|
|
95
|
+
|
|
96
|
+
def add_skipped(self, testcase: Union[Element, None] = None, **kwargs: str) -> None:
|
|
97
|
+
if testcase is None:
|
|
98
|
+
testcase = self.last_testcase
|
|
99
|
+
SubElement(testcase, "skipped", kwargs)
|
|
100
|
+
|
|
101
|
+
def write(self) -> None:
|
|
102
|
+
indent(self.results)
|
|
103
|
+
ET.ElementTree(self.results).write(self.filename, encoding="UTF-8")
|