xref: /openbmc/u-boot/test/py/conftest.py (revision d5170448)
1d201506cSStephen Warren# Copyright (c) 2015 Stephen Warren
2d201506cSStephen Warren# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
3d201506cSStephen Warren#
4d201506cSStephen Warren# SPDX-License-Identifier: GPL-2.0
5d201506cSStephen Warren
6d201506cSStephen Warren# Implementation of pytest run-time hook functions. These are invoked by
7d201506cSStephen Warren# pytest at certain points during operation, e.g. startup, for each executed
8d201506cSStephen Warren# test, at shutdown etc. These hooks perform functions such as:
9d201506cSStephen Warren# - Parsing custom command-line options.
10d201506cSStephen Warren# - Pullilng in user-specified board configuration.
11d201506cSStephen Warren# - Creating the U-Boot console test fixture.
12d201506cSStephen Warren# - Creating the HTML log file.
13d201506cSStephen Warren# - Monitoring each test's results.
14d201506cSStephen Warren# - Implementing custom pytest markers.
15d201506cSStephen Warren
16d201506cSStephen Warrenimport atexit
17d201506cSStephen Warrenimport errno
18d201506cSStephen Warrenimport os
19d201506cSStephen Warrenimport os.path
20d201506cSStephen Warrenimport pytest
21d201506cSStephen Warrenfrom _pytest.runner import runtestprotocol
22d201506cSStephen Warrenimport ConfigParser
231cd85f57SStephen Warrenimport re
24d201506cSStephen Warrenimport StringIO
25d201506cSStephen Warrenimport sys
26d201506cSStephen Warren
27d201506cSStephen Warren# Globals: The HTML log file, and the connection to the U-Boot console.
28d201506cSStephen Warrenlog = None
29d201506cSStephen Warrenconsole = None
30d201506cSStephen Warren
31d201506cSStephen Warrendef mkdir_p(path):
32e8debf39SStephen Warren    """Create a directory path.
33d201506cSStephen Warren
34d201506cSStephen Warren    This includes creating any intermediate/parent directories. Any errors
35d201506cSStephen Warren    caused due to already extant directories are ignored.
36d201506cSStephen Warren
37d201506cSStephen Warren    Args:
38d201506cSStephen Warren        path: The directory path to create.
39d201506cSStephen Warren
40d201506cSStephen Warren    Returns:
41d201506cSStephen Warren        Nothing.
42e8debf39SStephen Warren    """
43d201506cSStephen Warren
44d201506cSStephen Warren    try:
45d201506cSStephen Warren        os.makedirs(path)
46d201506cSStephen Warren    except OSError as exc:
47d201506cSStephen Warren        if exc.errno == errno.EEXIST and os.path.isdir(path):
48d201506cSStephen Warren            pass
49d201506cSStephen Warren        else:
50d201506cSStephen Warren            raise
51d201506cSStephen Warren
52d201506cSStephen Warrendef pytest_addoption(parser):
53e8debf39SStephen Warren    """pytest hook: Add custom command-line options to the cmdline parser.
54d201506cSStephen Warren
55d201506cSStephen Warren    Args:
56d201506cSStephen Warren        parser: The pytest command-line parser.
57d201506cSStephen Warren
58d201506cSStephen Warren    Returns:
59d201506cSStephen Warren        Nothing.
60e8debf39SStephen Warren    """
61d201506cSStephen Warren
62d201506cSStephen Warren    parser.addoption('--build-dir', default=None,
63d201506cSStephen Warren        help='U-Boot build directory (O=)')
64d201506cSStephen Warren    parser.addoption('--result-dir', default=None,
65d201506cSStephen Warren        help='U-Boot test result/tmp directory')
66d201506cSStephen Warren    parser.addoption('--persistent-data-dir', default=None,
67d201506cSStephen Warren        help='U-Boot test persistent generated data directory')
68d201506cSStephen Warren    parser.addoption('--board-type', '--bd', '-B', default='sandbox',
69d201506cSStephen Warren        help='U-Boot board type')
70d201506cSStephen Warren    parser.addoption('--board-identity', '--id', default='na',
71d201506cSStephen Warren        help='U-Boot board identity/instance')
72d201506cSStephen Warren    parser.addoption('--build', default=False, action='store_true',
73d201506cSStephen Warren        help='Compile U-Boot before running tests')
7489ab8410SStephen Warren    parser.addoption('--gdbserver', default=None,
7589ab8410SStephen Warren        help='Run sandbox under gdbserver. The argument is the channel '+
7689ab8410SStephen Warren        'over which gdbserver should communicate, e.g. localhost:1234')
77d201506cSStephen Warren
78d201506cSStephen Warrendef pytest_configure(config):
79e8debf39SStephen Warren    """pytest hook: Perform custom initialization at startup time.
80d201506cSStephen Warren
81d201506cSStephen Warren    Args:
82d201506cSStephen Warren        config: The pytest configuration.
83d201506cSStephen Warren
84d201506cSStephen Warren    Returns:
85d201506cSStephen Warren        Nothing.
86e8debf39SStephen Warren    """
87d201506cSStephen Warren
88d201506cSStephen Warren    global log
89d201506cSStephen Warren    global console
90d201506cSStephen Warren    global ubconfig
91d201506cSStephen Warren
92d201506cSStephen Warren    test_py_dir = os.path.dirname(os.path.abspath(__file__))
93d201506cSStephen Warren    source_dir = os.path.dirname(os.path.dirname(test_py_dir))
94d201506cSStephen Warren
95d201506cSStephen Warren    board_type = config.getoption('board_type')
96d201506cSStephen Warren    board_type_filename = board_type.replace('-', '_')
97d201506cSStephen Warren
98d201506cSStephen Warren    board_identity = config.getoption('board_identity')
99d201506cSStephen Warren    board_identity_filename = board_identity.replace('-', '_')
100d201506cSStephen Warren
101d201506cSStephen Warren    build_dir = config.getoption('build_dir')
102d201506cSStephen Warren    if not build_dir:
103d201506cSStephen Warren        build_dir = source_dir + '/build-' + board_type
104d201506cSStephen Warren    mkdir_p(build_dir)
105d201506cSStephen Warren
106d201506cSStephen Warren    result_dir = config.getoption('result_dir')
107d201506cSStephen Warren    if not result_dir:
108d201506cSStephen Warren        result_dir = build_dir
109d201506cSStephen Warren    mkdir_p(result_dir)
110d201506cSStephen Warren
111d201506cSStephen Warren    persistent_data_dir = config.getoption('persistent_data_dir')
112d201506cSStephen Warren    if not persistent_data_dir:
113d201506cSStephen Warren        persistent_data_dir = build_dir + '/persistent-data'
114d201506cSStephen Warren    mkdir_p(persistent_data_dir)
115d201506cSStephen Warren
11689ab8410SStephen Warren    gdbserver = config.getoption('gdbserver')
11789ab8410SStephen Warren    if gdbserver and board_type != 'sandbox':
11889ab8410SStephen Warren        raise Exception('--gdbserver only supported with sandbox')
11989ab8410SStephen Warren
120d201506cSStephen Warren    import multiplexed_log
121d201506cSStephen Warren    log = multiplexed_log.Logfile(result_dir + '/test-log.html')
122d201506cSStephen Warren
123d201506cSStephen Warren    if config.getoption('build'):
124d201506cSStephen Warren        if build_dir != source_dir:
125d201506cSStephen Warren            o_opt = 'O=%s' % build_dir
126d201506cSStephen Warren        else:
127d201506cSStephen Warren            o_opt = ''
128d201506cSStephen Warren        cmds = (
129d201506cSStephen Warren            ['make', o_opt, '-s', board_type + '_defconfig'],
130d201506cSStephen Warren            ['make', o_opt, '-s', '-j8'],
131d201506cSStephen Warren        )
13283357fd5SStephen Warren        with log.section('make'):
133d201506cSStephen Warren            runner = log.get_runner('make', sys.stdout)
134d201506cSStephen Warren            for cmd in cmds:
135d201506cSStephen Warren                runner.run(cmd, cwd=source_dir)
136d201506cSStephen Warren            runner.close()
13783357fd5SStephen Warren            log.status_pass('OK')
138d201506cSStephen Warren
139d201506cSStephen Warren    class ArbitraryAttributeContainer(object):
140d201506cSStephen Warren        pass
141d201506cSStephen Warren
142d201506cSStephen Warren    ubconfig = ArbitraryAttributeContainer()
143d201506cSStephen Warren    ubconfig.brd = dict()
144d201506cSStephen Warren    ubconfig.env = dict()
145d201506cSStephen Warren
146d201506cSStephen Warren    modules = [
147d201506cSStephen Warren        (ubconfig.brd, 'u_boot_board_' + board_type_filename),
148d201506cSStephen Warren        (ubconfig.env, 'u_boot_boardenv_' + board_type_filename),
149d201506cSStephen Warren        (ubconfig.env, 'u_boot_boardenv_' + board_type_filename + '_' +
150d201506cSStephen Warren            board_identity_filename),
151d201506cSStephen Warren    ]
152d201506cSStephen Warren    for (dict_to_fill, module_name) in modules:
153d201506cSStephen Warren        try:
154d201506cSStephen Warren            module = __import__(module_name)
155d201506cSStephen Warren        except ImportError:
156d201506cSStephen Warren            continue
157d201506cSStephen Warren        dict_to_fill.update(module.__dict__)
158d201506cSStephen Warren
159d201506cSStephen Warren    ubconfig.buildconfig = dict()
160d201506cSStephen Warren
161d201506cSStephen Warren    for conf_file in ('.config', 'include/autoconf.mk'):
162d201506cSStephen Warren        dot_config = build_dir + '/' + conf_file
163d201506cSStephen Warren        if not os.path.exists(dot_config):
164d201506cSStephen Warren            raise Exception(conf_file + ' does not exist; ' +
165d201506cSStephen Warren                'try passing --build option?')
166d201506cSStephen Warren
167d201506cSStephen Warren        with open(dot_config, 'rt') as f:
168d201506cSStephen Warren            ini_str = '[root]\n' + f.read()
169d201506cSStephen Warren            ini_sio = StringIO.StringIO(ini_str)
170d201506cSStephen Warren            parser = ConfigParser.RawConfigParser()
171d201506cSStephen Warren            parser.readfp(ini_sio)
172d201506cSStephen Warren            ubconfig.buildconfig.update(parser.items('root'))
173d201506cSStephen Warren
174d201506cSStephen Warren    ubconfig.test_py_dir = test_py_dir
175d201506cSStephen Warren    ubconfig.source_dir = source_dir
176d201506cSStephen Warren    ubconfig.build_dir = build_dir
177d201506cSStephen Warren    ubconfig.result_dir = result_dir
178d201506cSStephen Warren    ubconfig.persistent_data_dir = persistent_data_dir
179d201506cSStephen Warren    ubconfig.board_type = board_type
180d201506cSStephen Warren    ubconfig.board_identity = board_identity
18189ab8410SStephen Warren    ubconfig.gdbserver = gdbserver
1820671960bSSimon Glass    ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb'
183d201506cSStephen Warren
184d201506cSStephen Warren    env_vars = (
185d201506cSStephen Warren        'board_type',
186d201506cSStephen Warren        'board_identity',
187d201506cSStephen Warren        'source_dir',
188d201506cSStephen Warren        'test_py_dir',
189d201506cSStephen Warren        'build_dir',
190d201506cSStephen Warren        'result_dir',
191d201506cSStephen Warren        'persistent_data_dir',
192d201506cSStephen Warren    )
193d201506cSStephen Warren    for v in env_vars:
194d201506cSStephen Warren        os.environ['U_BOOT_' + v.upper()] = getattr(ubconfig, v)
195d201506cSStephen Warren
1962fedbaa4SSimon Glass    if board_type.startswith('sandbox'):
197d201506cSStephen Warren        import u_boot_console_sandbox
198d201506cSStephen Warren        console = u_boot_console_sandbox.ConsoleSandbox(log, ubconfig)
199d201506cSStephen Warren    else:
200d201506cSStephen Warren        import u_boot_console_exec_attach
201d201506cSStephen Warren        console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig)
202d201506cSStephen Warren
2031cd85f57SStephen Warrenre_ut_test_list = re.compile(r'_u_boot_list_2_(dm|env)_test_2_\1_test_(.*)\s*$')
2041cd85f57SStephen Warrendef generate_ut_subtest(metafunc, fixture_name):
2051cd85f57SStephen Warren    """Provide parametrization for a ut_subtest fixture.
2061cd85f57SStephen Warren
2071cd85f57SStephen Warren    Determines the set of unit tests built into a U-Boot binary by parsing the
2081cd85f57SStephen Warren    list of symbols generated by the build process. Provides this information
2091cd85f57SStephen Warren    to test functions by parameterizing their ut_subtest fixture parameter.
2101cd85f57SStephen Warren
2111cd85f57SStephen Warren    Args:
2121cd85f57SStephen Warren        metafunc: The pytest test function.
2131cd85f57SStephen Warren        fixture_name: The fixture name to test.
2141cd85f57SStephen Warren
2151cd85f57SStephen Warren    Returns:
2161cd85f57SStephen Warren        Nothing.
2171cd85f57SStephen Warren    """
2181cd85f57SStephen Warren
2191cd85f57SStephen Warren    fn = console.config.build_dir + '/u-boot.sym'
2201cd85f57SStephen Warren    try:
2211cd85f57SStephen Warren        with open(fn, 'rt') as f:
2221cd85f57SStephen Warren            lines = f.readlines()
2231cd85f57SStephen Warren    except:
2241cd85f57SStephen Warren        lines = []
2251cd85f57SStephen Warren    lines.sort()
2261cd85f57SStephen Warren
2271cd85f57SStephen Warren    vals = []
2281cd85f57SStephen Warren    for l in lines:
2291cd85f57SStephen Warren        m = re_ut_test_list.search(l)
2301cd85f57SStephen Warren        if not m:
2311cd85f57SStephen Warren            continue
2321cd85f57SStephen Warren        vals.append(m.group(1) + ' ' + m.group(2))
2331cd85f57SStephen Warren
2341cd85f57SStephen Warren    ids = ['ut_' + s.replace(' ', '_') for s in vals]
2351cd85f57SStephen Warren    metafunc.parametrize(fixture_name, vals, ids=ids)
2361cd85f57SStephen Warren
2371cd85f57SStephen Warrendef generate_config(metafunc, fixture_name):
2381cd85f57SStephen Warren    """Provide parametrization for {env,brd}__ fixtures.
239d201506cSStephen Warren
240d201506cSStephen Warren    If a test function takes parameter(s) (fixture names) of the form brd__xxx
241d201506cSStephen Warren    or env__xxx, the brd and env configuration dictionaries are consulted to
242d201506cSStephen Warren    find the list of values to use for those parameters, and the test is
243d201506cSStephen Warren    parametrized so that it runs once for each combination of values.
244d201506cSStephen Warren
245d201506cSStephen Warren    Args:
246d201506cSStephen Warren        metafunc: The pytest test function.
2471cd85f57SStephen Warren        fixture_name: The fixture name to test.
248d201506cSStephen Warren
249d201506cSStephen Warren    Returns:
250d201506cSStephen Warren        Nothing.
251e8debf39SStephen Warren    """
252d201506cSStephen Warren
253d201506cSStephen Warren    subconfigs = {
254d201506cSStephen Warren        'brd': console.config.brd,
255d201506cSStephen Warren        'env': console.config.env,
256d201506cSStephen Warren    }
2571cd85f57SStephen Warren    parts = fixture_name.split('__')
258d201506cSStephen Warren    if len(parts) < 2:
2591cd85f57SStephen Warren        return
260d201506cSStephen Warren    if parts[0] not in subconfigs:
2611cd85f57SStephen Warren        return
262d201506cSStephen Warren    subconfig = subconfigs[parts[0]]
263d201506cSStephen Warren    vals = []
2641cd85f57SStephen Warren    val = subconfig.get(fixture_name, [])
265d201506cSStephen Warren    # If that exact name is a key in the data source:
266d201506cSStephen Warren    if val:
267d201506cSStephen Warren        # ... use the dict value as a single parameter value.
268d201506cSStephen Warren        vals = (val, )
269d201506cSStephen Warren    else:
270d201506cSStephen Warren        # ... otherwise, see if there's a key that contains a list of
271d201506cSStephen Warren        # values to use instead.
2721cd85f57SStephen Warren        vals = subconfig.get(fixture_name+ 's', [])
273d20e5e97SStephen Warren    def fixture_id(index, val):
274d20e5e97SStephen Warren        try:
2751cd85f57SStephen Warren            return val['fixture_id']
276d20e5e97SStephen Warren        except:
2771cd85f57SStephen Warren            return fixture_name + str(index)
278d20e5e97SStephen Warren    ids = [fixture_id(index, val) for (index, val) in enumerate(vals)]
2791cd85f57SStephen Warren    metafunc.parametrize(fixture_name, vals, ids=ids)
2801cd85f57SStephen Warren
2811cd85f57SStephen Warrendef pytest_generate_tests(metafunc):
2821cd85f57SStephen Warren    """pytest hook: parameterize test functions based on custom rules.
2831cd85f57SStephen Warren
2841cd85f57SStephen Warren    Check each test function parameter (fixture name) to see if it is one of
2851cd85f57SStephen Warren    our custom names, and if so, provide the correct parametrization for that
2861cd85f57SStephen Warren    parameter.
2871cd85f57SStephen Warren
2881cd85f57SStephen Warren    Args:
2891cd85f57SStephen Warren        metafunc: The pytest test function.
2901cd85f57SStephen Warren
2911cd85f57SStephen Warren    Returns:
2921cd85f57SStephen Warren        Nothing.
2931cd85f57SStephen Warren    """
2941cd85f57SStephen Warren
2951cd85f57SStephen Warren    for fn in metafunc.fixturenames:
2961cd85f57SStephen Warren        if fn == 'ut_subtest':
2971cd85f57SStephen Warren            generate_ut_subtest(metafunc, fn)
2981cd85f57SStephen Warren            continue
2991cd85f57SStephen Warren        generate_config(metafunc, fn)
300d201506cSStephen Warren
301d8c1e033SStefan Brüns@pytest.fixture(scope='session')
302d8c1e033SStefan Brünsdef u_boot_log(request):
303d8c1e033SStefan Brüns     """Generate the value of a test's log fixture.
304d8c1e033SStefan Brüns
305d8c1e033SStefan Brüns     Args:
306d8c1e033SStefan Brüns         request: The pytest request.
307d8c1e033SStefan Brüns
308d8c1e033SStefan Brüns     Returns:
309d8c1e033SStefan Brüns         The fixture value.
310d8c1e033SStefan Brüns     """
311d8c1e033SStefan Brüns
312d8c1e033SStefan Brüns     return console.log
313d8c1e033SStefan Brüns
314d8c1e033SStefan Brüns@pytest.fixture(scope='session')
315d8c1e033SStefan Brünsdef u_boot_config(request):
316d8c1e033SStefan Brüns     """Generate the value of a test's u_boot_config fixture.
317d8c1e033SStefan Brüns
318d8c1e033SStefan Brüns     Args:
319d8c1e033SStefan Brüns         request: The pytest request.
320d8c1e033SStefan Brüns
321d8c1e033SStefan Brüns     Returns:
322d8c1e033SStefan Brüns         The fixture value.
323d8c1e033SStefan Brüns     """
324d8c1e033SStefan Brüns
325d8c1e033SStefan Brüns     return console.config
326d8c1e033SStefan Brüns
327636f38d8SStephen Warren@pytest.fixture(scope='function')
328d201506cSStephen Warrendef u_boot_console(request):
329e8debf39SStephen Warren    """Generate the value of a test's u_boot_console fixture.
330d201506cSStephen Warren
331d201506cSStephen Warren    Args:
332d201506cSStephen Warren        request: The pytest request.
333d201506cSStephen Warren
334d201506cSStephen Warren    Returns:
335d201506cSStephen Warren        The fixture value.
336e8debf39SStephen Warren    """
337d201506cSStephen Warren
338636f38d8SStephen Warren    console.ensure_spawned()
339d201506cSStephen Warren    return console
340d201506cSStephen Warren
34183357fd5SStephen Warrenanchors = {}
3421326022cSStephen Warrentests_not_run = []
3431326022cSStephen Warrentests_failed = []
3441326022cSStephen Warrentests_xpassed = []
3451326022cSStephen Warrentests_xfailed = []
3461326022cSStephen Warrentests_skipped = []
3471326022cSStephen Warrentests_passed = []
348d201506cSStephen Warren
349d201506cSStephen Warrendef pytest_itemcollected(item):
350e8debf39SStephen Warren    """pytest hook: Called once for each test found during collection.
351d201506cSStephen Warren
352d201506cSStephen Warren    This enables our custom result analysis code to see the list of all tests
353d201506cSStephen Warren    that should eventually be run.
354d201506cSStephen Warren
355d201506cSStephen Warren    Args:
356d201506cSStephen Warren        item: The item that was collected.
357d201506cSStephen Warren
358d201506cSStephen Warren    Returns:
359d201506cSStephen Warren        Nothing.
360e8debf39SStephen Warren    """
361d201506cSStephen Warren
3621326022cSStephen Warren    tests_not_run.append(item.name)
363d201506cSStephen Warren
364d201506cSStephen Warrendef cleanup():
365e8debf39SStephen Warren    """Clean up all global state.
366d201506cSStephen Warren
367d201506cSStephen Warren    Executed (via atexit) once the entire test process is complete. This
368d201506cSStephen Warren    includes logging the status of all tests, and the identity of any failed
369d201506cSStephen Warren    or skipped tests.
370d201506cSStephen Warren
371d201506cSStephen Warren    Args:
372d201506cSStephen Warren        None.
373d201506cSStephen Warren
374d201506cSStephen Warren    Returns:
375d201506cSStephen Warren        Nothing.
376e8debf39SStephen Warren    """
377d201506cSStephen Warren
378d201506cSStephen Warren    if console:
379d201506cSStephen Warren        console.close()
380d201506cSStephen Warren    if log:
38183357fd5SStephen Warren        with log.section('Status Report', 'status_report'):
382d201506cSStephen Warren            log.status_pass('%d passed' % len(tests_passed))
383d201506cSStephen Warren            if tests_skipped:
384d201506cSStephen Warren                log.status_skipped('%d skipped' % len(tests_skipped))
385d201506cSStephen Warren                for test in tests_skipped:
38683357fd5SStephen Warren                    anchor = anchors.get(test, None)
38783357fd5SStephen Warren                    log.status_skipped('... ' + test, anchor)
38878b39cc3SStephen Warren            if tests_xpassed:
38978b39cc3SStephen Warren                log.status_xpass('%d xpass' % len(tests_xpassed))
39078b39cc3SStephen Warren                for test in tests_xpassed:
39183357fd5SStephen Warren                    anchor = anchors.get(test, None)
39283357fd5SStephen Warren                    log.status_xpass('... ' + test, anchor)
39378b39cc3SStephen Warren            if tests_xfailed:
39478b39cc3SStephen Warren                log.status_xfail('%d xfail' % len(tests_xfailed))
39578b39cc3SStephen Warren                for test in tests_xfailed:
39683357fd5SStephen Warren                    anchor = anchors.get(test, None)
39783357fd5SStephen Warren                    log.status_xfail('... ' + test, anchor)
398d201506cSStephen Warren            if tests_failed:
399d201506cSStephen Warren                log.status_fail('%d failed' % len(tests_failed))
400d201506cSStephen Warren                for test in tests_failed:
40183357fd5SStephen Warren                    anchor = anchors.get(test, None)
40283357fd5SStephen Warren                    log.status_fail('... ' + test, anchor)
403d201506cSStephen Warren            if tests_not_run:
404d201506cSStephen Warren                log.status_fail('%d not run' % len(tests_not_run))
405d201506cSStephen Warren                for test in tests_not_run:
40683357fd5SStephen Warren                    anchor = anchors.get(test, None)
40783357fd5SStephen Warren                    log.status_fail('... ' + test, anchor)
408d201506cSStephen Warren        log.close()
409d201506cSStephen Warrenatexit.register(cleanup)
410d201506cSStephen Warren
411d201506cSStephen Warrendef setup_boardspec(item):
412e8debf39SStephen Warren    """Process any 'boardspec' marker for a test.
413d201506cSStephen Warren
414d201506cSStephen Warren    Such a marker lists the set of board types that a test does/doesn't
415d201506cSStephen Warren    support. If tests are being executed on an unsupported board, the test is
416d201506cSStephen Warren    marked to be skipped.
417d201506cSStephen Warren
418d201506cSStephen Warren    Args:
419d201506cSStephen Warren        item: The pytest test item.
420d201506cSStephen Warren
421d201506cSStephen Warren    Returns:
422d201506cSStephen Warren        Nothing.
423e8debf39SStephen Warren    """
424d201506cSStephen Warren
425d201506cSStephen Warren    mark = item.get_marker('boardspec')
426d201506cSStephen Warren    if not mark:
427d201506cSStephen Warren        return
428d201506cSStephen Warren    required_boards = []
429d201506cSStephen Warren    for board in mark.args:
430d201506cSStephen Warren        if board.startswith('!'):
431d201506cSStephen Warren            if ubconfig.board_type == board[1:]:
432*d5170448SStephen Warren                pytest.skip('board "%s" not supported' % ubconfig.board_type)
433d201506cSStephen Warren                return
434d201506cSStephen Warren        else:
435d201506cSStephen Warren            required_boards.append(board)
436d201506cSStephen Warren    if required_boards and ubconfig.board_type not in required_boards:
437*d5170448SStephen Warren        pytest.skip('board "%s" not supported' % ubconfig.board_type)
438d201506cSStephen Warren
439d201506cSStephen Warrendef setup_buildconfigspec(item):
440e8debf39SStephen Warren    """Process any 'buildconfigspec' marker for a test.
441d201506cSStephen Warren
442d201506cSStephen Warren    Such a marker lists some U-Boot configuration feature that the test
443d201506cSStephen Warren    requires. If tests are being executed on an U-Boot build that doesn't
444d201506cSStephen Warren    have the required feature, the test is marked to be skipped.
445d201506cSStephen Warren
446d201506cSStephen Warren    Args:
447d201506cSStephen Warren        item: The pytest test item.
448d201506cSStephen Warren
449d201506cSStephen Warren    Returns:
450d201506cSStephen Warren        Nothing.
451e8debf39SStephen Warren    """
452d201506cSStephen Warren
453d201506cSStephen Warren    mark = item.get_marker('buildconfigspec')
454d201506cSStephen Warren    if not mark:
455d201506cSStephen Warren        return
456d201506cSStephen Warren    for option in mark.args:
457d201506cSStephen Warren        if not ubconfig.buildconfig.get('config_' + option.lower(), None):
458*d5170448SStephen Warren            pytest.skip('.config feature "%s" not enabled' % option.lower())
459d201506cSStephen Warren
460b0a928a1SStephen Warrendef start_test_section(item):
461b0a928a1SStephen Warren    anchors[item.name] = log.start_section(item.name)
462b0a928a1SStephen Warren
463d201506cSStephen Warrendef pytest_runtest_setup(item):
464e8debf39SStephen Warren    """pytest hook: Configure (set up) a test item.
465d201506cSStephen Warren
466d201506cSStephen Warren    Called once for each test to perform any custom configuration. This hook
467d201506cSStephen Warren    is used to skip the test if certain conditions apply.
468d201506cSStephen Warren
469d201506cSStephen Warren    Args:
470d201506cSStephen Warren        item: The pytest test item.
471d201506cSStephen Warren
472d201506cSStephen Warren    Returns:
473d201506cSStephen Warren        Nothing.
474e8debf39SStephen Warren    """
475d201506cSStephen Warren
476b0a928a1SStephen Warren    start_test_section(item)
477d201506cSStephen Warren    setup_boardspec(item)
478d201506cSStephen Warren    setup_buildconfigspec(item)
479d201506cSStephen Warren
480d201506cSStephen Warrendef pytest_runtest_protocol(item, nextitem):
481e8debf39SStephen Warren    """pytest hook: Called to execute a test.
482d201506cSStephen Warren
483d201506cSStephen Warren    This hook wraps the standard pytest runtestprotocol() function in order
484d201506cSStephen Warren    to acquire visibility into, and record, each test function's result.
485d201506cSStephen Warren
486d201506cSStephen Warren    Args:
487d201506cSStephen Warren        item: The pytest test item to execute.
488d201506cSStephen Warren        nextitem: The pytest test item that will be executed after this one.
489d201506cSStephen Warren
490d201506cSStephen Warren    Returns:
491d201506cSStephen Warren        A list of pytest reports (test result data).
492e8debf39SStephen Warren    """
493d201506cSStephen Warren
494d201506cSStephen Warren    reports = runtestprotocol(item, nextitem=nextitem)
49578b39cc3SStephen Warren
496b0a928a1SStephen Warren    # In pytest 3, runtestprotocol() may not call pytest_runtest_setup() if
497b0a928a1SStephen Warren    # the test is skipped. That call is required to create the test's section
498b0a928a1SStephen Warren    # in the log file. The call to log.end_section() requires that the log
499b0a928a1SStephen Warren    # contain a section for this test. Create a section for the test if it
500b0a928a1SStephen Warren    # doesn't already exist.
501b0a928a1SStephen Warren    if not item.name in anchors:
502b0a928a1SStephen Warren        start_test_section(item)
503b0a928a1SStephen Warren
50478b39cc3SStephen Warren    failure_cleanup = False
50578b39cc3SStephen Warren    test_list = tests_passed
50678b39cc3SStephen Warren    msg = 'OK'
50778b39cc3SStephen Warren    msg_log = log.status_pass
508d201506cSStephen Warren    for report in reports:
509d201506cSStephen Warren        if report.outcome == 'failed':
51078b39cc3SStephen Warren            if hasattr(report, 'wasxfail'):
51178b39cc3SStephen Warren                test_list = tests_xpassed
51278b39cc3SStephen Warren                msg = 'XPASSED'
51378b39cc3SStephen Warren                msg_log = log.status_xpass
51478b39cc3SStephen Warren            else:
51578b39cc3SStephen Warren                failure_cleanup = True
51678b39cc3SStephen Warren                test_list = tests_failed
51778b39cc3SStephen Warren                msg = 'FAILED:\n' + str(report.longrepr)
51878b39cc3SStephen Warren                msg_log = log.status_fail
519d201506cSStephen Warren            break
520d201506cSStephen Warren        if report.outcome == 'skipped':
52178b39cc3SStephen Warren            if hasattr(report, 'wasxfail'):
52278b39cc3SStephen Warren                failure_cleanup = True
52378b39cc3SStephen Warren                test_list = tests_xfailed
52478b39cc3SStephen Warren                msg = 'XFAILED:\n' + str(report.longrepr)
52578b39cc3SStephen Warren                msg_log = log.status_xfail
52678b39cc3SStephen Warren                break
52778b39cc3SStephen Warren            test_list = tests_skipped
52878b39cc3SStephen Warren            msg = 'SKIPPED:\n' + str(report.longrepr)
52978b39cc3SStephen Warren            msg_log = log.status_skipped
530d201506cSStephen Warren
53178b39cc3SStephen Warren    if failure_cleanup:
532c10eb9d3SStephen Warren        console.drain_console()
53378b39cc3SStephen Warren
5341326022cSStephen Warren    test_list.append(item.name)
535d201506cSStephen Warren    tests_not_run.remove(item.name)
536d201506cSStephen Warren
537d201506cSStephen Warren    try:
53878b39cc3SStephen Warren        msg_log(msg)
539d201506cSStephen Warren    except:
540d201506cSStephen Warren        # If something went wrong with logging, it's better to let the test
541d201506cSStephen Warren        # process continue, which may report other exceptions that triggered
542d201506cSStephen Warren        # the logging issue (e.g. console.log wasn't created). Hence, just
543d201506cSStephen Warren        # squash the exception. If the test setup failed due to e.g. syntax
544d201506cSStephen Warren        # error somewhere else, this won't be seen. However, once that issue
545d201506cSStephen Warren        # is fixed, if this exception still exists, it will then be logged as
546d201506cSStephen Warren        # part of the test's stdout.
547d201506cSStephen Warren        import traceback
548d201506cSStephen Warren        print 'Exception occurred while logging runtest status:'
549d201506cSStephen Warren        traceback.print_exc()
550d201506cSStephen Warren        # FIXME: Can we force a test failure here?
551d201506cSStephen Warren
552d201506cSStephen Warren    log.end_section(item.name)
553d201506cSStephen Warren
55478b39cc3SStephen Warren    if failure_cleanup:
555d201506cSStephen Warren        console.cleanup_spawn()
556d201506cSStephen Warren
557d201506cSStephen Warren    return reports
558