blob: 5c8d2fdba3129dbfbdf6e62598777b01939b6611 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0
Stephen Warren10e50632016-01-15 11:15:24 -07002# Copyright (c) 2015 Stephen Warren
3# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
Stephen Warren10e50632016-01-15 11:15:24 -07004
5# Implementation of pytest run-time hook functions. These are invoked by
6# pytest at certain points during operation, e.g. startup, for each executed
7# test, at shutdown etc. These hooks perform functions such as:
8# - Parsing custom command-line options.
9# - Pullilng in user-specified board configuration.
10# - Creating the U-Boot console test fixture.
11# - Creating the HTML log file.
12# - Monitoring each test's results.
13# - Implementing custom pytest markers.
14
15import atexit
Tom Rini6a990412019-10-24 11:59:21 -040016import configparser
Stephen Warren10e50632016-01-15 11:15:24 -070017import errno
Simon Glass62b92f82022-08-06 17:51:57 -060018import filelock
Tom Rini6a990412019-10-24 11:59:21 -040019import io
Stephen Warren10e50632016-01-15 11:15:24 -070020import os
21import os.path
Simon Glass62b92f82022-08-06 17:51:57 -060022from pathlib import Path
Stephen Warren10e50632016-01-15 11:15:24 -070023import pytest
Stephen Warren770fe172016-02-08 14:44:16 -070024import re
Tom Rini6a990412019-10-24 11:59:21 -040025from _pytest.runner import runtestprotocol
Simon Glassf6dbc362024-11-12 07:13:18 -070026import subprocess
Stephen Warren10e50632016-01-15 11:15:24 -070027import sys
Simon Glassd834d9a2024-10-09 18:29:03 -060028from u_boot_spawn import BootFail, Timeout, Unexpected, handle_exception
Stephen Warren10e50632016-01-15 11:15:24 -070029
30# Globals: The HTML log file, and the connection to the U-Boot console.
31log = None
32console = None
33
Simon Glass62b92f82022-08-06 17:51:57 -060034TEST_PY_DIR = os.path.dirname(os.path.abspath(__file__))
35
Stephen Warren10e50632016-01-15 11:15:24 -070036def mkdir_p(path):
Stephen Warren75e731e2016-01-26 13:41:30 -070037 """Create a directory path.
Stephen Warren10e50632016-01-15 11:15:24 -070038
39 This includes creating any intermediate/parent directories. Any errors
40 caused due to already extant directories are ignored.
41
42 Args:
43 path: The directory path to create.
44
45 Returns:
46 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070047 """
Stephen Warren10e50632016-01-15 11:15:24 -070048
49 try:
50 os.makedirs(path)
51 except OSError as exc:
52 if exc.errno == errno.EEXIST and os.path.isdir(path):
53 pass
54 else:
55 raise
56
57def pytest_addoption(parser):
Stephen Warren75e731e2016-01-26 13:41:30 -070058 """pytest hook: Add custom command-line options to the cmdline parser.
Stephen Warren10e50632016-01-15 11:15:24 -070059
60 Args:
61 parser: The pytest command-line parser.
62
63 Returns:
64 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070065 """
Stephen Warren10e50632016-01-15 11:15:24 -070066
67 parser.addoption('--build-dir', default=None,
68 help='U-Boot build directory (O=)')
69 parser.addoption('--result-dir', default=None,
70 help='U-Boot test result/tmp directory')
71 parser.addoption('--persistent-data-dir', default=None,
72 help='U-Boot test persistent generated data directory')
73 parser.addoption('--board-type', '--bd', '-B', default='sandbox',
74 help='U-Boot board type')
75 parser.addoption('--board-identity', '--id', default='na',
76 help='U-Boot board identity/instance')
77 parser.addoption('--build', default=False, action='store_true',
78 help='Compile U-Boot before running tests')
Simon Glass6e094842020-03-18 09:43:01 -060079 parser.addoption('--buildman', default=False, action='store_true',
80 help='Use buildman to build U-Boot (assuming --build is given)')
Stephen Warren33db1ee2016-02-04 16:11:50 -070081 parser.addoption('--gdbserver', default=None,
82 help='Run sandbox under gdbserver. The argument is the channel '+
83 'over which gdbserver should communicate, e.g. localhost:1234')
Simon Glassf6dbc362024-11-12 07:13:18 -070084 parser.addoption('--role', help='U-Boot board role (for Labgrid-sjg)')
Simon Glassf1b1bb82024-11-12 07:13:17 -070085 parser.addoption('--use-running-system', default=False, action='store_true',
86 help="Assume that U-Boot is ready and don't wait for a prompt")
Stephen Warren10e50632016-01-15 11:15:24 -070087
Simon Glass686fad72022-08-06 17:51:56 -060088def run_build(config, source_dir, build_dir, board_type, log):
89 """run_build: Build U-Boot
90
91 Args:
92 config: The pytest configuration.
93 soruce_dir (str): Directory containing source code
94 build_dir (str): Directory to build in
95 board_type (str): board_type parameter (e.g. 'sandbox')
96 log (Logfile): Log file to use
97 """
98 if config.getoption('buildman'):
99 if build_dir != source_dir:
100 dest_args = ['-o', build_dir, '-w']
101 else:
102 dest_args = ['-i']
103 cmds = (['buildman', '--board', board_type] + dest_args,)
104 name = 'buildman'
105 else:
106 if build_dir != source_dir:
107 o_opt = 'O=%s' % build_dir
108 else:
109 o_opt = ''
110 cmds = (
111 ['make', o_opt, '-s', board_type + '_defconfig'],
112 ['make', o_opt, '-s', '-j{}'.format(os.cpu_count())],
113 )
114 name = 'make'
115
116 with log.section(name):
117 runner = log.get_runner(name, sys.stdout)
118 for cmd in cmds:
119 runner.run(cmd, cwd=source_dir)
120 runner.close()
121 log.status_pass('OK')
122
Simon Glass35ad4322024-10-09 18:29:00 -0600123def get_details(config):
124 """Obtain salient details about the board and directories to use
125
126 Args:
127 config (pytest.Config): pytest configuration
128
129 Returns:
130 tuple:
131 str: Board type (U-Boot build name)
132 str: Identity for the lab board
133 str: Build directory
134 str: Source directory
135 """
Simon Glassf6dbc362024-11-12 07:13:18 -0700136 role = config.getoption('role')
137
138 # Get a few provided parameters
Simon Glass35ad4322024-10-09 18:29:00 -0600139 build_dir = config.getoption('build_dir')
Simon Glassf6dbc362024-11-12 07:13:18 -0700140 if role:
141 # When using a role, build_dir and build_dir_extra are normally not set,
142 # since they are picked up from Labgrid-sjg via the u-boot-test-getrole
143 # script
144 board_identity = role
145 cmd = ['u-boot-test-getrole', role, '--configure']
146 env = os.environ.copy()
147 if build_dir:
148 env['U_BOOT_BUILD_DIR'] = build_dir
149 proc = subprocess.run(cmd, capture_output=True, encoding='utf-8',
150 env=env)
151 if proc.returncode:
152 raise ValueError(proc.stderr)
153 # For debugging
154 # print('conftest: lab:', proc.stdout)
155 vals = {}
156 for line in proc.stdout.splitlines():
157 item, value = line.split(' ', maxsplit=1)
158 k = item.split(':')[-1]
159 vals[k] = value
160 # For debugging
161 # print('conftest: lab info:', vals)
162 board_type, default_build_dir, source_dir = (vals['board'],
163 vals['build_dir'], vals['source_dir'])
164 else:
165 board_type = config.getoption('board_type')
166 board_identity = config.getoption('board_identity')
Simon Glass35ad4322024-10-09 18:29:00 -0600167
Simon Glassf6dbc362024-11-12 07:13:18 -0700168 source_dir = os.path.dirname(os.path.dirname(TEST_PY_DIR))
169 default_build_dir = source_dir + '/build-' + board_type
Simon Glass62b92f82022-08-06 17:51:57 -0600170 if not build_dir:
Simon Glass35ad4322024-10-09 18:29:00 -0600171 build_dir = default_build_dir
172
173 return board_type, board_identity, build_dir, source_dir
174
175def pytest_xdist_setupnodes(config, specs):
176 """Clear out any 'done' file from a previous build"""
177 global build_done_file
178
179 build_dir = get_details(config)[2]
180
Simon Glass62b92f82022-08-06 17:51:57 -0600181 build_done_file = Path(build_dir) / 'build.done'
182 if build_done_file.exists():
183 os.remove(build_done_file)
184
Stephen Warren10e50632016-01-15 11:15:24 -0700185def pytest_configure(config):
Stephen Warren75e731e2016-01-26 13:41:30 -0700186 """pytest hook: Perform custom initialization at startup time.
Stephen Warren10e50632016-01-15 11:15:24 -0700187
188 Args:
189 config: The pytest configuration.
190
191 Returns:
192 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700193 """
Simon Glassde8e25b2019-12-01 19:34:18 -0700194 def parse_config(conf_file):
195 """Parse a config file, loading it into the ubconfig container
196
197 Args:
198 conf_file: Filename to load (within build_dir)
199
200 Raises
201 Exception if the file does not exist
202 """
203 dot_config = build_dir + '/' + conf_file
204 if not os.path.exists(dot_config):
205 raise Exception(conf_file + ' does not exist; ' +
206 'try passing --build option?')
207
208 with open(dot_config, 'rt') as f:
209 ini_str = '[root]\n' + f.read()
210 ini_sio = io.StringIO(ini_str)
211 parser = configparser.RawConfigParser()
212 parser.read_file(ini_sio)
213 ubconfig.buildconfig.update(parser.items('root'))
Stephen Warren10e50632016-01-15 11:15:24 -0700214
215 global log
216 global console
217 global ubconfig
218
Simon Glass35ad4322024-10-09 18:29:00 -0600219 board_type, board_identity, build_dir, source_dir = get_details(config)
Stephen Warren10e50632016-01-15 11:15:24 -0700220
Stephen Warren10e50632016-01-15 11:15:24 -0700221 board_type_filename = board_type.replace('-', '_')
Stephen Warren10e50632016-01-15 11:15:24 -0700222 board_identity_filename = board_identity.replace('-', '_')
Stephen Warren10e50632016-01-15 11:15:24 -0700223 mkdir_p(build_dir)
224
225 result_dir = config.getoption('result_dir')
226 if not result_dir:
227 result_dir = build_dir
228 mkdir_p(result_dir)
229
230 persistent_data_dir = config.getoption('persistent_data_dir')
231 if not persistent_data_dir:
232 persistent_data_dir = build_dir + '/persistent-data'
233 mkdir_p(persistent_data_dir)
234
Stephen Warren33db1ee2016-02-04 16:11:50 -0700235 gdbserver = config.getoption('gdbserver')
Igor Opaniukea5f17d2019-02-12 16:18:14 +0200236 if gdbserver and not board_type.startswith('sandbox'):
237 raise Exception('--gdbserver only supported with sandbox targets')
Stephen Warren33db1ee2016-02-04 16:11:50 -0700238
Stephen Warren10e50632016-01-15 11:15:24 -0700239 import multiplexed_log
240 log = multiplexed_log.Logfile(result_dir + '/test-log.html')
241
242 if config.getoption('build'):
Simon Glass62b92f82022-08-06 17:51:57 -0600243 worker_id = os.environ.get("PYTEST_XDIST_WORKER")
244 with filelock.FileLock(os.path.join(build_dir, 'build.lock')):
245 build_done_file = Path(build_dir) / 'build.done'
246 if (not worker_id or worker_id == 'master' or
247 not build_done_file.exists()):
248 run_build(config, source_dir, build_dir, board_type, log)
249 build_done_file.touch()
Stephen Warren10e50632016-01-15 11:15:24 -0700250
251 class ArbitraryAttributeContainer(object):
252 pass
253
254 ubconfig = ArbitraryAttributeContainer()
255 ubconfig.brd = dict()
256 ubconfig.env = dict()
257
258 modules = [
259 (ubconfig.brd, 'u_boot_board_' + board_type_filename),
260 (ubconfig.env, 'u_boot_boardenv_' + board_type_filename),
261 (ubconfig.env, 'u_boot_boardenv_' + board_type_filename + '_' +
262 board_identity_filename),
263 ]
264 for (dict_to_fill, module_name) in modules:
265 try:
266 module = __import__(module_name)
267 except ImportError:
268 continue
269 dict_to_fill.update(module.__dict__)
270
271 ubconfig.buildconfig = dict()
272
Simon Glassde8e25b2019-12-01 19:34:18 -0700273 # buildman -k puts autoconf.mk in the rootdir, so handle this as well
274 # as the standard U-Boot build which leaves it in include/autoconf.mk
275 parse_config('.config')
276 if os.path.exists(build_dir + '/' + 'autoconf.mk'):
277 parse_config('autoconf.mk')
278 else:
279 parse_config('include/autoconf.mk')
Stephen Warren10e50632016-01-15 11:15:24 -0700280
Simon Glass62b92f82022-08-06 17:51:57 -0600281 ubconfig.test_py_dir = TEST_PY_DIR
Stephen Warren10e50632016-01-15 11:15:24 -0700282 ubconfig.source_dir = source_dir
283 ubconfig.build_dir = build_dir
284 ubconfig.result_dir = result_dir
285 ubconfig.persistent_data_dir = persistent_data_dir
286 ubconfig.board_type = board_type
287 ubconfig.board_identity = board_identity
Stephen Warren33db1ee2016-02-04 16:11:50 -0700288 ubconfig.gdbserver = gdbserver
Simon Glassf1b1bb82024-11-12 07:13:17 -0700289 ubconfig.use_running_system = config.getoption('use_running_system')
Simon Glass3b097872016-07-03 09:40:36 -0600290 ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb'
Simon Glassd834d9a2024-10-09 18:29:03 -0600291 ubconfig.connection_ok = True
Stephen Warren10e50632016-01-15 11:15:24 -0700292
293 env_vars = (
294 'board_type',
295 'board_identity',
296 'source_dir',
297 'test_py_dir',
298 'build_dir',
299 'result_dir',
300 'persistent_data_dir',
301 )
302 for v in env_vars:
303 os.environ['U_BOOT_' + v.upper()] = getattr(ubconfig, v)
304
Simon Glass13f422e2016-07-04 11:58:37 -0600305 if board_type.startswith('sandbox'):
Stephen Warren10e50632016-01-15 11:15:24 -0700306 import u_boot_console_sandbox
307 console = u_boot_console_sandbox.ConsoleSandbox(log, ubconfig)
308 else:
309 import u_boot_console_exec_attach
310 console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig)
311
Simon Glass23300b42021-10-23 17:26:11 -0600312re_ut_test_list = re.compile(r'[^a-zA-Z0-9_]_u_boot_list_2_ut_(.*)_test_2_(.*)\s*$')
Simon Glassed298be2020-10-25 20:38:31 -0600313def generate_ut_subtest(metafunc, fixture_name, sym_path):
Stephen Warren770fe172016-02-08 14:44:16 -0700314 """Provide parametrization for a ut_subtest fixture.
315
316 Determines the set of unit tests built into a U-Boot binary by parsing the
317 list of symbols generated by the build process. Provides this information
318 to test functions by parameterizing their ut_subtest fixture parameter.
319
320 Args:
321 metafunc: The pytest test function.
322 fixture_name: The fixture name to test.
Simon Glassed298be2020-10-25 20:38:31 -0600323 sym_path: Relative path to the symbol file with preceding '/'
324 (e.g. '/u-boot.sym')
Stephen Warren770fe172016-02-08 14:44:16 -0700325
326 Returns:
327 Nothing.
328 """
Simon Glassed298be2020-10-25 20:38:31 -0600329 fn = console.config.build_dir + sym_path
Stephen Warren770fe172016-02-08 14:44:16 -0700330 try:
331 with open(fn, 'rt') as f:
332 lines = f.readlines()
333 except:
334 lines = []
335 lines.sort()
336
337 vals = []
338 for l in lines:
339 m = re_ut_test_list.search(l)
340 if not m:
341 continue
Simon Glass1f1614b2022-10-20 18:22:50 -0600342 suite, name = m.groups()
343
344 # Tests marked with _norun should only be run manually using 'ut -f'
345 if name.endswith('_norun'):
346 continue
347
348 vals.append(f'{suite} {name}')
Stephen Warren770fe172016-02-08 14:44:16 -0700349
350 ids = ['ut_' + s.replace(' ', '_') for s in vals]
351 metafunc.parametrize(fixture_name, vals, ids=ids)
352
353def generate_config(metafunc, fixture_name):
354 """Provide parametrization for {env,brd}__ fixtures.
Stephen Warren10e50632016-01-15 11:15:24 -0700355
356 If a test function takes parameter(s) (fixture names) of the form brd__xxx
357 or env__xxx, the brd and env configuration dictionaries are consulted to
358 find the list of values to use for those parameters, and the test is
359 parametrized so that it runs once for each combination of values.
360
361 Args:
362 metafunc: The pytest test function.
Stephen Warren770fe172016-02-08 14:44:16 -0700363 fixture_name: The fixture name to test.
Stephen Warren10e50632016-01-15 11:15:24 -0700364
365 Returns:
366 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700367 """
Stephen Warren10e50632016-01-15 11:15:24 -0700368
369 subconfigs = {
370 'brd': console.config.brd,
371 'env': console.config.env,
372 }
Stephen Warren770fe172016-02-08 14:44:16 -0700373 parts = fixture_name.split('__')
374 if len(parts) < 2:
375 return
376 if parts[0] not in subconfigs:
377 return
378 subconfig = subconfigs[parts[0]]
379 vals = []
380 val = subconfig.get(fixture_name, [])
381 # If that exact name is a key in the data source:
382 if val:
383 # ... use the dict value as a single parameter value.
384 vals = (val, )
385 else:
386 # ... otherwise, see if there's a key that contains a list of
387 # values to use instead.
388 vals = subconfig.get(fixture_name+ 's', [])
389 def fixture_id(index, val):
390 try:
391 return val['fixture_id']
392 except:
393 return fixture_name + str(index)
394 ids = [fixture_id(index, val) for (index, val) in enumerate(vals)]
395 metafunc.parametrize(fixture_name, vals, ids=ids)
396
397def pytest_generate_tests(metafunc):
398 """pytest hook: parameterize test functions based on custom rules.
399
400 Check each test function parameter (fixture name) to see if it is one of
401 our custom names, and if so, provide the correct parametrization for that
402 parameter.
403
404 Args:
405 metafunc: The pytest test function.
406
407 Returns:
408 Nothing.
409 """
Stephen Warren10e50632016-01-15 11:15:24 -0700410 for fn in metafunc.fixturenames:
Stephen Warren770fe172016-02-08 14:44:16 -0700411 if fn == 'ut_subtest':
Simon Glassed298be2020-10-25 20:38:31 -0600412 generate_ut_subtest(metafunc, fn, '/u-boot.sym')
413 continue
Simon Glassb6c665f2022-04-30 00:56:55 -0600414 m_subtest = re.match('ut_(.)pl_subtest', fn)
415 if m_subtest:
416 spl_name = m_subtest.group(1)
417 generate_ut_subtest(
418 metafunc, fn, f'/{spl_name}pl/u-boot-{spl_name}pl.sym')
Stephen Warren10e50632016-01-15 11:15:24 -0700419 continue
Stephen Warren770fe172016-02-08 14:44:16 -0700420 generate_config(metafunc, fn)
Stephen Warren10e50632016-01-15 11:15:24 -0700421
Stefan Brüns364ea872016-11-05 17:45:32 +0100422@pytest.fixture(scope='session')
423def u_boot_log(request):
424 """Generate the value of a test's log fixture.
425
426 Args:
427 request: The pytest request.
428
429 Returns:
430 The fixture value.
431 """
432
433 return console.log
434
435@pytest.fixture(scope='session')
436def u_boot_config(request):
437 """Generate the value of a test's u_boot_config fixture.
438
439 Args:
440 request: The pytest request.
441
442 Returns:
443 The fixture value.
444 """
445
446 return console.config
447
Stephen Warrene1d24d02016-01-22 12:30:08 -0700448@pytest.fixture(scope='function')
Stephen Warren10e50632016-01-15 11:15:24 -0700449def u_boot_console(request):
Stephen Warren75e731e2016-01-26 13:41:30 -0700450 """Generate the value of a test's u_boot_console fixture.
Stephen Warren10e50632016-01-15 11:15:24 -0700451
452 Args:
453 request: The pytest request.
454
455 Returns:
456 The fixture value.
Stephen Warren75e731e2016-01-26 13:41:30 -0700457 """
Simon Glassd834d9a2024-10-09 18:29:03 -0600458 if not ubconfig.connection_ok:
459 pytest.skip('Cannot get target connection')
460 return None
461 try:
462 console.ensure_spawned()
463 except OSError as err:
464 handle_exception(ubconfig, console, log, err, 'Lab failure', True)
465 except Timeout as err:
466 handle_exception(ubconfig, console, log, err, 'Lab timeout', True)
467 except BootFail as err:
468 handle_exception(ubconfig, console, log, err, 'Boot fail', True,
469 console.get_spawn_output())
470 except Unexpected:
471 handle_exception(ubconfig, console, log, err, 'Unexpected test output',
472 False)
Stephen Warren10e50632016-01-15 11:15:24 -0700473 return console
474
Stephen Warrene3f2a502016-02-03 16:46:34 -0700475anchors = {}
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700476tests_not_run = []
477tests_failed = []
478tests_xpassed = []
479tests_xfailed = []
480tests_skipped = []
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700481tests_warning = []
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700482tests_passed = []
Stephen Warren10e50632016-01-15 11:15:24 -0700483
484def pytest_itemcollected(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700485 """pytest hook: Called once for each test found during collection.
Stephen Warren10e50632016-01-15 11:15:24 -0700486
487 This enables our custom result analysis code to see the list of all tests
488 that should eventually be run.
489
490 Args:
491 item: The item that was collected.
492
493 Returns:
494 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700495 """
Stephen Warren10e50632016-01-15 11:15:24 -0700496
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700497 tests_not_run.append(item.name)
Stephen Warren10e50632016-01-15 11:15:24 -0700498
499def cleanup():
Stephen Warren75e731e2016-01-26 13:41:30 -0700500 """Clean up all global state.
Stephen Warren10e50632016-01-15 11:15:24 -0700501
502 Executed (via atexit) once the entire test process is complete. This
503 includes logging the status of all tests, and the identity of any failed
504 or skipped tests.
505
506 Args:
507 None.
508
509 Returns:
510 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700511 """
Stephen Warren10e50632016-01-15 11:15:24 -0700512
513 if console:
514 console.close()
515 if log:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700516 with log.section('Status Report', 'status_report'):
517 log.status_pass('%d passed' % len(tests_passed))
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700518 if tests_warning:
519 log.status_warning('%d passed with warning' % len(tests_warning))
520 for test in tests_warning:
521 anchor = anchors.get(test, None)
522 log.status_warning('... ' + test, anchor)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700523 if tests_skipped:
524 log.status_skipped('%d skipped' % len(tests_skipped))
525 for test in tests_skipped:
526 anchor = anchors.get(test, None)
527 log.status_skipped('... ' + test, anchor)
528 if tests_xpassed:
529 log.status_xpass('%d xpass' % len(tests_xpassed))
530 for test in tests_xpassed:
531 anchor = anchors.get(test, None)
532 log.status_xpass('... ' + test, anchor)
533 if tests_xfailed:
534 log.status_xfail('%d xfail' % len(tests_xfailed))
535 for test in tests_xfailed:
536 anchor = anchors.get(test, None)
537 log.status_xfail('... ' + test, anchor)
538 if tests_failed:
539 log.status_fail('%d failed' % len(tests_failed))
540 for test in tests_failed:
541 anchor = anchors.get(test, None)
542 log.status_fail('... ' + test, anchor)
543 if tests_not_run:
544 log.status_fail('%d not run' % len(tests_not_run))
545 for test in tests_not_run:
546 anchor = anchors.get(test, None)
547 log.status_fail('... ' + test, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700548 log.close()
549atexit.register(cleanup)
550
551def setup_boardspec(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700552 """Process any 'boardspec' marker for a test.
Stephen Warren10e50632016-01-15 11:15:24 -0700553
554 Such a marker lists the set of board types that a test does/doesn't
555 support. If tests are being executed on an unsupported board, the test is
556 marked to be skipped.
557
558 Args:
559 item: The pytest test item.
560
561 Returns:
562 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700563 """
Stephen Warren10e50632016-01-15 11:15:24 -0700564
Stephen Warren10e50632016-01-15 11:15:24 -0700565 required_boards = []
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400566 for boards in item.iter_markers('boardspec'):
567 board = boards.args[0]
Stephen Warren10e50632016-01-15 11:15:24 -0700568 if board.startswith('!'):
569 if ubconfig.board_type == board[1:]:
Stephen Warren0f0eeac2017-09-18 11:11:48 -0600570 pytest.skip('board "%s" not supported' % ubconfig.board_type)
Stephen Warren10e50632016-01-15 11:15:24 -0700571 return
572 else:
573 required_boards.append(board)
574 if required_boards and ubconfig.board_type not in required_boards:
Stephen Warren0f0eeac2017-09-18 11:11:48 -0600575 pytest.skip('board "%s" not supported' % ubconfig.board_type)
Stephen Warren10e50632016-01-15 11:15:24 -0700576
577def setup_buildconfigspec(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700578 """Process any 'buildconfigspec' marker for a test.
Stephen Warren10e50632016-01-15 11:15:24 -0700579
580 Such a marker lists some U-Boot configuration feature that the test
581 requires. If tests are being executed on an U-Boot build that doesn't
582 have the required feature, the test is marked to be skipped.
583
584 Args:
585 item: The pytest test item.
586
587 Returns:
588 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700589 """
Stephen Warren10e50632016-01-15 11:15:24 -0700590
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400591 for options in item.iter_markers('buildconfigspec'):
592 option = options.args[0]
593 if not ubconfig.buildconfig.get('config_' + option.lower(), None):
594 pytest.skip('.config feature "%s" not enabled' % option.lower())
Cristian Ciocaltea6c6c8072019-12-24 17:19:12 +0200595 for options in item.iter_markers('notbuildconfigspec'):
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400596 option = options.args[0]
597 if ubconfig.buildconfig.get('config_' + option.lower(), None):
598 pytest.skip('.config feature "%s" enabled' % option.lower())
Stephen Warren10e50632016-01-15 11:15:24 -0700599
Stephen Warren2079db32017-09-18 11:11:49 -0600600def tool_is_in_path(tool):
601 for path in os.environ["PATH"].split(os.pathsep):
602 fn = os.path.join(path, tool)
603 if os.path.isfile(fn) and os.access(fn, os.X_OK):
604 return True
605 return False
606
607def setup_requiredtool(item):
608 """Process any 'requiredtool' marker for a test.
609
610 Such a marker lists some external tool (binary, executable, application)
611 that the test requires. If tests are being executed on a system that
612 doesn't have the required tool, the test is marked to be skipped.
613
614 Args:
615 item: The pytest test item.
616
617 Returns:
618 Nothing.
619 """
620
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400621 for tools in item.iter_markers('requiredtool'):
622 tool = tools.args[0]
Stephen Warren2079db32017-09-18 11:11:49 -0600623 if not tool_is_in_path(tool):
624 pytest.skip('tool "%s" not in $PATH' % tool)
625
Simon Glass54ef2ca2022-08-06 17:51:47 -0600626def setup_singlethread(item):
627 """Process any 'singlethread' marker for a test.
628
629 Skip this test if running in parallel.
630
631 Args:
632 item: The pytest test item.
633
634 Returns:
635 Nothing.
636 """
637 for single in item.iter_markers('singlethread'):
638 worker_id = os.environ.get("PYTEST_XDIST_WORKER")
639 if worker_id and worker_id != 'master':
640 pytest.skip('must run single-threaded')
641
Stephen Warren3e3d1432016-10-17 17:25:52 -0600642def start_test_section(item):
643 anchors[item.name] = log.start_section(item.name)
644
Stephen Warren10e50632016-01-15 11:15:24 -0700645def pytest_runtest_setup(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700646 """pytest hook: Configure (set up) a test item.
Stephen Warren10e50632016-01-15 11:15:24 -0700647
648 Called once for each test to perform any custom configuration. This hook
649 is used to skip the test if certain conditions apply.
650
651 Args:
652 item: The pytest test item.
653
654 Returns:
655 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700656 """
Stephen Warren10e50632016-01-15 11:15:24 -0700657
Stephen Warren3e3d1432016-10-17 17:25:52 -0600658 start_test_section(item)
Stephen Warren10e50632016-01-15 11:15:24 -0700659 setup_boardspec(item)
660 setup_buildconfigspec(item)
Stephen Warren2079db32017-09-18 11:11:49 -0600661 setup_requiredtool(item)
Simon Glass54ef2ca2022-08-06 17:51:47 -0600662 setup_singlethread(item)
Stephen Warren10e50632016-01-15 11:15:24 -0700663
664def pytest_runtest_protocol(item, nextitem):
Stephen Warren75e731e2016-01-26 13:41:30 -0700665 """pytest hook: Called to execute a test.
Stephen Warren10e50632016-01-15 11:15:24 -0700666
667 This hook wraps the standard pytest runtestprotocol() function in order
668 to acquire visibility into, and record, each test function's result.
669
670 Args:
671 item: The pytest test item to execute.
672 nextitem: The pytest test item that will be executed after this one.
673
674 Returns:
675 A list of pytest reports (test result data).
Stephen Warren75e731e2016-01-26 13:41:30 -0700676 """
Stephen Warren10e50632016-01-15 11:15:24 -0700677
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700678 log.get_and_reset_warning()
Stephen Warren76e6a9e2021-01-30 20:12:18 -0700679 ihook = item.ihook
680 ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
Stephen Warren10e50632016-01-15 11:15:24 -0700681 reports = runtestprotocol(item, nextitem=nextitem)
Stephen Warren76e6a9e2021-01-30 20:12:18 -0700682 ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location)
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700683 was_warning = log.get_and_reset_warning()
Stephen Warren25b05242016-01-27 23:57:51 -0700684
Stephen Warren3e3d1432016-10-17 17:25:52 -0600685 # In pytest 3, runtestprotocol() may not call pytest_runtest_setup() if
686 # the test is skipped. That call is required to create the test's section
687 # in the log file. The call to log.end_section() requires that the log
688 # contain a section for this test. Create a section for the test if it
689 # doesn't already exist.
690 if not item.name in anchors:
691 start_test_section(item)
692
Stephen Warren25b05242016-01-27 23:57:51 -0700693 failure_cleanup = False
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700694 if not was_warning:
695 test_list = tests_passed
696 msg = 'OK'
697 msg_log = log.status_pass
698 else:
699 test_list = tests_warning
700 msg = 'OK (with warning)'
701 msg_log = log.status_warning
Stephen Warren10e50632016-01-15 11:15:24 -0700702 for report in reports:
703 if report.outcome == 'failed':
Stephen Warren25b05242016-01-27 23:57:51 -0700704 if hasattr(report, 'wasxfail'):
705 test_list = tests_xpassed
706 msg = 'XPASSED'
707 msg_log = log.status_xpass
708 else:
709 failure_cleanup = True
710 test_list = tests_failed
711 msg = 'FAILED:\n' + str(report.longrepr)
712 msg_log = log.status_fail
Stephen Warren10e50632016-01-15 11:15:24 -0700713 break
714 if report.outcome == 'skipped':
Stephen Warren25b05242016-01-27 23:57:51 -0700715 if hasattr(report, 'wasxfail'):
716 failure_cleanup = True
717 test_list = tests_xfailed
718 msg = 'XFAILED:\n' + str(report.longrepr)
719 msg_log = log.status_xfail
720 break
721 test_list = tests_skipped
722 msg = 'SKIPPED:\n' + str(report.longrepr)
723 msg_log = log.status_skipped
Stephen Warren10e50632016-01-15 11:15:24 -0700724
Stephen Warren25b05242016-01-27 23:57:51 -0700725 if failure_cleanup:
Stephen Warren97a54662016-01-22 12:30:09 -0700726 console.drain_console()
Stephen Warren25b05242016-01-27 23:57:51 -0700727
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700728 test_list.append(item.name)
Stephen Warren10e50632016-01-15 11:15:24 -0700729 tests_not_run.remove(item.name)
730
731 try:
Stephen Warren25b05242016-01-27 23:57:51 -0700732 msg_log(msg)
Stephen Warren10e50632016-01-15 11:15:24 -0700733 except:
734 # If something went wrong with logging, it's better to let the test
735 # process continue, which may report other exceptions that triggered
736 # the logging issue (e.g. console.log wasn't created). Hence, just
737 # squash the exception. If the test setup failed due to e.g. syntax
738 # error somewhere else, this won't be seen. However, once that issue
739 # is fixed, if this exception still exists, it will then be logged as
740 # part of the test's stdout.
741 import traceback
Paul Burton00f2d202017-09-14 14:34:43 -0700742 print('Exception occurred while logging runtest status:')
Stephen Warren10e50632016-01-15 11:15:24 -0700743 traceback.print_exc()
744 # FIXME: Can we force a test failure here?
745
746 log.end_section(item.name)
747
Stephen Warren25b05242016-01-27 23:57:51 -0700748 if failure_cleanup:
Stephen Warren10e50632016-01-15 11:15:24 -0700749 console.cleanup_spawn()
750
Stephen Warren76e6a9e2021-01-30 20:12:18 -0700751 return True