Tests: lttngtest: confusing comment regarding supported python versions
[lttng-tools.git] / tests / utils / lttngtest / tap_generator.py
CommitLineData
ef945e4d
JG
1#!/usr/bin/env python3
2#
3# Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7
8import contextlib
2a69bf14 9import os
ef945e4d 10import sys
2a69bf14 11import time
ce8470c9 12from typing import Iterator, Optional
ef945e4d
JG
13
14
f7169e41 15def _get_time_ns():
b26c3b64
JG
16 # type: () -> int
17
18 # time.monotonic is only available since Python 3.3. We don't support
19 # those older versions so we can simply assert here.
20 assert sys.version_info >= (3, 3, 0)
21
22 # time.monotonic_ns is only available for python >= 3.8,
23 # so the value is multiplied by 10^9 to maintain compatibility with
24 # older versions of the interpreter.
25 return int(time.monotonic() * 1000000000)
f7169e41
KS
26
27
ef945e4d 28class InvalidTestPlan(RuntimeError):
ce8470c9
MJ
29 def __init__(self, msg):
30 # type: (str) -> None
ef945e4d
JG
31 super().__init__(msg)
32
33
34class BailOut(RuntimeError):
ce8470c9
MJ
35 def __init__(self, msg):
36 # type: (str) -> None
ef945e4d
JG
37 super().__init__(msg)
38
39
40class TestCase:
ce8470c9
MJ
41 def __init__(
42 self,
43 tap_generator, # type: "TapGenerator"
44 description, # type: str
45 ):
46 self._tap_generator = tap_generator # type: "TapGenerator"
47 self._result = None # type: Optional[bool]
48 self._description = description # type: str
ef945e4d
JG
49
50 @property
ce8470c9
MJ
51 def result(self):
52 # type: () -> Optional[bool]
ef945e4d
JG
53 return self._result
54
55 @property
ce8470c9
MJ
56 def description(self):
57 # type: () -> str
ef945e4d
JG
58 return self._description
59
ce8470c9
MJ
60 def _set_result(self, result):
61 # type: (bool) -> None
ef945e4d
JG
62 if self._result is not None:
63 raise RuntimeError("Can't set test case result twice")
64
65 self._result = result
66 self._tap_generator.test(result, self._description)
67
ce8470c9
MJ
68 def success(self):
69 # type: () -> None
ef945e4d
JG
70 self._set_result(True)
71
ce8470c9
MJ
72 def fail(self):
73 # type: () -> None
ef945e4d
JG
74 self._set_result(False)
75
76
77# Produces a test execution report in the TAP format.
78class TapGenerator:
ce8470c9
MJ
79 def __init__(self, total_test_count):
80 # type: (int) -> None
ef945e4d
JG
81 if total_test_count <= 0:
82 raise ValueError("Test count must be greater than zero")
83
ce8470c9
MJ
84 self._total_test_count = total_test_count # type: int
85 self._last_test_case_id = 0 # type: int
86 self._printed_plan = False # type: bool
87 self._has_failure = False # type: bool
2a69bf14
KS
88 self._time_tests = True # type: bool
89 if os.getenv("TAP_AUTOTIME", "1") == "" or os.getenv("TAP_AUTOTIME", "1") == "0":
90 self._time_tests = False
f7169e41 91 self._last_time = _get_time_ns()
ef945e4d
JG
92
93 def __del__(self):
94 if self.remaining_test_cases > 0:
95 self.bail_out(
96 "Missing {remaining_test_cases} test cases".format(
97 remaining_test_cases=self.remaining_test_cases
98 )
99 )
100
101 @property
ce8470c9
MJ
102 def remaining_test_cases(self):
103 # type: () -> int
ef945e4d
JG
104 return self._total_test_count - self._last_test_case_id
105
ce8470c9
MJ
106 def _print(self, msg):
107 # type: (str) -> None
ef945e4d
JG
108 if not self._printed_plan:
109 print(
110 "1..{total_test_count}".format(total_test_count=self._total_test_count),
111 flush=True,
112 )
113 self._printed_plan = True
114
115 print(msg, flush=True)
116
ce8470c9
MJ
117 def skip_all(self, reason):
118 # type: (str) -> None
ef945e4d
JG
119 if self._last_test_case_id != 0:
120 raise RuntimeError("Can't skip all tests after running test cases")
121
122 if reason:
123 self._print("1..0 # Skip all: {reason}".format(reason=reason))
124
125 self._last_test_case_id = self._total_test_count
126
ce8470c9
MJ
127 def skip(self, reason, skip_count=1):
128 # type: (str, int) -> None
ef945e4d
JG
129 for i in range(skip_count):
130 self._last_test_case_id = self._last_test_case_id + 1
131 self._print(
132 "ok {test_number} # Skip: {reason}".format(
133 reason=reason, test_number=(i + self._last_test_case_id)
134 )
135 )
136
ce8470c9
MJ
137 def bail_out(self, reason):
138 # type: (str) -> None
ef945e4d
JG
139 self._print("Bail out! {reason}".format(reason=reason))
140 self._last_test_case_id = self._total_test_count
141 raise BailOut(reason)
142
ce8470c9
MJ
143 def test(self, result, description):
144 # type: (bool, str) -> None
f7169e41 145 duration = (_get_time_ns() - self._last_time) / 1000000
ef945e4d
JG
146 if self._last_test_case_id == self._total_test_count:
147 raise InvalidTestPlan("Executing too many tests")
148
149 if result is False:
150 self._has_failure = True
151
152 result_string = "ok" if result else "not ok"
153 self._last_test_case_id = self._last_test_case_id + 1
154 self._print(
155 "{result_string} {case_id} - {description}".format(
156 result_string=result_string,
157 case_id=self._last_test_case_id,
158 description=description,
159 )
160 )
2a69bf14
KS
161 if self._time_tests:
162 self._print("---\n duration_ms: {}\n...\n".format(duration))
f7169e41 163 self._last_time = _get_time_ns()
ef945e4d 164
ce8470c9
MJ
165 def ok(self, description):
166 # type: (str) -> None
ef945e4d
JG
167 self.test(True, description)
168
ce8470c9
MJ
169 def fail(self, description):
170 # type: (str) -> None
ef945e4d
JG
171 self.test(False, description)
172
173 @property
ce8470c9
MJ
174 def is_successful(self):
175 # type: () -> bool
ef945e4d
JG
176 return (
177 self._last_test_case_id == self._total_test_count and not self._has_failure
178 )
179
180 @contextlib.contextmanager
ce8470c9
MJ
181 def case(self, description):
182 # type: (str) -> Iterator[TestCase]
ef945e4d
JG
183 test_case = TestCase(self, description)
184 try:
185 yield test_case
186 except Exception as e:
187 self.diagnostic(
188 "Exception `{exception_type}` thrown during test case `{description}`, marking as failure.".format(
189 description=test_case.description, exception_type=type(e).__name__
190 )
191 )
192
193 if str(e) != "":
194 self.diagnostic(str(e))
195
196 test_case.fail()
197 finally:
198 if test_case.result is None:
199 test_case.success()
200
ce8470c9
MJ
201 def diagnostic(self, msg):
202 # type: (str) -> None
ef945e4d 203 print("# {msg}".format(msg=msg), file=sys.stderr, flush=True)
This page took 0.040064 seconds and 4 git commands to generate.