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