Commit | Line | Data |
---|---|---|
5d7e8359 PP |
1 | # SPDX-License-Identifier: MIT |
2 | # | |
3 | # Copyright (c) 2020 Philippe Proulx <pproulx@efficios.com> | |
4 | ||
5 | import unittest | |
6 | import subprocess | |
7 | import functools | |
8 | import signal | |
9 | import os | |
10 | import os.path | |
11 | import re | |
12 | import json | |
13 | ||
14 | ||
15 | # the `conds-triggers` program's full path | |
f5567ea8 | 16 | _CONDS_TRIGGERS_PATH = os.environ["BT_TESTS_LIB_CONDS_TRIGGER_BIN"] |
5d7e8359 PP |
17 | |
18 | ||
19 | # test methods are added by _create_tests() | |
20 | class LibPrePostCondsTestCase(unittest.TestCase): | |
21 | pass | |
22 | ||
23 | ||
24 | # a condition trigger descriptor (base) | |
25 | class _CondTriggerDescriptor: | |
26 | def __init__(self, index, trigger_name, cond_id): | |
27 | self._index = index | |
28 | self._trigger_name = trigger_name | |
29 | self._cond_id = cond_id | |
30 | ||
31 | @property | |
32 | def index(self): | |
33 | return self._index | |
34 | ||
35 | @property | |
36 | def trigger_name(self): | |
37 | return self._trigger_name | |
38 | ||
39 | @property | |
40 | def cond_id(self): | |
41 | return self._cond_id | |
42 | ||
43 | ||
44 | # precondition trigger descriptor | |
45 | class _PreCondTriggerDescriptor(_CondTriggerDescriptor): | |
46 | @property | |
47 | def type_str(self): | |
f5567ea8 | 48 | return "pre" |
5d7e8359 PP |
49 | |
50 | ||
51 | # postcondition trigger descriptor | |
52 | class _PostCondTriggerDescriptor(_CondTriggerDescriptor): | |
53 | @property | |
54 | def type_str(self): | |
f5567ea8 | 55 | return "post" |
5d7e8359 PP |
56 | |
57 | ||
58 | # test method template for `LibPrePostCondsTestCase` | |
59 | def _test(self, descriptor): | |
60 | # Execute: | |
61 | # | |
62 | # $ conds-triggers run <index> | |
63 | # | |
64 | # where `<index>` is the descriptor's index. | |
65 | with subprocess.Popen( | |
f5567ea8 | 66 | [_CONDS_TRIGGERS_PATH, "run", str(descriptor.index)], |
5d7e8359 PP |
67 | stderr=subprocess.PIPE, |
68 | universal_newlines=True, | |
69 | ) as proc: | |
70 | # wait for termination and get standard output/error data | |
71 | timeout = 5 | |
72 | ||
73 | try: | |
74 | # wait for program end and get standard error pipe's contents | |
75 | _, stderr = proc.communicate(timeout=timeout) | |
76 | except subprocess.TimeoutExpired: | |
f5567ea8 | 77 | self.fail("Process hanged for {} seconds".format(timeout)) |
5d7e8359 PP |
78 | return |
79 | ||
80 | # assert that program aborted (only available on POSIX) | |
f5567ea8 | 81 | if os.name == "posix": |
5d7e8359 PP |
82 | self.assertEqual(proc.returncode, -int(signal.SIGABRT)) |
83 | ||
84 | # assert that the standard error text contains the condition ID | |
f5567ea8 | 85 | text = "Condition ID: `{}`.".format(descriptor.cond_id) |
5d7e8359 PP |
86 | self.assertIn(text, stderr) |
87 | ||
88 | ||
89 | # Condition trigger descriptors from the JSON array returned by | |
90 | # | |
91 | # $ conds-triggers list | |
92 | def _cond_trigger_descriptors_from_json(json_descr_array): | |
93 | descriptors = [] | |
94 | descriptor_names = set() | |
95 | ||
96 | for index, json_descr in enumerate(json_descr_array): | |
97 | # sanity check: check for duplicate | |
f5567ea8 | 98 | trigger_name = json_descr["name"] |
5d7e8359 PP |
99 | |
100 | if trigger_name in descriptor_names: | |
101 | raise ValueError( | |
f5567ea8 | 102 | "Duplicate condition trigger name `{}`".format(trigger_name) |
5d7e8359 PP |
103 | ) |
104 | ||
105 | # condition ID | |
f5567ea8 | 106 | cond_id = json_descr["cond-id"] |
5d7e8359 | 107 | |
f5567ea8 | 108 | if cond_id.startswith("pre"): |
5d7e8359 | 109 | cond_type = _PreCondTriggerDescriptor |
f5567ea8 | 110 | elif cond_id.startswith("post"): |
5d7e8359 PP |
111 | cond_type = _PostCondTriggerDescriptor |
112 | else: | |
f5567ea8 | 113 | raise ValueError("Invalid condition ID `{}`".format(cond_id)) |
5d7e8359 PP |
114 | |
115 | descriptors.append(cond_type(index, trigger_name, cond_id)) | |
116 | descriptor_names.add(trigger_name) | |
117 | ||
118 | return descriptors | |
119 | ||
120 | ||
121 | # creates the individual tests of `LibPrePostCondsTestCase` | |
122 | def _create_tests(): | |
123 | # Execute `conds-triggers list` to get a JSON array of condition | |
124 | # trigger descriptors. | |
125 | json_descr_array = json.loads( | |
f5567ea8 | 126 | subprocess.check_output([_CONDS_TRIGGERS_PATH, "list"], universal_newlines=True) |
5d7e8359 PP |
127 | ) |
128 | ||
129 | # get condition trigger descriptor objects from JSON | |
130 | descriptors = _cond_trigger_descriptors_from_json(json_descr_array) | |
131 | ||
132 | # create test methods | |
133 | for descriptor in descriptors: | |
134 | # test method name | |
f5567ea8 FD |
135 | test_meth_name = "test_{}".format( |
136 | re.sub(r"[^a-zA-Z0-9_]", "_", descriptor.trigger_name) | |
5d7e8359 PP |
137 | ) |
138 | ||
139 | # test method | |
140 | meth = functools.partialmethod(_test, descriptor) | |
141 | setattr(LibPrePostCondsTestCase, test_meth_name, meth) | |
142 | ||
143 | ||
144 | _create_tests() | |
145 | ||
146 | ||
f5567ea8 | 147 | if __name__ == "__main__": |
5d7e8359 | 148 | unittest.main() |