xref: /OK3568_Linux_fs/u-boot/test/py/conftest.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# Copyright (c) 2015 Stephen Warren
2*4882a593Smuzhiyun# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0
5*4882a593Smuzhiyun
6*4882a593Smuzhiyun# Implementation of pytest run-time hook functions. These are invoked by
7*4882a593Smuzhiyun# pytest at certain points during operation, e.g. startup, for each executed
8*4882a593Smuzhiyun# test, at shutdown etc. These hooks perform functions such as:
9*4882a593Smuzhiyun# - Parsing custom command-line options.
10*4882a593Smuzhiyun# - Pullilng in user-specified board configuration.
11*4882a593Smuzhiyun# - Creating the U-Boot console test fixture.
12*4882a593Smuzhiyun# - Creating the HTML log file.
13*4882a593Smuzhiyun# - Monitoring each test's results.
14*4882a593Smuzhiyun# - Implementing custom pytest markers.
15*4882a593Smuzhiyun
16*4882a593Smuzhiyunimport atexit
17*4882a593Smuzhiyunimport errno
18*4882a593Smuzhiyunimport os
19*4882a593Smuzhiyunimport os.path
20*4882a593Smuzhiyunimport pytest
21*4882a593Smuzhiyunfrom _pytest.runner import runtestprotocol
22*4882a593Smuzhiyunimport ConfigParser
23*4882a593Smuzhiyunimport re
24*4882a593Smuzhiyunimport StringIO
25*4882a593Smuzhiyunimport sys
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun# Globals: The HTML log file, and the connection to the U-Boot console.
28*4882a593Smuzhiyunlog = None
29*4882a593Smuzhiyunconsole = None
30*4882a593Smuzhiyun
31*4882a593Smuzhiyundef mkdir_p(path):
32*4882a593Smuzhiyun    """Create a directory path.
33*4882a593Smuzhiyun
34*4882a593Smuzhiyun    This includes creating any intermediate/parent directories. Any errors
35*4882a593Smuzhiyun    caused due to already extant directories are ignored.
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun    Args:
38*4882a593Smuzhiyun        path: The directory path to create.
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun    Returns:
41*4882a593Smuzhiyun        Nothing.
42*4882a593Smuzhiyun    """
43*4882a593Smuzhiyun
44*4882a593Smuzhiyun    try:
45*4882a593Smuzhiyun        os.makedirs(path)
46*4882a593Smuzhiyun    except OSError as exc:
47*4882a593Smuzhiyun        if exc.errno == errno.EEXIST and os.path.isdir(path):
48*4882a593Smuzhiyun            pass
49*4882a593Smuzhiyun        else:
50*4882a593Smuzhiyun            raise
51*4882a593Smuzhiyun
52*4882a593Smuzhiyundef pytest_addoption(parser):
53*4882a593Smuzhiyun    """pytest hook: Add custom command-line options to the cmdline parser.
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun    Args:
56*4882a593Smuzhiyun        parser: The pytest command-line parser.
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun    Returns:
59*4882a593Smuzhiyun        Nothing.
60*4882a593Smuzhiyun    """
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun    parser.addoption('--build-dir', default=None,
63*4882a593Smuzhiyun        help='U-Boot build directory (O=)')
64*4882a593Smuzhiyun    parser.addoption('--result-dir', default=None,
65*4882a593Smuzhiyun        help='U-Boot test result/tmp directory')
66*4882a593Smuzhiyun    parser.addoption('--persistent-data-dir', default=None,
67*4882a593Smuzhiyun        help='U-Boot test persistent generated data directory')
68*4882a593Smuzhiyun    parser.addoption('--board-type', '--bd', '-B', default='sandbox',
69*4882a593Smuzhiyun        help='U-Boot board type')
70*4882a593Smuzhiyun    parser.addoption('--board-identity', '--id', default='na',
71*4882a593Smuzhiyun        help='U-Boot board identity/instance')
72*4882a593Smuzhiyun    parser.addoption('--build', default=False, action='store_true',
73*4882a593Smuzhiyun        help='Compile U-Boot before running tests')
74*4882a593Smuzhiyun    parser.addoption('--gdbserver', default=None,
75*4882a593Smuzhiyun        help='Run sandbox under gdbserver. The argument is the channel '+
76*4882a593Smuzhiyun        'over which gdbserver should communicate, e.g. localhost:1234')
77*4882a593Smuzhiyun
78*4882a593Smuzhiyundef pytest_configure(config):
79*4882a593Smuzhiyun    """pytest hook: Perform custom initialization at startup time.
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun    Args:
82*4882a593Smuzhiyun        config: The pytest configuration.
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun    Returns:
85*4882a593Smuzhiyun        Nothing.
86*4882a593Smuzhiyun    """
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun    global log
89*4882a593Smuzhiyun    global console
90*4882a593Smuzhiyun    global ubconfig
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun    test_py_dir = os.path.dirname(os.path.abspath(__file__))
93*4882a593Smuzhiyun    source_dir = os.path.dirname(os.path.dirname(test_py_dir))
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun    board_type = config.getoption('board_type')
96*4882a593Smuzhiyun    board_type_filename = board_type.replace('-', '_')
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun    board_identity = config.getoption('board_identity')
99*4882a593Smuzhiyun    board_identity_filename = board_identity.replace('-', '_')
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun    build_dir = config.getoption('build_dir')
102*4882a593Smuzhiyun    if not build_dir:
103*4882a593Smuzhiyun        build_dir = source_dir + '/build-' + board_type
104*4882a593Smuzhiyun    mkdir_p(build_dir)
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun    result_dir = config.getoption('result_dir')
107*4882a593Smuzhiyun    if not result_dir:
108*4882a593Smuzhiyun        result_dir = build_dir
109*4882a593Smuzhiyun    mkdir_p(result_dir)
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun    persistent_data_dir = config.getoption('persistent_data_dir')
112*4882a593Smuzhiyun    if not persistent_data_dir:
113*4882a593Smuzhiyun        persistent_data_dir = build_dir + '/persistent-data'
114*4882a593Smuzhiyun    mkdir_p(persistent_data_dir)
115*4882a593Smuzhiyun
116*4882a593Smuzhiyun    gdbserver = config.getoption('gdbserver')
117*4882a593Smuzhiyun    if gdbserver and board_type != 'sandbox':
118*4882a593Smuzhiyun        raise Exception('--gdbserver only supported with sandbox')
119*4882a593Smuzhiyun
120*4882a593Smuzhiyun    import multiplexed_log
121*4882a593Smuzhiyun    log = multiplexed_log.Logfile(result_dir + '/test-log.html')
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun    if config.getoption('build'):
124*4882a593Smuzhiyun        if build_dir != source_dir:
125*4882a593Smuzhiyun            o_opt = 'O=%s' % build_dir
126*4882a593Smuzhiyun        else:
127*4882a593Smuzhiyun            o_opt = ''
128*4882a593Smuzhiyun        cmds = (
129*4882a593Smuzhiyun            ['make', o_opt, '-s', board_type + '_defconfig'],
130*4882a593Smuzhiyun            ['make', o_opt, '-s', '-j8'],
131*4882a593Smuzhiyun        )
132*4882a593Smuzhiyun        with log.section('make'):
133*4882a593Smuzhiyun            runner = log.get_runner('make', sys.stdout)
134*4882a593Smuzhiyun            for cmd in cmds:
135*4882a593Smuzhiyun                runner.run(cmd, cwd=source_dir)
136*4882a593Smuzhiyun            runner.close()
137*4882a593Smuzhiyun            log.status_pass('OK')
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun    class ArbitraryAttributeContainer(object):
140*4882a593Smuzhiyun        pass
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun    ubconfig = ArbitraryAttributeContainer()
143*4882a593Smuzhiyun    ubconfig.brd = dict()
144*4882a593Smuzhiyun    ubconfig.env = dict()
145*4882a593Smuzhiyun
146*4882a593Smuzhiyun    modules = [
147*4882a593Smuzhiyun        (ubconfig.brd, 'u_boot_board_' + board_type_filename),
148*4882a593Smuzhiyun        (ubconfig.env, 'u_boot_boardenv_' + board_type_filename),
149*4882a593Smuzhiyun        (ubconfig.env, 'u_boot_boardenv_' + board_type_filename + '_' +
150*4882a593Smuzhiyun            board_identity_filename),
151*4882a593Smuzhiyun    ]
152*4882a593Smuzhiyun    for (dict_to_fill, module_name) in modules:
153*4882a593Smuzhiyun        try:
154*4882a593Smuzhiyun            module = __import__(module_name)
155*4882a593Smuzhiyun        except ImportError:
156*4882a593Smuzhiyun            continue
157*4882a593Smuzhiyun        dict_to_fill.update(module.__dict__)
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun    ubconfig.buildconfig = dict()
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun    for conf_file in ('.config', 'include/autoconf.mk'):
162*4882a593Smuzhiyun        dot_config = build_dir + '/' + conf_file
163*4882a593Smuzhiyun        if not os.path.exists(dot_config):
164*4882a593Smuzhiyun            raise Exception(conf_file + ' does not exist; ' +
165*4882a593Smuzhiyun                'try passing --build option?')
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun        with open(dot_config, 'rt') as f:
168*4882a593Smuzhiyun            ini_str = '[root]\n' + f.read()
169*4882a593Smuzhiyun            ini_sio = StringIO.StringIO(ini_str)
170*4882a593Smuzhiyun            parser = ConfigParser.RawConfigParser()
171*4882a593Smuzhiyun            parser.readfp(ini_sio)
172*4882a593Smuzhiyun            ubconfig.buildconfig.update(parser.items('root'))
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun    ubconfig.test_py_dir = test_py_dir
175*4882a593Smuzhiyun    ubconfig.source_dir = source_dir
176*4882a593Smuzhiyun    ubconfig.build_dir = build_dir
177*4882a593Smuzhiyun    ubconfig.result_dir = result_dir
178*4882a593Smuzhiyun    ubconfig.persistent_data_dir = persistent_data_dir
179*4882a593Smuzhiyun    ubconfig.board_type = board_type
180*4882a593Smuzhiyun    ubconfig.board_identity = board_identity
181*4882a593Smuzhiyun    ubconfig.gdbserver = gdbserver
182*4882a593Smuzhiyun    ubconfig.dtb = build_dir + '/arch/sandbox/dts/test.dtb'
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun    env_vars = (
185*4882a593Smuzhiyun        'board_type',
186*4882a593Smuzhiyun        'board_identity',
187*4882a593Smuzhiyun        'source_dir',
188*4882a593Smuzhiyun        'test_py_dir',
189*4882a593Smuzhiyun        'build_dir',
190*4882a593Smuzhiyun        'result_dir',
191*4882a593Smuzhiyun        'persistent_data_dir',
192*4882a593Smuzhiyun    )
193*4882a593Smuzhiyun    for v in env_vars:
194*4882a593Smuzhiyun        os.environ['U_BOOT_' + v.upper()] = getattr(ubconfig, v)
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun    if board_type.startswith('sandbox'):
197*4882a593Smuzhiyun        import u_boot_console_sandbox
198*4882a593Smuzhiyun        console = u_boot_console_sandbox.ConsoleSandbox(log, ubconfig)
199*4882a593Smuzhiyun    else:
200*4882a593Smuzhiyun        import u_boot_console_exec_attach
201*4882a593Smuzhiyun        console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig)
202*4882a593Smuzhiyun
203*4882a593Smuzhiyunre_ut_test_list = re.compile(r'_u_boot_list_2_(dm|env)_test_2_\1_test_(.*)\s*$')
204*4882a593Smuzhiyundef generate_ut_subtest(metafunc, fixture_name):
205*4882a593Smuzhiyun    """Provide parametrization for a ut_subtest fixture.
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun    Determines the set of unit tests built into a U-Boot binary by parsing the
208*4882a593Smuzhiyun    list of symbols generated by the build process. Provides this information
209*4882a593Smuzhiyun    to test functions by parameterizing their ut_subtest fixture parameter.
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun    Args:
212*4882a593Smuzhiyun        metafunc: The pytest test function.
213*4882a593Smuzhiyun        fixture_name: The fixture name to test.
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun    Returns:
216*4882a593Smuzhiyun        Nothing.
217*4882a593Smuzhiyun    """
218*4882a593Smuzhiyun
219*4882a593Smuzhiyun    fn = console.config.build_dir + '/u-boot.sym'
220*4882a593Smuzhiyun    try:
221*4882a593Smuzhiyun        with open(fn, 'rt') as f:
222*4882a593Smuzhiyun            lines = f.readlines()
223*4882a593Smuzhiyun    except:
224*4882a593Smuzhiyun        lines = []
225*4882a593Smuzhiyun    lines.sort()
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun    vals = []
228*4882a593Smuzhiyun    for l in lines:
229*4882a593Smuzhiyun        m = re_ut_test_list.search(l)
230*4882a593Smuzhiyun        if not m:
231*4882a593Smuzhiyun            continue
232*4882a593Smuzhiyun        vals.append(m.group(1) + ' ' + m.group(2))
233*4882a593Smuzhiyun
234*4882a593Smuzhiyun    ids = ['ut_' + s.replace(' ', '_') for s in vals]
235*4882a593Smuzhiyun    metafunc.parametrize(fixture_name, vals, ids=ids)
236*4882a593Smuzhiyun
237*4882a593Smuzhiyundef generate_config(metafunc, fixture_name):
238*4882a593Smuzhiyun    """Provide parametrization for {env,brd}__ fixtures.
239*4882a593Smuzhiyun
240*4882a593Smuzhiyun    If a test function takes parameter(s) (fixture names) of the form brd__xxx
241*4882a593Smuzhiyun    or env__xxx, the brd and env configuration dictionaries are consulted to
242*4882a593Smuzhiyun    find the list of values to use for those parameters, and the test is
243*4882a593Smuzhiyun    parametrized so that it runs once for each combination of values.
244*4882a593Smuzhiyun
245*4882a593Smuzhiyun    Args:
246*4882a593Smuzhiyun        metafunc: The pytest test function.
247*4882a593Smuzhiyun        fixture_name: The fixture name to test.
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun    Returns:
250*4882a593Smuzhiyun        Nothing.
251*4882a593Smuzhiyun    """
252*4882a593Smuzhiyun
253*4882a593Smuzhiyun    subconfigs = {
254*4882a593Smuzhiyun        'brd': console.config.brd,
255*4882a593Smuzhiyun        'env': console.config.env,
256*4882a593Smuzhiyun    }
257*4882a593Smuzhiyun    parts = fixture_name.split('__')
258*4882a593Smuzhiyun    if len(parts) < 2:
259*4882a593Smuzhiyun        return
260*4882a593Smuzhiyun    if parts[0] not in subconfigs:
261*4882a593Smuzhiyun        return
262*4882a593Smuzhiyun    subconfig = subconfigs[parts[0]]
263*4882a593Smuzhiyun    vals = []
264*4882a593Smuzhiyun    val = subconfig.get(fixture_name, [])
265*4882a593Smuzhiyun    # If that exact name is a key in the data source:
266*4882a593Smuzhiyun    if val:
267*4882a593Smuzhiyun        # ... use the dict value as a single parameter value.
268*4882a593Smuzhiyun        vals = (val, )
269*4882a593Smuzhiyun    else:
270*4882a593Smuzhiyun        # ... otherwise, see if there's a key that contains a list of
271*4882a593Smuzhiyun        # values to use instead.
272*4882a593Smuzhiyun        vals = subconfig.get(fixture_name+ 's', [])
273*4882a593Smuzhiyun    def fixture_id(index, val):
274*4882a593Smuzhiyun        try:
275*4882a593Smuzhiyun            return val['fixture_id']
276*4882a593Smuzhiyun        except:
277*4882a593Smuzhiyun            return fixture_name + str(index)
278*4882a593Smuzhiyun    ids = [fixture_id(index, val) for (index, val) in enumerate(vals)]
279*4882a593Smuzhiyun    metafunc.parametrize(fixture_name, vals, ids=ids)
280*4882a593Smuzhiyun
281*4882a593Smuzhiyundef pytest_generate_tests(metafunc):
282*4882a593Smuzhiyun    """pytest hook: parameterize test functions based on custom rules.
283*4882a593Smuzhiyun
284*4882a593Smuzhiyun    Check each test function parameter (fixture name) to see if it is one of
285*4882a593Smuzhiyun    our custom names, and if so, provide the correct parametrization for that
286*4882a593Smuzhiyun    parameter.
287*4882a593Smuzhiyun
288*4882a593Smuzhiyun    Args:
289*4882a593Smuzhiyun        metafunc: The pytest test function.
290*4882a593Smuzhiyun
291*4882a593Smuzhiyun    Returns:
292*4882a593Smuzhiyun        Nothing.
293*4882a593Smuzhiyun    """
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun    for fn in metafunc.fixturenames:
296*4882a593Smuzhiyun        if fn == 'ut_subtest':
297*4882a593Smuzhiyun            generate_ut_subtest(metafunc, fn)
298*4882a593Smuzhiyun            continue
299*4882a593Smuzhiyun        generate_config(metafunc, fn)
300*4882a593Smuzhiyun
301*4882a593Smuzhiyun@pytest.fixture(scope='session')
302*4882a593Smuzhiyundef u_boot_log(request):
303*4882a593Smuzhiyun     """Generate the value of a test's log fixture.
304*4882a593Smuzhiyun
305*4882a593Smuzhiyun     Args:
306*4882a593Smuzhiyun         request: The pytest request.
307*4882a593Smuzhiyun
308*4882a593Smuzhiyun     Returns:
309*4882a593Smuzhiyun         The fixture value.
310*4882a593Smuzhiyun     """
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun     return console.log
313*4882a593Smuzhiyun
314*4882a593Smuzhiyun@pytest.fixture(scope='session')
315*4882a593Smuzhiyundef u_boot_config(request):
316*4882a593Smuzhiyun     """Generate the value of a test's u_boot_config fixture.
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun     Args:
319*4882a593Smuzhiyun         request: The pytest request.
320*4882a593Smuzhiyun
321*4882a593Smuzhiyun     Returns:
322*4882a593Smuzhiyun         The fixture value.
323*4882a593Smuzhiyun     """
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun     return console.config
326*4882a593Smuzhiyun
327*4882a593Smuzhiyun@pytest.fixture(scope='function')
328*4882a593Smuzhiyundef u_boot_console(request):
329*4882a593Smuzhiyun    """Generate the value of a test's u_boot_console fixture.
330*4882a593Smuzhiyun
331*4882a593Smuzhiyun    Args:
332*4882a593Smuzhiyun        request: The pytest request.
333*4882a593Smuzhiyun
334*4882a593Smuzhiyun    Returns:
335*4882a593Smuzhiyun        The fixture value.
336*4882a593Smuzhiyun    """
337*4882a593Smuzhiyun
338*4882a593Smuzhiyun    console.ensure_spawned()
339*4882a593Smuzhiyun    return console
340*4882a593Smuzhiyun
341*4882a593Smuzhiyunanchors = {}
342*4882a593Smuzhiyuntests_not_run = []
343*4882a593Smuzhiyuntests_failed = []
344*4882a593Smuzhiyuntests_xpassed = []
345*4882a593Smuzhiyuntests_xfailed = []
346*4882a593Smuzhiyuntests_skipped = []
347*4882a593Smuzhiyuntests_passed = []
348*4882a593Smuzhiyun
349*4882a593Smuzhiyundef pytest_itemcollected(item):
350*4882a593Smuzhiyun    """pytest hook: Called once for each test found during collection.
351*4882a593Smuzhiyun
352*4882a593Smuzhiyun    This enables our custom result analysis code to see the list of all tests
353*4882a593Smuzhiyun    that should eventually be run.
354*4882a593Smuzhiyun
355*4882a593Smuzhiyun    Args:
356*4882a593Smuzhiyun        item: The item that was collected.
357*4882a593Smuzhiyun
358*4882a593Smuzhiyun    Returns:
359*4882a593Smuzhiyun        Nothing.
360*4882a593Smuzhiyun    """
361*4882a593Smuzhiyun
362*4882a593Smuzhiyun    tests_not_run.append(item.name)
363*4882a593Smuzhiyun
364*4882a593Smuzhiyundef cleanup():
365*4882a593Smuzhiyun    """Clean up all global state.
366*4882a593Smuzhiyun
367*4882a593Smuzhiyun    Executed (via atexit) once the entire test process is complete. This
368*4882a593Smuzhiyun    includes logging the status of all tests, and the identity of any failed
369*4882a593Smuzhiyun    or skipped tests.
370*4882a593Smuzhiyun
371*4882a593Smuzhiyun    Args:
372*4882a593Smuzhiyun        None.
373*4882a593Smuzhiyun
374*4882a593Smuzhiyun    Returns:
375*4882a593Smuzhiyun        Nothing.
376*4882a593Smuzhiyun    """
377*4882a593Smuzhiyun
378*4882a593Smuzhiyun    if console:
379*4882a593Smuzhiyun        console.close()
380*4882a593Smuzhiyun    if log:
381*4882a593Smuzhiyun        with log.section('Status Report', 'status_report'):
382*4882a593Smuzhiyun            log.status_pass('%d passed' % len(tests_passed))
383*4882a593Smuzhiyun            if tests_skipped:
384*4882a593Smuzhiyun                log.status_skipped('%d skipped' % len(tests_skipped))
385*4882a593Smuzhiyun                for test in tests_skipped:
386*4882a593Smuzhiyun                    anchor = anchors.get(test, None)
387*4882a593Smuzhiyun                    log.status_skipped('... ' + test, anchor)
388*4882a593Smuzhiyun            if tests_xpassed:
389*4882a593Smuzhiyun                log.status_xpass('%d xpass' % len(tests_xpassed))
390*4882a593Smuzhiyun                for test in tests_xpassed:
391*4882a593Smuzhiyun                    anchor = anchors.get(test, None)
392*4882a593Smuzhiyun                    log.status_xpass('... ' + test, anchor)
393*4882a593Smuzhiyun            if tests_xfailed:
394*4882a593Smuzhiyun                log.status_xfail('%d xfail' % len(tests_xfailed))
395*4882a593Smuzhiyun                for test in tests_xfailed:
396*4882a593Smuzhiyun                    anchor = anchors.get(test, None)
397*4882a593Smuzhiyun                    log.status_xfail('... ' + test, anchor)
398*4882a593Smuzhiyun            if tests_failed:
399*4882a593Smuzhiyun                log.status_fail('%d failed' % len(tests_failed))
400*4882a593Smuzhiyun                for test in tests_failed:
401*4882a593Smuzhiyun                    anchor = anchors.get(test, None)
402*4882a593Smuzhiyun                    log.status_fail('... ' + test, anchor)
403*4882a593Smuzhiyun            if tests_not_run:
404*4882a593Smuzhiyun                log.status_fail('%d not run' % len(tests_not_run))
405*4882a593Smuzhiyun                for test in tests_not_run:
406*4882a593Smuzhiyun                    anchor = anchors.get(test, None)
407*4882a593Smuzhiyun                    log.status_fail('... ' + test, anchor)
408*4882a593Smuzhiyun        log.close()
409*4882a593Smuzhiyunatexit.register(cleanup)
410*4882a593Smuzhiyun
411*4882a593Smuzhiyundef setup_boardspec(item):
412*4882a593Smuzhiyun    """Process any 'boardspec' marker for a test.
413*4882a593Smuzhiyun
414*4882a593Smuzhiyun    Such a marker lists the set of board types that a test does/doesn't
415*4882a593Smuzhiyun    support. If tests are being executed on an unsupported board, the test is
416*4882a593Smuzhiyun    marked to be skipped.
417*4882a593Smuzhiyun
418*4882a593Smuzhiyun    Args:
419*4882a593Smuzhiyun        item: The pytest test item.
420*4882a593Smuzhiyun
421*4882a593Smuzhiyun    Returns:
422*4882a593Smuzhiyun        Nothing.
423*4882a593Smuzhiyun    """
424*4882a593Smuzhiyun
425*4882a593Smuzhiyun    mark = item.get_marker('boardspec')
426*4882a593Smuzhiyun    if not mark:
427*4882a593Smuzhiyun        return
428*4882a593Smuzhiyun    required_boards = []
429*4882a593Smuzhiyun    for board in mark.args:
430*4882a593Smuzhiyun        if board.startswith('!'):
431*4882a593Smuzhiyun            if ubconfig.board_type == board[1:]:
432*4882a593Smuzhiyun                pytest.skip('board not supported')
433*4882a593Smuzhiyun                return
434*4882a593Smuzhiyun        else:
435*4882a593Smuzhiyun            required_boards.append(board)
436*4882a593Smuzhiyun    if required_boards and ubconfig.board_type not in required_boards:
437*4882a593Smuzhiyun        pytest.skip('board not supported')
438*4882a593Smuzhiyun
439*4882a593Smuzhiyundef setup_buildconfigspec(item):
440*4882a593Smuzhiyun    """Process any 'buildconfigspec' marker for a test.
441*4882a593Smuzhiyun
442*4882a593Smuzhiyun    Such a marker lists some U-Boot configuration feature that the test
443*4882a593Smuzhiyun    requires. If tests are being executed on an U-Boot build that doesn't
444*4882a593Smuzhiyun    have the required feature, the test is marked to be skipped.
445*4882a593Smuzhiyun
446*4882a593Smuzhiyun    Args:
447*4882a593Smuzhiyun        item: The pytest test item.
448*4882a593Smuzhiyun
449*4882a593Smuzhiyun    Returns:
450*4882a593Smuzhiyun        Nothing.
451*4882a593Smuzhiyun    """
452*4882a593Smuzhiyun
453*4882a593Smuzhiyun    mark = item.get_marker('buildconfigspec')
454*4882a593Smuzhiyun    if not mark:
455*4882a593Smuzhiyun        return
456*4882a593Smuzhiyun    for option in mark.args:
457*4882a593Smuzhiyun        if not ubconfig.buildconfig.get('config_' + option.lower(), None):
458*4882a593Smuzhiyun            pytest.skip('.config feature not enabled')
459*4882a593Smuzhiyun
460*4882a593Smuzhiyundef start_test_section(item):
461*4882a593Smuzhiyun    anchors[item.name] = log.start_section(item.name)
462*4882a593Smuzhiyun
463*4882a593Smuzhiyundef pytest_runtest_setup(item):
464*4882a593Smuzhiyun    """pytest hook: Configure (set up) a test item.
465*4882a593Smuzhiyun
466*4882a593Smuzhiyun    Called once for each test to perform any custom configuration. This hook
467*4882a593Smuzhiyun    is used to skip the test if certain conditions apply.
468*4882a593Smuzhiyun
469*4882a593Smuzhiyun    Args:
470*4882a593Smuzhiyun        item: The pytest test item.
471*4882a593Smuzhiyun
472*4882a593Smuzhiyun    Returns:
473*4882a593Smuzhiyun        Nothing.
474*4882a593Smuzhiyun    """
475*4882a593Smuzhiyun
476*4882a593Smuzhiyun    start_test_section(item)
477*4882a593Smuzhiyun    setup_boardspec(item)
478*4882a593Smuzhiyun    setup_buildconfigspec(item)
479*4882a593Smuzhiyun
480*4882a593Smuzhiyundef pytest_runtest_protocol(item, nextitem):
481*4882a593Smuzhiyun    """pytest hook: Called to execute a test.
482*4882a593Smuzhiyun
483*4882a593Smuzhiyun    This hook wraps the standard pytest runtestprotocol() function in order
484*4882a593Smuzhiyun    to acquire visibility into, and record, each test function's result.
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun    Args:
487*4882a593Smuzhiyun        item: The pytest test item to execute.
488*4882a593Smuzhiyun        nextitem: The pytest test item that will be executed after this one.
489*4882a593Smuzhiyun
490*4882a593Smuzhiyun    Returns:
491*4882a593Smuzhiyun        A list of pytest reports (test result data).
492*4882a593Smuzhiyun    """
493*4882a593Smuzhiyun
494*4882a593Smuzhiyun    reports = runtestprotocol(item, nextitem=nextitem)
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun    # In pytest 3, runtestprotocol() may not call pytest_runtest_setup() if
497*4882a593Smuzhiyun    # the test is skipped. That call is required to create the test's section
498*4882a593Smuzhiyun    # in the log file. The call to log.end_section() requires that the log
499*4882a593Smuzhiyun    # contain a section for this test. Create a section for the test if it
500*4882a593Smuzhiyun    # doesn't already exist.
501*4882a593Smuzhiyun    if not item.name in anchors:
502*4882a593Smuzhiyun        start_test_section(item)
503*4882a593Smuzhiyun
504*4882a593Smuzhiyun    failure_cleanup = False
505*4882a593Smuzhiyun    test_list = tests_passed
506*4882a593Smuzhiyun    msg = 'OK'
507*4882a593Smuzhiyun    msg_log = log.status_pass
508*4882a593Smuzhiyun    for report in reports:
509*4882a593Smuzhiyun        if report.outcome == 'failed':
510*4882a593Smuzhiyun            if hasattr(report, 'wasxfail'):
511*4882a593Smuzhiyun                test_list = tests_xpassed
512*4882a593Smuzhiyun                msg = 'XPASSED'
513*4882a593Smuzhiyun                msg_log = log.status_xpass
514*4882a593Smuzhiyun            else:
515*4882a593Smuzhiyun                failure_cleanup = True
516*4882a593Smuzhiyun                test_list = tests_failed
517*4882a593Smuzhiyun                msg = 'FAILED:\n' + str(report.longrepr)
518*4882a593Smuzhiyun                msg_log = log.status_fail
519*4882a593Smuzhiyun            break
520*4882a593Smuzhiyun        if report.outcome == 'skipped':
521*4882a593Smuzhiyun            if hasattr(report, 'wasxfail'):
522*4882a593Smuzhiyun                failure_cleanup = True
523*4882a593Smuzhiyun                test_list = tests_xfailed
524*4882a593Smuzhiyun                msg = 'XFAILED:\n' + str(report.longrepr)
525*4882a593Smuzhiyun                msg_log = log.status_xfail
526*4882a593Smuzhiyun                break
527*4882a593Smuzhiyun            test_list = tests_skipped
528*4882a593Smuzhiyun            msg = 'SKIPPED:\n' + str(report.longrepr)
529*4882a593Smuzhiyun            msg_log = log.status_skipped
530*4882a593Smuzhiyun
531*4882a593Smuzhiyun    if failure_cleanup:
532*4882a593Smuzhiyun        console.drain_console()
533*4882a593Smuzhiyun
534*4882a593Smuzhiyun    test_list.append(item.name)
535*4882a593Smuzhiyun    tests_not_run.remove(item.name)
536*4882a593Smuzhiyun
537*4882a593Smuzhiyun    try:
538*4882a593Smuzhiyun        msg_log(msg)
539*4882a593Smuzhiyun    except:
540*4882a593Smuzhiyun        # If something went wrong with logging, it's better to let the test
541*4882a593Smuzhiyun        # process continue, which may report other exceptions that triggered
542*4882a593Smuzhiyun        # the logging issue (e.g. console.log wasn't created). Hence, just
543*4882a593Smuzhiyun        # squash the exception. If the test setup failed due to e.g. syntax
544*4882a593Smuzhiyun        # error somewhere else, this won't be seen. However, once that issue
545*4882a593Smuzhiyun        # is fixed, if this exception still exists, it will then be logged as
546*4882a593Smuzhiyun        # part of the test's stdout.
547*4882a593Smuzhiyun        import traceback
548*4882a593Smuzhiyun        print 'Exception occurred while logging runtest status:'
549*4882a593Smuzhiyun        traceback.print_exc()
550*4882a593Smuzhiyun        # FIXME: Can we force a test failure here?
551*4882a593Smuzhiyun
552*4882a593Smuzhiyun    log.end_section(item.name)
553*4882a593Smuzhiyun
554*4882a593Smuzhiyun    if failure_cleanup:
555*4882a593Smuzhiyun        console.cleanup_spawn()
556*4882a593Smuzhiyun
557*4882a593Smuzhiyun    return reports
558