Skip to content

Commit c9201b3

Browse files
Report what failed in metabox tests (infra) (#1655)
* Init * Report steps that failed * Actually return something in all operators and check for False This will avoid me breaking again Metabox with None == True, None means an api forgot to return, lets mark the step as failed * Also catch NotFound from Put * Better logging Co-authored-by: Fernando Bravo <[email protected]> * Also return on Send, Sleep and Signal --------- Co-authored-by: Fernando Bravo <[email protected]>
1 parent 4305ba1 commit c9201b3

File tree

3 files changed

+52
-27
lines changed

3 files changed

+52
-27
lines changed

metabox/metabox/core/actions.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
This module defines the Actions classes.
2121
2222
"""
23+
import pprint
2324

2425
__all__ = [
2526
"Start",
@@ -52,7 +53,17 @@ def __init__(self, *args, **kwargs):
5253

5354
def __call__(self, scn):
5455
assert self.handler is not None
55-
getattr(scn, self.handler)(*self.args, **self.kwargs)
56+
return getattr(scn, self.handler)(*self.args, **self.kwargs)
57+
58+
def __str__(self):
59+
kwargs_str = ", ".join(
60+
f"{key}={pprint.pformat(value)}"
61+
for key, value in self.kwargs.items()
62+
)
63+
args_str = ", ".join(pprint.pformat(arg) for arg in self.args)
64+
# if both are defined, separate them with a comma, else nothing
65+
arg_repr_str = ", ".join(arg for arg in (args_str, kwargs_str) if arg)
66+
return "{}({})".format(type(self).__name__, arg_repr_str)
5667

5768

5869
class Start(ActionBase):

metabox/metabox/core/runner.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,15 +284,20 @@ def run(self):
284284
)
285285
)
286286
scn.run()
287-
if not scn.has_passed():
287+
if scn.failures:
288288
self.failed = True
289289
logger.error(scenario_description + " scenario has failed.")
290290

291-
# let's escape < from the output to avoid confusing loguru
291+
# let's escape < from the all outputs to avoid confusing loguru
292292
# loguru assumes that <> is used for colorizing
293-
output = scn.get_output_streams().strip().replace("<", r"\<")
293+
logger.error("The following steps failed")
294+
for failed_steps in scn.failures:
295+
step = str(failed_steps).replace("<", r"\<")
296+
logger.error(" - {}".format(step))
294297

298+
output = scn.get_output_streams().strip().replace("<", r"\<")
295299
logger.error("Scenario output:\n" + output)
300+
296301
if self.hold_on_fail:
297302
if scn.mode == "remote":
298303
msg = (

metabox/metabox/core/scenario.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import time
2828
import shlex
2929

30+
from pylxd.exceptions import NotFound
31+
3032
from metabox.core.actions import Start, Expect, Send, SelectTestPlan
3133
from metabox.core.aggregator import aggregator
3234

@@ -70,7 +72,7 @@ def __init__(
7072
self.agent_machine = None
7173
self.agent_revision = agent_revision
7274
self.start_session = True
73-
self._checks = []
75+
self.failures = []
7476
self._ret_code = None
7577
self._stdout = ""
7678
self._stderr = ""
@@ -82,10 +84,6 @@ def get_output_streams(self):
8284
return self._pts.stdout_data_full
8385
return self._outstr_full
8486

85-
def has_passed(self):
86-
"""Check whether all the assertions passed."""
87-
return all(self._checks)
88-
8987
def run(self):
9088
# Simple scenarios don't need to specify a START step
9189
# If there's no START step, add one unless the scenario
@@ -109,9 +107,11 @@ def run(self):
109107
break
110108
step.kwargs["interactive"] = interactive
111109
try:
112-
step(self)
113-
except (TimeoutError, ConnectionError):
114-
self._checks.append(False)
110+
# step that fail explicitly return false or raise an exception
111+
if not step(self):
112+
self.failures.append(step)
113+
except (TimeoutError, ConnectionError, NotFound):
114+
self.failures.append(step)
115115
break
116116
if self._pts:
117117
self._stdout = self._pts.stdout_data_full
@@ -126,17 +126,15 @@ def _assign_outcome(self, ret_code, stdout, stderr, outstr_full):
126126
self._stderr = stderr
127127
self._outstr_full = outstr_full
128128

129-
# TODO: add storing of what actually failed in the assert methods
130129
def assert_printed(self, pattern):
131130
"""
132131
Check if during Checkbox execution a line produced that matches the
133132
pattern.
134133
:param patter: regular expresion to check against the lines.
135134
"""
136135
regex = re.compile(pattern)
137-
self._checks.append(
138-
bool(regex.search(self._stdout))
139-
or bool(regex.search(self._stderr))
136+
return bool(regex.search(self._stdout)) or bool(
137+
regex.search(self._stderr)
140138
)
141139

142140
def assert_not_printed(self, pattern):
@@ -150,20 +148,20 @@ def assert_not_printed(self, pattern):
150148
found = regex.search(self._pts.stdout_data_full)
151149
else:
152150
found = regex.search(self._stdout) or regex.search(self._stderr)
153-
self._checks.append(not found)
151+
return not found
154152

155153
def assert_ret_code(self, code):
156154
"""Check if Checkbox returned given code."""
157-
self._checks.append(code == self._ret_code)
155+
return code == self._ret_code
158156

159157
def assertIn(self, member, container):
160-
self._checks.append(member in container)
158+
return member in container
161159

162160
def assertEqual(self, first, second):
163-
self._checks.append(first == second)
161+
return first == second
164162

165163
def assertNotEqual(self, first, second):
166-
self._checks.append(first != second)
164+
return first != second
167165

168166
def start(self, cmd="", interactive=False, timeout=0):
169167
if self.mode == "remote":
@@ -185,6 +183,7 @@ def start(self, cmd="", interactive=False, timeout=0):
185183
self._pts = outcome
186184
else:
187185
self._assign_outcome(*outcome)
186+
return True # return code is checked with a different operator
188187

189188
def start_all(self, interactive=False, timeout=0):
190189
self.start_agent()
@@ -214,28 +213,33 @@ def start_agent(self, force=False):
214213
def expect(self, data, timeout=60):
215214
assert self._pts is not None
216215
outcome = self._pts.expect(data, timeout)
217-
self._checks.append(outcome)
216+
return outcome
218217

219218
def expect_not(self, data, timeout=60):
220219
assert self._pts is not None
221220
outcome = self._pts.expect_not(data, timeout)
222-
self._checks.append(outcome)
221+
return outcome
223222

224223
def send(self, data):
225224
assert self._pts is not None
226225
self._pts.send(data.encode("utf-8"), binary=True)
226+
# send raises an exception on failure
227+
return True
227228

228229
def sleep(self, secs):
229230
time.sleep(secs)
231+
return True
230232

231233
def signal(self, signal):
232234
assert self._pts is not None
233235
self._pts.send_signal(signal)
236+
# same as send, this uses send under the hood
237+
return True
234238

235239
def select_test_plan(self, testplan_id, timeout=60):
236240
assert self._pts is not None
237241
outcome = self._pts.select_test_plan(testplan_id, timeout)
238-
self._checks.append(outcome)
242+
return outcome
239243

240244
def run_cmd(self, cmd, env={}, interactive=False, timeout=0, target="all"):
241245
if self.mode == "remote":
@@ -268,6 +272,7 @@ def reboot(self, timeout=0, target="all"):
268272
self.agent_machine.reboot(timeout)
269273
else:
270274
self.local_machine.reboot(timeout)
275+
return True
271276

272277
def put(self, filepath, data, mode=None, uid=1000, gid=1000, target="all"):
273278
if self.mode == "remote":
@@ -280,6 +285,8 @@ def put(self, filepath, data, mode=None, uid=1000, gid=1000, target="all"):
280285
self.agent_machine.put(filepath, data, mode, uid, gid)
281286
else:
282287
self.local_machine.put(filepath, data, mode, uid, gid)
288+
# put raises an exception on failure
289+
return True
283290

284291
def switch_on_networking(self, target="all"):
285292
if self.mode == "remote":
@@ -292,6 +299,7 @@ def switch_on_networking(self, target="all"):
292299
self.agent_machine.switch_on_networking()
293300
else:
294301
self.local_machine.switch_on_networking()
302+
return True
295303

296304
def switch_off_networking(self, target="all"):
297305
if self.mode == "remote":
@@ -304,6 +312,7 @@ def switch_off_networking(self, target="all"):
304312
self.agent_machine.switch_off_networking()
305313
else:
306314
self.local_machine.switch_off_networking()
315+
return True
307316

308317
def stop_agent(self):
309318
return self.agent_machine.stop_agent()
@@ -322,15 +331,15 @@ def mktree(self, path, privileged=False, timeout=0, target="all"):
322331
if privileged:
323332
cmd = ["sudo"] + cmd
324333
cmd_str = shlex.join(cmd)
325-
self.run_cmd(cmd_str, target=target, timeout=timeout)
334+
return self.run_cmd(cmd_str, target=target, timeout=timeout)
326335

327336
def run_manage(self, args, timeout=0, target="all"):
328337
"""
329338
Runs the manage.py script with some arguments
330339
"""
331340
path = "/home/ubuntu/checkbox/metabox/metabox/metabox-provider"
332341
cmd = f"bash -c 'cd {path} ; python3 manage.py {args}'"
333-
self.run_cmd(cmd, target=target, timeout=timeout)
342+
return self.run_cmd(cmd, target=target, timeout=timeout)
334343

335344
def assert_in_file(self, pattern, path):
336345
"""
@@ -344,4 +353,4 @@ def assert_in_file(self, pattern, path):
344353

345354
result = self.run_cmd(f"cat {path}")
346355
regex = re.compile(pattern)
347-
self._checks.append(bool(regex.search(result.stdout)))
356+
return bool(regex.search(result.stdout))

0 commit comments

Comments
 (0)