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