blob: 46a410cf268b80c16baf08c7bd8712cc262d649f [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
Stephen Warren10e50632016-01-15 11:15:24 -070026import sys
Simon Glassd834d9a2024-10-09 18:29:03 -060027from u_boot_spawn import BootFail, Timeout, Unexpected, handle_exception
Stephen Warren10e50632016-01-15 11:15:24 -070028
29# Globals: The HTML log file, and the connection to the U-Boot console.
30log = None
31console = None
32
Simon Glass62b92f82022-08-06 17:51:57 -060033TEST_PY_DIR = os.path.dirname(os.path.abspath(__file__))
34
Stephen Warren10e50632016-01-15 11:15:24 -070035def mkdir_p(path):
Stephen Warren75e731e2016-01-26 13:41:30 -070036 """Create a directory path.
Stephen Warren10e50632016-01-15 11:15:24 -070037
38 This includes creating any intermediate/parent directories. Any errors
39 caused due to already extant directories are ignored.
40
41 Args:
42 path: The directory path to create.
43
44 Returns:
45 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070046 """
Stephen Warren10e50632016-01-15 11:15:24 -070047
48 try:
49 os.makedirs(path)
50 except OSError as exc:
51 if exc.errno == errno.EEXIST and os.path.isdir(path):
52 pass
53 else:
54 raise
55
56def pytest_addoption(parser):
Stephen Warren75e731e2016-01-26 13:41:30 -070057 """pytest hook: Add custom command-line options to the cmdline parser.
Stephen Warren10e50632016-01-15 11:15:24 -070058
59 Args:
60 parser: The pytest command-line parser.
61
62 Returns:
63 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070064 """
Stephen Warren10e50632016-01-15 11:15:24 -070065
66 parser.addoption('--build-dir', default=None,
67 help='U-Boot build directory (O=)')
68 parser.addoption('--result-dir', default=None,
69 help='U-Boot test result/tmp directory')
70 parser.addoption('--persistent-data-dir', default=None,
71 help='U-Boot test persistent generated data directory')
72 parser.addoption('--board-type', '--bd', '-B', default='sandbox',
73 help='U-Boot board type')
74 parser.addoption('--board-identity', '--id', default='na',
75 help='U-Boot board identity/instance')
76 parser.addoption('--build', default=False, action='store_true',
77 help='Compile U-Boot before running tests')
Simon Glass6e094842020-03-18 09:43:01 -060078 parser.addoption('--buildman', default=False, action='store_true',
79 help='Use buildman to build U-Boot (assuming --build is given)')
Stephen Warren33db1ee2016-02-04 16:11:50 -070080 parser.addoption('--gdbserver', default=None,
81 help='Run sandbox under gdbserver. The argument is the channel '+
82 'over which gdbserver should communicate, e.g. localhost:1234')
Stephen Warren10e50632016-01-15 11:15:24 -070083
Simon Glass686fad72022-08-06 17:51:56 -060084def run_build(config, source_dir, build_dir, board_type, log):
85 """run_build: Build U-Boot
86
87 Args:
88 config: The pytest configuration.
89 soruce_dir (str): Directory containing source code
90 build_dir (str): Directory to build in
91 board_type (str): board_type parameter (e.g. 'sandbox')
92 log (Logfile): Log file to use
93 """
94 if config.getoption('buildman'):
95 if build_dir != source_dir:
96 dest_args = ['-o', build_dir, '-w']
97 else:
98 dest_args = ['-i']
99 cmds = (['buildman', '--board', board_type] + dest_args,)
100 name = 'buildman'
101 else:
102 if build_dir != source_dir:
103 o_opt = 'O=%s' % build_dir
104 else:
105 o_opt = ''
106 cmds = (
107 ['make', o_opt, '-s', board_type + '_defconfig'],
108 ['make', o_opt, '-s', '-j{}'.format(os.cpu_count())],
109 )
110 name = 'make'
111
112 with log.section(name):
113 runner = log.get_runner(name, sys.stdout)
114 for cmd in cmds:
115 runner.run(cmd, cwd=source_dir)
116 runner.close()
117 log.status_pass('OK')
118
Simon Glass35ad4322024-10-09 18:29:00 -0600119def get_details(config):
120 """Obtain salient details about the board and directories to use
121
122 Args:
123 config (pytest.Config): pytest configuration
124
125 Returns:
126 tuple:
127 str: Board type (U-Boot build name)
128 str: Identity for the lab board
129 str: Build directory
130 str: Source directory
131 """
Simon Glass62b92f82022-08-06 17:51:57 -0600132 board_type = config.getoption('board_type')
Simon Glass35ad4322024-10-09 18:29:00 -0600133 board_identity = config.getoption('board_identity')
134 build_dir = config.getoption('build_dir')
135
Simon Glass62b92f82022-08-06 17:51:57 -0600136 source_dir = os.path.dirname(os.path.dirname(TEST_PY_DIR))
Simon Glass35ad4322024-10-09 18:29:00 -0600137 default_build_dir = source_dir + '/build-' + board_type
Simon Glass62b92f82022-08-06 17:51:57 -0600138 if not build_dir:
Simon Glass35ad4322024-10-09 18:29:00 -0600139 build_dir = default_build_dir
140
141 return board_type, board_identity, build_dir, source_dir
142
143def pytest_xdist_setupnodes(config, specs):
144 """Clear out any 'done' file from a previous build"""
145 global build_done_file
146
147 build_dir = get_details(config)[2]
148
Simon Glass62b92f82022-08-06 17:51:57 -0600149 build_done_file = Path(build_dir) / 'build.done'
150 if build_done_file.exists():
151 os.remove(build_done_file)
152
Stephen Warren10e50632016-01-15 11:15:24 -0700153def pytest_configure(config):
Stephen Warren75e731e2016-01-26 13:41:30 -0700154 """pytest hook: Perform custom initialization at startup time.
Stephen Warren10e50632016-01-15 11:15:24 -0700155
156 Args:
157 config: The pytest configuration.
158
159 Returns:
160 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700161 """
Simon Glassde8e25b2019-12-01 19:34:18 -0700162 def parse_config(conf_file):
163 """Parse a config file, loading it into the ubconfig container
164
165 Args:
166 conf_file: Filename to load (within build_dir)
167
168 Raises
169 Exception if the file does not exist
170 """
171 dot_config = build_dir + '/' + conf_file
172 if not os.path.exists(dot_config):
173 raise Exception(conf_file + ' does not exist; ' +
174 'try passing --build option?')
175
176 with open(dot_config, 'rt') as f:
177 ini_str = '[root]\n' + f.read()
178 ini_sio = io.StringIO(ini_str)
179 parser = configparser.RawConfigParser()
180 parser.read_file(ini_sio)
181 ubconfig.buildconfig.update(parser.items('root'))
Stephen Warren10e50632016-01-15 11:15:24 -0700182
183 global log
184 global console
185 global ubconfig
186
Simon Glass35ad4322024-10-09 18:29:00 -0600187 board_type, board_identity, build_dir, source_dir = get_details(config)
Stephen Warren10e50632016-01-15 11:15:24 -0700188
Stephen Warren10e50632016-01-15 11:15:24 -0700189 board_type_filename = board_type.replace('-', '_')
Stephen Warren10e50632016-01-15 11:15:24 -0700190 board_identity_filename = board_identity.replace('-', '_')
Stephen Warren10e50632016-01-15 11:15:24 -0700191 mkdir_p(build_dir)
192
193 result_dir = config.getoption('result_dir')
194 if not result_dir:
195 result_dir = build_dir
196 mkdir_p(result_dir)
197
198 persistent_data_dir = config.getoption('persistent_data_dir')
199 if not persistent_data_dir:
200 persistent_data_dir = build_dir + '/persistent-data'
201 mkdir_p(persistent_data_dir)
202
Stephen Warren33db1ee2016-02-04 16:11:50 -0700203 gdbserver = config.getoption('gdbserver')
Igor Opaniukea5f17d2019-02-12 16:18:14 +0200204 if gdbserver and not board_type.startswith('sandbox'):
205 raise Exception('--gdbserver only supported with sandbox targets')
Stephen Warren33db1ee2016-02-04 16:11:50 -0700206
Stephen Warren10e50632016-01-15 11:15:24 -0700207 import multiplexed_log
208 log = multiplexed_log.Logfile(result_dir + '/test-log.html')
209
210 if config.getoption('build'):
Simon Glass62b92f82022-08-06 17:51:57 -0600211 worker_id = os.environ.get("PYTEST_XDIST_WORKER")
212 with filelock.FileLock(os.path.join(build_dir, 'build.lock')):
213 build_done_file = Path(build_dir) / 'build.done'
214 if (not worker_id or worker_id == 'master' or
215 not build_done_file.exists()):
216 run_build(config, source_dir, build_dir, board_type, log)
217 build_done_file.touch()
Stephen Warren10e50632016-01-15 11:15:24 -0700218
219 class ArbitraryAttributeContainer(object):
220 pass
221
222 ubconfig = ArbitraryAttributeContainer()
223 ubconfig.brd = dict()
224 ubconfig.env = dict()
225
226 modules = [
227 (ubconfig.brd, 'u_boot_board_' + board_type_filename),
228 (ubconfig.env, 'u_boot_boardenv_' + board_type_filename),
229 (ubconfig.env, 'u_boot_boardenv_' + board_type_filename + '_' +
230 board_identity_filename),
231 ]
232 for (dict_to_fill, module_name) in modules:
233 try:
234 module = __import__(module_name)
235 except ImportError:
236 continue
237 dict_to_fill.update(module.__dict__)
238
239 ubconfig.buildconfig = dict()
240
Simon Glassde8e25b2019-12-01 19:34:18 -0700241 # buildman -k puts autoconf.mk in the rootdir, so handle this as well
242 # as the standard U-Boot build which leaves it in include/autoconf.mk
243 parse_config('.config')
244 if os.path.exists(build_dir + '/' + 'autoconf.mk'):
245 parse_config('autoconf.mk')
246 else:
247 parse_config('include/autoconf.mk')
Stephen Warren10e50632016-01-15 11:15:24 -0700248
Simon Glass62b92f82022-08-06 17:51:57 -0600249 ubconfig.test_py_dir = TEST_PY_DIR
Stephen Warren10e50632016-01-15 11:15:24 -0700250 ubconfig.source_dir = source_dir
251 ubconfig.build_dir = build_dir
252 ubconfig.result_dir = result_dir
253 ubconfig.persistent_data_dir = persistent_data_dir
254 ubconfig.board_type = board_type
255 ubconfig.board_identity = board_identity
Stephen Warren33db1ee2016-02-04 16:11:50 -0700256 ubconfig.gdbserver = gdbserver
Simon Glass3b097872016-07-03 09:40:36 -0600257 ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb'
Simon Glassd834d9a2024-10-09 18:29:03 -0600258 ubconfig.connection_ok = True
Stephen Warren10e50632016-01-15 11:15:24 -0700259
260 env_vars = (
261 'board_type',
262 'board_identity',
263 'source_dir',
264 'test_py_dir',
265 'build_dir',
266 'result_dir',
267 'persistent_data_dir',
268 )
269 for v in env_vars:
270 os.environ['U_BOOT_' + v.upper()] = getattr(ubconfig, v)
271
Simon Glass13f422e2016-07-04 11:58:37 -0600272 if board_type.startswith('sandbox'):
Stephen Warren10e50632016-01-15 11:15:24 -0700273 import u_boot_console_sandbox
274 console = u_boot_console_sandbox.ConsoleSandbox(log, ubconfig)
275 else:
276 import u_boot_console_exec_attach
277 console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig)
278
Simon Glass23300b42021-10-23 17:26:11 -0600279re_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 -0600280def generate_ut_subtest(metafunc, fixture_name, sym_path):
Stephen Warren770fe172016-02-08 14:44:16 -0700281 """Provide parametrization for a ut_subtest fixture.
282
283 Determines the set of unit tests built into a U-Boot binary by parsing the
284 list of symbols generated by the build process. Provides this information
285 to test functions by parameterizing their ut_subtest fixture parameter.
286
287 Args:
288 metafunc: The pytest test function.
289 fixture_name: The fixture name to test.
Simon Glassed298be2020-10-25 20:38:31 -0600290 sym_path: Relative path to the symbol file with preceding '/'
291 (e.g. '/u-boot.sym')
Stephen Warren770fe172016-02-08 14:44:16 -0700292
293 Returns:
294 Nothing.
295 """
Simon Glassed298be2020-10-25 20:38:31 -0600296 fn = console.config.build_dir + sym_path
Stephen Warren770fe172016-02-08 14:44:16 -0700297 try:
298 with open(fn, 'rt') as f:
299 lines = f.readlines()
300 except:
301 lines = []
302 lines.sort()
303
304 vals = []
305 for l in lines:
306 m = re_ut_test_list.search(l)
307 if not m:
308 continue
Simon Glass1f1614b2022-10-20 18:22:50 -0600309 suite, name = m.groups()
310
311 # Tests marked with _norun should only be run manually using 'ut -f'
312 if name.endswith('_norun'):
313 continue
314
315 vals.append(f'{suite} {name}')
Stephen Warren770fe172016-02-08 14:44:16 -0700316
317 ids = ['ut_' + s.replace(' ', '_') for s in vals]
318 metafunc.parametrize(fixture_name, vals, ids=ids)
319
320def generate_config(metafunc, fixture_name):
321 """Provide parametrization for {env,brd}__ fixtures.
Stephen Warren10e50632016-01-15 11:15:24 -0700322
323 If a test function takes parameter(s) (fixture names) of the form brd__xxx
324 or env__xxx, the brd and env configuration dictionaries are consulted to
325 find the list of values to use for those parameters, and the test is
326 parametrized so that it runs once for each combination of values.
327
328 Args:
329 metafunc: The pytest test function.
Stephen Warren770fe172016-02-08 14:44:16 -0700330 fixture_name: The fixture name to test.
Stephen Warren10e50632016-01-15 11:15:24 -0700331
332 Returns:
333 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700334 """
Stephen Warren10e50632016-01-15 11:15:24 -0700335
336 subconfigs = {
337 'brd': console.config.brd,
338 'env': console.config.env,
339 }
Stephen Warren770fe172016-02-08 14:44:16 -0700340 parts = fixture_name.split('__')
341 if len(parts) < 2:
342 return
343 if parts[0] not in subconfigs:
344 return
345 subconfig = subconfigs[parts[0]]
346 vals = []
347 val = subconfig.get(fixture_name, [])
348 # If that exact name is a key in the data source:
349 if val:
350 # ... use the dict value as a single parameter value.
351 vals = (val, )
352 else:
353 # ... otherwise, see if there's a key that contains a list of
354 # values to use instead.
355 vals = subconfig.get(fixture_name+ 's', [])
356 def fixture_id(index, val):
357 try:
358 return val['fixture_id']
359 except:
360 return fixture_name + str(index)
361 ids = [fixture_id(index, val) for (index, val) in enumerate(vals)]
362 metafunc.parametrize(fixture_name, vals, ids=ids)
363
364def pytest_generate_tests(metafunc):
365 """pytest hook: parameterize test functions based on custom rules.
366
367 Check each test function parameter (fixture name) to see if it is one of
368 our custom names, and if so, provide the correct parametrization for that
369 parameter.
370
371 Args:
372 metafunc: The pytest test function.
373
374 Returns:
375 Nothing.
376 """
Stephen Warren10e50632016-01-15 11:15:24 -0700377 for fn in metafunc.fixturenames:
Stephen Warren770fe172016-02-08 14:44:16 -0700378 if fn == 'ut_subtest':
Simon Glassed298be2020-10-25 20:38:31 -0600379 generate_ut_subtest(metafunc, fn, '/u-boot.sym')
380 continue
Simon Glassb6c665f2022-04-30 00:56:55 -0600381 m_subtest = re.match('ut_(.)pl_subtest', fn)
382 if m_subtest:
383 spl_name = m_subtest.group(1)
384 generate_ut_subtest(
385 metafunc, fn, f'/{spl_name}pl/u-boot-{spl_name}pl.sym')
Stephen Warren10e50632016-01-15 11:15:24 -0700386 continue
Stephen Warren770fe172016-02-08 14:44:16 -0700387 generate_config(metafunc, fn)
Stephen Warren10e50632016-01-15 11:15:24 -0700388
Stefan Brüns364ea872016-11-05 17:45:32 +0100389@pytest.fixture(scope='session')
390def u_boot_log(request):
391 """Generate the value of a test's log fixture.
392
393 Args:
394 request: The pytest request.
395
396 Returns:
397 The fixture value.
398 """
399
400 return console.log
401
402@pytest.fixture(scope='session')
403def u_boot_config(request):
404 """Generate the value of a test's u_boot_config fixture.
405
406 Args:
407 request: The pytest request.
408
409 Returns:
410 The fixture value.
411 """
412
413 return console.config
414
Stephen Warrene1d24d02016-01-22 12:30:08 -0700415@pytest.fixture(scope='function')
Stephen Warren10e50632016-01-15 11:15:24 -0700416def u_boot_console(request):
Stephen Warren75e731e2016-01-26 13:41:30 -0700417 """Generate the value of a test's u_boot_console fixture.
Stephen Warren10e50632016-01-15 11:15:24 -0700418
419 Args:
420 request: The pytest request.
421
422 Returns:
423 The fixture value.
Stephen Warren75e731e2016-01-26 13:41:30 -0700424 """
Simon Glassd834d9a2024-10-09 18:29:03 -0600425 if not ubconfig.connection_ok:
426 pytest.skip('Cannot get target connection')
427 return None
428 try:
429 console.ensure_spawned()
430 except OSError as err:
431 handle_exception(ubconfig, console, log, err, 'Lab failure', True)
432 except Timeout as err:
433 handle_exception(ubconfig, console, log, err, 'Lab timeout', True)
434 except BootFail as err:
435 handle_exception(ubconfig, console, log, err, 'Boot fail', True,
436 console.get_spawn_output())
437 except Unexpected:
438 handle_exception(ubconfig, console, log, err, 'Unexpected test output',
439 False)
Stephen Warren10e50632016-01-15 11:15:24 -0700440 return console
441
Stephen Warrene3f2a502016-02-03 16:46:34 -0700442anchors = {}
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700443tests_not_run = []
444tests_failed = []
445tests_xpassed = []
446tests_xfailed = []
447tests_skipped = []
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700448tests_warning = []
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700449tests_passed = []
Stephen Warren10e50632016-01-15 11:15:24 -0700450
451def pytest_itemcollected(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700452 """pytest hook: Called once for each test found during collection.
Stephen Warren10e50632016-01-15 11:15:24 -0700453
454 This enables our custom result analysis code to see the list of all tests
455 that should eventually be run.
456
457 Args:
458 item: The item that was collected.
459
460 Returns:
461 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700462 """
Stephen Warren10e50632016-01-15 11:15:24 -0700463
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700464 tests_not_run.append(item.name)
Stephen Warren10e50632016-01-15 11:15:24 -0700465
466def cleanup():
Stephen Warren75e731e2016-01-26 13:41:30 -0700467 """Clean up all global state.
Stephen Warren10e50632016-01-15 11:15:24 -0700468
469 Executed (via atexit) once the entire test process is complete. This
470 includes logging the status of all tests, and the identity of any failed
471 or skipped tests.
472
473 Args:
474 None.
475
476 Returns:
477 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700478 """
Stephen Warren10e50632016-01-15 11:15:24 -0700479
480 if console:
481 console.close()
482 if log:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700483 with log.section('Status Report', 'status_report'):
484 log.status_pass('%d passed' % len(tests_passed))
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700485 if tests_warning:
486 log.status_warning('%d passed with warning' % len(tests_warning))
487 for test in tests_warning:
488 anchor = anchors.get(test, None)
489 log.status_warning('... ' + test, anchor)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700490 if tests_skipped:
491 log.status_skipped('%d skipped' % len(tests_skipped))
492 for test in tests_skipped:
493 anchor = anchors.get(test, None)
494 log.status_skipped('... ' + test, anchor)
495 if tests_xpassed:
496 log.status_xpass('%d xpass' % len(tests_xpassed))
497 for test in tests_xpassed:
498 anchor = anchors.get(test, None)
499 log.status_xpass('... ' + test, anchor)
500 if tests_xfailed:
501 log.status_xfail('%d xfail' % len(tests_xfailed))
502 for test in tests_xfailed:
503 anchor = anchors.get(test, None)
504 log.status_xfail('... ' + test, anchor)
505 if tests_failed:
506 log.status_fail('%d failed' % len(tests_failed))
507 for test in tests_failed:
508 anchor = anchors.get(test, None)
509 log.status_fail('... ' + test, anchor)
510 if tests_not_run:
511 log.status_fail('%d not run' % len(tests_not_run))
512 for test in tests_not_run:
513 anchor = anchors.get(test, None)
514 log.status_fail('... ' + test, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700515 log.close()
516atexit.register(cleanup)
517
518def setup_boardspec(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700519 """Process any 'boardspec' marker for a test.
Stephen Warren10e50632016-01-15 11:15:24 -0700520
521 Such a marker lists the set of board types that a test does/doesn't
522 support. If tests are being executed on an unsupported board, the test is
523 marked to be skipped.
524
525 Args:
526 item: The pytest test item.
527
528 Returns:
529 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700530 """
Stephen Warren10e50632016-01-15 11:15:24 -0700531
Stephen Warren10e50632016-01-15 11:15:24 -0700532 required_boards = []
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400533 for boards in item.iter_markers('boardspec'):
534 board = boards.args[0]
Stephen Warren10e50632016-01-15 11:15:24 -0700535 if board.startswith('!'):
536 if ubconfig.board_type == board[1:]:
Stephen Warren0f0eeac2017-09-18 11:11:48 -0600537 pytest.skip('board "%s" not supported' % ubconfig.board_type)
Stephen Warren10e50632016-01-15 11:15:24 -0700538 return
539 else:
540 required_boards.append(board)
541 if required_boards and ubconfig.board_type not in required_boards:
Stephen Warren0f0eeac2017-09-18 11:11:48 -0600542 pytest.skip('board "%s" not supported' % ubconfig.board_type)
Stephen Warren10e50632016-01-15 11:15:24 -0700543
544def setup_buildconfigspec(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700545 """Process any 'buildconfigspec' marker for a test.
Stephen Warren10e50632016-01-15 11:15:24 -0700546
547 Such a marker lists some U-Boot configuration feature that the test
548 requires. If tests are being executed on an U-Boot build that doesn't
549 have the required feature, the test is marked to be skipped.
550
551 Args:
552 item: The pytest test item.
553
554 Returns:
555 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700556 """
Stephen Warren10e50632016-01-15 11:15:24 -0700557
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400558 for options in item.iter_markers('buildconfigspec'):
559 option = options.args[0]
560 if not ubconfig.buildconfig.get('config_' + option.lower(), None):
561 pytest.skip('.config feature "%s" not enabled' % option.lower())
Cristian Ciocaltea6c6c8072019-12-24 17:19:12 +0200562 for options in item.iter_markers('notbuildconfigspec'):
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400563 option = options.args[0]
564 if ubconfig.buildconfig.get('config_' + option.lower(), None):
565 pytest.skip('.config feature "%s" enabled' % option.lower())
Stephen Warren10e50632016-01-15 11:15:24 -0700566
Stephen Warren2079db32017-09-18 11:11:49 -0600567def tool_is_in_path(tool):
568 for path in os.environ["PATH"].split(os.pathsep):
569 fn = os.path.join(path, tool)
570 if os.path.isfile(fn) and os.access(fn, os.X_OK):
571 return True
572 return False
573
574def setup_requiredtool(item):
575 """Process any 'requiredtool' marker for a test.
576
577 Such a marker lists some external tool (binary, executable, application)
578 that the test requires. If tests are being executed on a system that
579 doesn't have the required tool, the test is marked to be skipped.
580
581 Args:
582 item: The pytest test item.
583
584 Returns:
585 Nothing.
586 """
587
Marek Vasut9dfdf6e2019-10-24 11:59:19 -0400588 for tools in item.iter_markers('requiredtool'):
589 tool = tools.args[0]
Stephen Warren2079db32017-09-18 11:11:49 -0600590 if not tool_is_in_path(tool):
591 pytest.skip('tool "%s" not in $PATH' % tool)
592
Simon Glass54ef2ca2022-08-06 17:51:47 -0600593def setup_singlethread(item):
594 """Process any 'singlethread' marker for a test.
595
596 Skip this test if running in parallel.
597
598 Args:
599 item: The pytest test item.
600
601 Returns:
602 Nothing.
603 """
604 for single in item.iter_markers('singlethread'):
605 worker_id = os.environ.get("PYTEST_XDIST_WORKER")
606 if worker_id and worker_id != 'master':
607 pytest.skip('must run single-threaded')
608
Stephen Warren3e3d1432016-10-17 17:25:52 -0600609def start_test_section(item):
610 anchors[item.name] = log.start_section(item.name)
611
Stephen Warren10e50632016-01-15 11:15:24 -0700612def pytest_runtest_setup(item):
Stephen Warren75e731e2016-01-26 13:41:30 -0700613 """pytest hook: Configure (set up) a test item.
Stephen Warren10e50632016-01-15 11:15:24 -0700614
615 Called once for each test to perform any custom configuration. This hook
616 is used to skip the test if certain conditions apply.
617
618 Args:
619 item: The pytest test item.
620
621 Returns:
622 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700623 """
Stephen Warren10e50632016-01-15 11:15:24 -0700624
Stephen Warren3e3d1432016-10-17 17:25:52 -0600625 start_test_section(item)
Stephen Warren10e50632016-01-15 11:15:24 -0700626 setup_boardspec(item)
627 setup_buildconfigspec(item)
Stephen Warren2079db32017-09-18 11:11:49 -0600628 setup_requiredtool(item)
Simon Glass54ef2ca2022-08-06 17:51:47 -0600629 setup_singlethread(item)
Stephen Warren10e50632016-01-15 11:15:24 -0700630
631def pytest_runtest_protocol(item, nextitem):
Stephen Warren75e731e2016-01-26 13:41:30 -0700632 """pytest hook: Called to execute a test.
Stephen Warren10e50632016-01-15 11:15:24 -0700633
634 This hook wraps the standard pytest runtestprotocol() function in order
635 to acquire visibility into, and record, each test function's result.
636
637 Args:
638 item: The pytest test item to execute.
639 nextitem: The pytest test item that will be executed after this one.
640
641 Returns:
642 A list of pytest reports (test result data).
Stephen Warren75e731e2016-01-26 13:41:30 -0700643 """
Stephen Warren10e50632016-01-15 11:15:24 -0700644
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700645 log.get_and_reset_warning()
Stephen Warren76e6a9e2021-01-30 20:12:18 -0700646 ihook = item.ihook
647 ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
Stephen Warren10e50632016-01-15 11:15:24 -0700648 reports = runtestprotocol(item, nextitem=nextitem)
Stephen Warren76e6a9e2021-01-30 20:12:18 -0700649 ihook.pytest_runtest_logfinish(nodeid=item.nodeid, location=item.location)
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700650 was_warning = log.get_and_reset_warning()
Stephen Warren25b05242016-01-27 23:57:51 -0700651
Stephen Warren3e3d1432016-10-17 17:25:52 -0600652 # In pytest 3, runtestprotocol() may not call pytest_runtest_setup() if
653 # the test is skipped. That call is required to create the test's section
654 # in the log file. The call to log.end_section() requires that the log
655 # contain a section for this test. Create a section for the test if it
656 # doesn't already exist.
657 if not item.name in anchors:
658 start_test_section(item)
659
Stephen Warren25b05242016-01-27 23:57:51 -0700660 failure_cleanup = False
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700661 if not was_warning:
662 test_list = tests_passed
663 msg = 'OK'
664 msg_log = log.status_pass
665 else:
666 test_list = tests_warning
667 msg = 'OK (with warning)'
668 msg_log = log.status_warning
Stephen Warren10e50632016-01-15 11:15:24 -0700669 for report in reports:
670 if report.outcome == 'failed':
Stephen Warren25b05242016-01-27 23:57:51 -0700671 if hasattr(report, 'wasxfail'):
672 test_list = tests_xpassed
673 msg = 'XPASSED'
674 msg_log = log.status_xpass
675 else:
676 failure_cleanup = True
677 test_list = tests_failed
678 msg = 'FAILED:\n' + str(report.longrepr)
679 msg_log = log.status_fail
Stephen Warren10e50632016-01-15 11:15:24 -0700680 break
681 if report.outcome == 'skipped':
Stephen Warren25b05242016-01-27 23:57:51 -0700682 if hasattr(report, 'wasxfail'):
683 failure_cleanup = True
684 test_list = tests_xfailed
685 msg = 'XFAILED:\n' + str(report.longrepr)
686 msg_log = log.status_xfail
687 break
688 test_list = tests_skipped
689 msg = 'SKIPPED:\n' + str(report.longrepr)
690 msg_log = log.status_skipped
Stephen Warren10e50632016-01-15 11:15:24 -0700691
Stephen Warren25b05242016-01-27 23:57:51 -0700692 if failure_cleanup:
Stephen Warren97a54662016-01-22 12:30:09 -0700693 console.drain_console()
Stephen Warren25b05242016-01-27 23:57:51 -0700694
Stephen Warrenaaf4e912016-02-10 13:47:37 -0700695 test_list.append(item.name)
Stephen Warren10e50632016-01-15 11:15:24 -0700696 tests_not_run.remove(item.name)
697
698 try:
Stephen Warren25b05242016-01-27 23:57:51 -0700699 msg_log(msg)
Stephen Warren10e50632016-01-15 11:15:24 -0700700 except:
701 # If something went wrong with logging, it's better to let the test
702 # process continue, which may report other exceptions that triggered
703 # the logging issue (e.g. console.log wasn't created). Hence, just
704 # squash the exception. If the test setup failed due to e.g. syntax
705 # error somewhere else, this won't be seen. However, once that issue
706 # is fixed, if this exception still exists, it will then be logged as
707 # part of the test's stdout.
708 import traceback
Paul Burton00f2d202017-09-14 14:34:43 -0700709 print('Exception occurred while logging runtest status:')
Stephen Warren10e50632016-01-15 11:15:24 -0700710 traceback.print_exc()
711 # FIXME: Can we force a test failure here?
712
713 log.end_section(item.name)
714
Stephen Warren25b05242016-01-27 23:57:51 -0700715 if failure_cleanup:
Stephen Warren10e50632016-01-15 11:15:24 -0700716 console.cleanup_spawn()
717
Stephen Warren76e6a9e2021-01-30 20:12:18 -0700718 return True