119ea8026Sopenharmony_ci#!/usr/bin/env python3
219ea8026Sopenharmony_ci#
319ea8026Sopenharmony_ci# Script to compile and runs tests.
419ea8026Sopenharmony_ci#
519ea8026Sopenharmony_ci# Example:
619ea8026Sopenharmony_ci# ./scripts/test.py runners/test_runner -b
719ea8026Sopenharmony_ci#
819ea8026Sopenharmony_ci# Copyright (c) 2022, The littlefs authors.
919ea8026Sopenharmony_ci# SPDX-License-Identifier: BSD-3-Clause
1019ea8026Sopenharmony_ci#
1119ea8026Sopenharmony_ci
1219ea8026Sopenharmony_ciimport collections as co
1319ea8026Sopenharmony_ciimport csv
1419ea8026Sopenharmony_ciimport errno
1519ea8026Sopenharmony_ciimport glob
1619ea8026Sopenharmony_ciimport itertools as it
1719ea8026Sopenharmony_ciimport math as m
1819ea8026Sopenharmony_ciimport os
1919ea8026Sopenharmony_ciimport pty
2019ea8026Sopenharmony_ciimport re
2119ea8026Sopenharmony_ciimport shlex
2219ea8026Sopenharmony_ciimport shutil
2319ea8026Sopenharmony_ciimport signal
2419ea8026Sopenharmony_ciimport subprocess as sp
2519ea8026Sopenharmony_ciimport threading as th
2619ea8026Sopenharmony_ciimport time
2719ea8026Sopenharmony_ciimport toml
2819ea8026Sopenharmony_ci
2919ea8026Sopenharmony_ci
3019ea8026Sopenharmony_ciRUNNER_PATH = './runners/test_runner'
3119ea8026Sopenharmony_ciHEADER_PATH = 'runners/test_runner.h'
3219ea8026Sopenharmony_ci
3319ea8026Sopenharmony_ciGDB_PATH = ['gdb']
3419ea8026Sopenharmony_ciVALGRIND_PATH = ['valgrind']
3519ea8026Sopenharmony_ciPERF_SCRIPT = ['./scripts/perf.py']
3619ea8026Sopenharmony_ci
3719ea8026Sopenharmony_ci
3819ea8026Sopenharmony_cidef openio(path, mode='r', buffering=-1):
3919ea8026Sopenharmony_ci    # allow '-' for stdin/stdout
4019ea8026Sopenharmony_ci    if path == '-':
4119ea8026Sopenharmony_ci        if mode == 'r':
4219ea8026Sopenharmony_ci            return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
4319ea8026Sopenharmony_ci        else:
4419ea8026Sopenharmony_ci            return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
4519ea8026Sopenharmony_ci    else:
4619ea8026Sopenharmony_ci        return open(path, mode, buffering)
4719ea8026Sopenharmony_ci
4819ea8026Sopenharmony_ciclass TestCase:
4919ea8026Sopenharmony_ci    # create a TestCase object from a config
5019ea8026Sopenharmony_ci    def __init__(self, config, args={}):
5119ea8026Sopenharmony_ci        self.name = config.pop('name')
5219ea8026Sopenharmony_ci        self.path = config.pop('path')
5319ea8026Sopenharmony_ci        self.suite = config.pop('suite')
5419ea8026Sopenharmony_ci        self.lineno = config.pop('lineno', None)
5519ea8026Sopenharmony_ci        self.if_ = config.pop('if', None)
5619ea8026Sopenharmony_ci        if isinstance(self.if_, bool):
5719ea8026Sopenharmony_ci            self.if_ = 'true' if self.if_ else 'false'
5819ea8026Sopenharmony_ci        self.code = config.pop('code')
5919ea8026Sopenharmony_ci        self.code_lineno = config.pop('code_lineno', None)
6019ea8026Sopenharmony_ci        self.in_ = config.pop('in',
6119ea8026Sopenharmony_ci            config.pop('suite_in', None))
6219ea8026Sopenharmony_ci
6319ea8026Sopenharmony_ci        self.reentrant = config.pop('reentrant',
6419ea8026Sopenharmony_ci            config.pop('suite_reentrant', False))
6519ea8026Sopenharmony_ci
6619ea8026Sopenharmony_ci        # figure out defines and build possible permutations
6719ea8026Sopenharmony_ci        self.defines = set()
6819ea8026Sopenharmony_ci        self.permutations = []
6919ea8026Sopenharmony_ci
7019ea8026Sopenharmony_ci        # defines can be a dict or a list or dicts
7119ea8026Sopenharmony_ci        suite_defines = config.pop('suite_defines', {})
7219ea8026Sopenharmony_ci        if not isinstance(suite_defines, list):
7319ea8026Sopenharmony_ci            suite_defines = [suite_defines]
7419ea8026Sopenharmony_ci        defines = config.pop('defines', {})
7519ea8026Sopenharmony_ci        if not isinstance(defines, list):
7619ea8026Sopenharmony_ci            defines = [defines]
7719ea8026Sopenharmony_ci
7819ea8026Sopenharmony_ci        def csplit(v):
7919ea8026Sopenharmony_ci            # split commas but only outside of parens
8019ea8026Sopenharmony_ci            parens = 0
8119ea8026Sopenharmony_ci            i_ = 0
8219ea8026Sopenharmony_ci            for i in range(len(v)):
8319ea8026Sopenharmony_ci                if v[i] == ',' and parens == 0:
8419ea8026Sopenharmony_ci                    yield v[i_:i]
8519ea8026Sopenharmony_ci                    i_ = i+1
8619ea8026Sopenharmony_ci                elif v[i] in '([{':
8719ea8026Sopenharmony_ci                    parens += 1
8819ea8026Sopenharmony_ci                elif v[i] in '}])':
8919ea8026Sopenharmony_ci                    parens -= 1
9019ea8026Sopenharmony_ci            if v[i_:].strip():
9119ea8026Sopenharmony_ci                yield v[i_:]
9219ea8026Sopenharmony_ci
9319ea8026Sopenharmony_ci        def parse_define(v):
9419ea8026Sopenharmony_ci            # a define entry can be a list
9519ea8026Sopenharmony_ci            if isinstance(v, list):
9619ea8026Sopenharmony_ci                for v_ in v:
9719ea8026Sopenharmony_ci                    yield from parse_define(v_)
9819ea8026Sopenharmony_ci            # or a string
9919ea8026Sopenharmony_ci            elif isinstance(v, str):
10019ea8026Sopenharmony_ci                # which can be comma-separated values, with optional
10119ea8026Sopenharmony_ci                # range statements. This matches the runtime define parser in
10219ea8026Sopenharmony_ci                # the runner itself.
10319ea8026Sopenharmony_ci                for v_ in csplit(v):
10419ea8026Sopenharmony_ci                    m = re.search(r'\brange\b\s*\('
10519ea8026Sopenharmony_ci                        '(?P<start>[^,\s]*)'
10619ea8026Sopenharmony_ci                        '\s*(?:,\s*(?P<stop>[^,\s]*)'
10719ea8026Sopenharmony_ci                        '\s*(?:,\s*(?P<step>[^,\s]*)\s*)?)?\)',
10819ea8026Sopenharmony_ci                        v_)
10919ea8026Sopenharmony_ci                    if m:
11019ea8026Sopenharmony_ci                        start = (int(m.group('start'), 0)
11119ea8026Sopenharmony_ci                            if m.group('start') else 0)
11219ea8026Sopenharmony_ci                        stop = (int(m.group('stop'), 0)
11319ea8026Sopenharmony_ci                            if m.group('stop') else None)
11419ea8026Sopenharmony_ci                        step = (int(m.group('step'), 0)
11519ea8026Sopenharmony_ci                            if m.group('step') else 1)
11619ea8026Sopenharmony_ci                        if m.lastindex <= 1:
11719ea8026Sopenharmony_ci                            start, stop = 0, start
11819ea8026Sopenharmony_ci                        for x in range(start, stop, step):
11919ea8026Sopenharmony_ci                            yield from parse_define('%s(%d)%s' % (
12019ea8026Sopenharmony_ci                                v_[:m.start()], x, v_[m.end():]))
12119ea8026Sopenharmony_ci                    else:
12219ea8026Sopenharmony_ci                        yield v_
12319ea8026Sopenharmony_ci            # or a literal value
12419ea8026Sopenharmony_ci            elif isinstance(v, bool):
12519ea8026Sopenharmony_ci                yield 'true' if v else 'false'
12619ea8026Sopenharmony_ci            else:
12719ea8026Sopenharmony_ci                yield v
12819ea8026Sopenharmony_ci
12919ea8026Sopenharmony_ci        # build possible permutations
13019ea8026Sopenharmony_ci        for suite_defines_ in suite_defines:
13119ea8026Sopenharmony_ci            self.defines |= suite_defines_.keys()
13219ea8026Sopenharmony_ci            for defines_ in defines:
13319ea8026Sopenharmony_ci                self.defines |= defines_.keys()
13419ea8026Sopenharmony_ci                self.permutations.extend(dict(perm) for perm in it.product(*(
13519ea8026Sopenharmony_ci                    [(k, v) for v in parse_define(vs)]
13619ea8026Sopenharmony_ci                    for k, vs in sorted((suite_defines_ | defines_).items()))))
13719ea8026Sopenharmony_ci
13819ea8026Sopenharmony_ci        for k in config.keys():
13919ea8026Sopenharmony_ci            print('%swarning:%s in %s, found unused key %r' % (
14019ea8026Sopenharmony_ci                '\x1b[01;33m' if args['color'] else '',
14119ea8026Sopenharmony_ci                '\x1b[m' if args['color'] else '',
14219ea8026Sopenharmony_ci                self.name,
14319ea8026Sopenharmony_ci                k),
14419ea8026Sopenharmony_ci                file=sys.stderr)
14519ea8026Sopenharmony_ci
14619ea8026Sopenharmony_ci
14719ea8026Sopenharmony_ciclass TestSuite:
14819ea8026Sopenharmony_ci    # create a TestSuite object from a toml file
14919ea8026Sopenharmony_ci    def __init__(self, path, args={}):
15019ea8026Sopenharmony_ci        self.path = path
15119ea8026Sopenharmony_ci        self.name = os.path.basename(path)
15219ea8026Sopenharmony_ci        if self.name.endswith('.toml'):
15319ea8026Sopenharmony_ci            self.name = self.name[:-len('.toml')]
15419ea8026Sopenharmony_ci
15519ea8026Sopenharmony_ci        # load toml file and parse test cases
15619ea8026Sopenharmony_ci        with open(self.path) as f:
15719ea8026Sopenharmony_ci            # load tests
15819ea8026Sopenharmony_ci            config = toml.load(f)
15919ea8026Sopenharmony_ci
16019ea8026Sopenharmony_ci            # find line numbers
16119ea8026Sopenharmony_ci            f.seek(0)
16219ea8026Sopenharmony_ci            case_linenos = []
16319ea8026Sopenharmony_ci            code_linenos = []
16419ea8026Sopenharmony_ci            for i, line in enumerate(f):
16519ea8026Sopenharmony_ci                match = re.match(
16619ea8026Sopenharmony_ci                    '(?P<case>\[\s*cases\s*\.\s*(?P<name>\w+)\s*\])'
16719ea8026Sopenharmony_ci                        '|' '(?P<code>code\s*=)',
16819ea8026Sopenharmony_ci                    line)
16919ea8026Sopenharmony_ci                if match and match.group('case'):
17019ea8026Sopenharmony_ci                    case_linenos.append((i+1, match.group('name')))
17119ea8026Sopenharmony_ci                elif match and match.group('code'):
17219ea8026Sopenharmony_ci                    code_linenos.append(i+2)
17319ea8026Sopenharmony_ci
17419ea8026Sopenharmony_ci            # sort in case toml parsing did not retain order
17519ea8026Sopenharmony_ci            case_linenos.sort()
17619ea8026Sopenharmony_ci
17719ea8026Sopenharmony_ci            cases = config.pop('cases')
17819ea8026Sopenharmony_ci            for (lineno, name), (nlineno, _) in it.zip_longest(
17919ea8026Sopenharmony_ci                    case_linenos, case_linenos[1:],
18019ea8026Sopenharmony_ci                    fillvalue=(float('inf'), None)):
18119ea8026Sopenharmony_ci                code_lineno = min(
18219ea8026Sopenharmony_ci                    (l for l in code_linenos if l >= lineno and l < nlineno),
18319ea8026Sopenharmony_ci                    default=None)
18419ea8026Sopenharmony_ci                cases[name]['lineno'] = lineno
18519ea8026Sopenharmony_ci                cases[name]['code_lineno'] = code_lineno
18619ea8026Sopenharmony_ci
18719ea8026Sopenharmony_ci            self.if_ = config.pop('if', None)
18819ea8026Sopenharmony_ci            if isinstance(self.if_, bool):
18919ea8026Sopenharmony_ci                self.if_ = 'true' if self.if_ else 'false'
19019ea8026Sopenharmony_ci
19119ea8026Sopenharmony_ci            self.code = config.pop('code', None)
19219ea8026Sopenharmony_ci            self.code_lineno = min(
19319ea8026Sopenharmony_ci                (l for l in code_linenos
19419ea8026Sopenharmony_ci                    if not case_linenos or l < case_linenos[0][0]),
19519ea8026Sopenharmony_ci                default=None)
19619ea8026Sopenharmony_ci
19719ea8026Sopenharmony_ci            # a couple of these we just forward to all cases
19819ea8026Sopenharmony_ci            defines = config.pop('defines', {})
19919ea8026Sopenharmony_ci            in_ = config.pop('in', None)
20019ea8026Sopenharmony_ci            reentrant = config.pop('reentrant', False)
20119ea8026Sopenharmony_ci
20219ea8026Sopenharmony_ci            self.cases = []
20319ea8026Sopenharmony_ci            for name, case in sorted(cases.items(),
20419ea8026Sopenharmony_ci                    key=lambda c: c[1].get('lineno')):
20519ea8026Sopenharmony_ci                self.cases.append(TestCase(config={
20619ea8026Sopenharmony_ci                    'name': name,
20719ea8026Sopenharmony_ci                    'path': path + (':%d' % case['lineno']
20819ea8026Sopenharmony_ci                        if 'lineno' in case else ''),
20919ea8026Sopenharmony_ci                    'suite': self.name,
21019ea8026Sopenharmony_ci                    'suite_defines': defines,
21119ea8026Sopenharmony_ci                    'suite_in': in_,
21219ea8026Sopenharmony_ci                    'suite_reentrant': reentrant,
21319ea8026Sopenharmony_ci                    **case},
21419ea8026Sopenharmony_ci                    args=args))
21519ea8026Sopenharmony_ci
21619ea8026Sopenharmony_ci            # combine per-case defines
21719ea8026Sopenharmony_ci            self.defines = set.union(*(
21819ea8026Sopenharmony_ci                set(case.defines) for case in self.cases))
21919ea8026Sopenharmony_ci
22019ea8026Sopenharmony_ci            # combine other per-case things
22119ea8026Sopenharmony_ci            self.reentrant = any(case.reentrant for case in self.cases)
22219ea8026Sopenharmony_ci
22319ea8026Sopenharmony_ci        for k in config.keys():
22419ea8026Sopenharmony_ci            print('%swarning:%s in %s, found unused key %r' % (
22519ea8026Sopenharmony_ci                '\x1b[01;33m' if args['color'] else '',
22619ea8026Sopenharmony_ci                '\x1b[m' if args['color'] else '',
22719ea8026Sopenharmony_ci                self.name,
22819ea8026Sopenharmony_ci                k),
22919ea8026Sopenharmony_ci                file=sys.stderr)
23019ea8026Sopenharmony_ci
23119ea8026Sopenharmony_ci
23219ea8026Sopenharmony_ci
23319ea8026Sopenharmony_cidef compile(test_paths, **args):
23419ea8026Sopenharmony_ci    # find .toml files
23519ea8026Sopenharmony_ci    paths = []
23619ea8026Sopenharmony_ci    for path in test_paths:
23719ea8026Sopenharmony_ci        if os.path.isdir(path):
23819ea8026Sopenharmony_ci            path = path + '/*.toml'
23919ea8026Sopenharmony_ci
24019ea8026Sopenharmony_ci        for path in glob.glob(path):
24119ea8026Sopenharmony_ci            paths.append(path)
24219ea8026Sopenharmony_ci
24319ea8026Sopenharmony_ci    if not paths:
24419ea8026Sopenharmony_ci        print('no test suites found in %r?' % test_paths)
24519ea8026Sopenharmony_ci        sys.exit(-1)
24619ea8026Sopenharmony_ci
24719ea8026Sopenharmony_ci    # load the suites
24819ea8026Sopenharmony_ci    suites = [TestSuite(path, args) for path in paths]
24919ea8026Sopenharmony_ci    suites.sort(key=lambda s: s.name)
25019ea8026Sopenharmony_ci
25119ea8026Sopenharmony_ci    # check for name conflicts, these will cause ambiguity problems later
25219ea8026Sopenharmony_ci    # when running tests
25319ea8026Sopenharmony_ci    seen = {}
25419ea8026Sopenharmony_ci    for suite in suites:
25519ea8026Sopenharmony_ci        if suite.name in seen:
25619ea8026Sopenharmony_ci            print('%swarning:%s conflicting suite %r, %s and %s' % (
25719ea8026Sopenharmony_ci                '\x1b[01;33m' if args['color'] else '',
25819ea8026Sopenharmony_ci                '\x1b[m' if args['color'] else '',
25919ea8026Sopenharmony_ci                suite.name,
26019ea8026Sopenharmony_ci                suite.path,
26119ea8026Sopenharmony_ci                seen[suite.name].path),
26219ea8026Sopenharmony_ci                file=sys.stderr)
26319ea8026Sopenharmony_ci        seen[suite.name] = suite
26419ea8026Sopenharmony_ci
26519ea8026Sopenharmony_ci        for case in suite.cases:
26619ea8026Sopenharmony_ci            # only allow conflicts if a case and its suite share a name
26719ea8026Sopenharmony_ci            if case.name in seen and not (
26819ea8026Sopenharmony_ci                    isinstance(seen[case.name], TestSuite)
26919ea8026Sopenharmony_ci                    and seen[case.name].cases == [case]):
27019ea8026Sopenharmony_ci                print('%swarning:%s conflicting case %r, %s and %s' % (
27119ea8026Sopenharmony_ci                    '\x1b[01;33m' if args['color'] else '',
27219ea8026Sopenharmony_ci                    '\x1b[m' if args['color'] else '',
27319ea8026Sopenharmony_ci                    case.name,
27419ea8026Sopenharmony_ci                    case.path,
27519ea8026Sopenharmony_ci                    seen[case.name].path),
27619ea8026Sopenharmony_ci                    file=sys.stderr)
27719ea8026Sopenharmony_ci            seen[case.name] = case
27819ea8026Sopenharmony_ci
27919ea8026Sopenharmony_ci    # we can only compile one test suite at a time
28019ea8026Sopenharmony_ci    if not args.get('source'):
28119ea8026Sopenharmony_ci        if len(suites) > 1:
28219ea8026Sopenharmony_ci            print('more than one test suite for compilation? (%r)' % test_paths)
28319ea8026Sopenharmony_ci            sys.exit(-1)
28419ea8026Sopenharmony_ci
28519ea8026Sopenharmony_ci        suite = suites[0]
28619ea8026Sopenharmony_ci
28719ea8026Sopenharmony_ci    # write generated test source
28819ea8026Sopenharmony_ci    if 'output' in args:
28919ea8026Sopenharmony_ci        with openio(args['output'], 'w') as f:
29019ea8026Sopenharmony_ci            _write = f.write
29119ea8026Sopenharmony_ci            def write(s):
29219ea8026Sopenharmony_ci                f.lineno += s.count('\n')
29319ea8026Sopenharmony_ci                _write(s)
29419ea8026Sopenharmony_ci            def writeln(s=''):
29519ea8026Sopenharmony_ci                f.lineno += s.count('\n') + 1
29619ea8026Sopenharmony_ci                _write(s)
29719ea8026Sopenharmony_ci                _write('\n')
29819ea8026Sopenharmony_ci            f.lineno = 1
29919ea8026Sopenharmony_ci            f.write = write
30019ea8026Sopenharmony_ci            f.writeln = writeln
30119ea8026Sopenharmony_ci
30219ea8026Sopenharmony_ci            f.writeln("// Generated by %s:" % sys.argv[0])
30319ea8026Sopenharmony_ci            f.writeln("//")
30419ea8026Sopenharmony_ci            f.writeln("// %s" % ' '.join(sys.argv))
30519ea8026Sopenharmony_ci            f.writeln("//")
30619ea8026Sopenharmony_ci            f.writeln()
30719ea8026Sopenharmony_ci
30819ea8026Sopenharmony_ci            # include test_runner.h in every generated file
30919ea8026Sopenharmony_ci            f.writeln("#include \"%s\"" % args['include'])
31019ea8026Sopenharmony_ci            f.writeln()
31119ea8026Sopenharmony_ci
31219ea8026Sopenharmony_ci            # write out generated functions, this can end up in different
31319ea8026Sopenharmony_ci            # files depending on the "in" attribute
31419ea8026Sopenharmony_ci            #
31519ea8026Sopenharmony_ci            # note it's up to the specific generated file to declare
31619ea8026Sopenharmony_ci            # the test defines
31719ea8026Sopenharmony_ci            def write_case_functions(f, suite, case):
31819ea8026Sopenharmony_ci                    # create case define functions
31919ea8026Sopenharmony_ci                    if case.defines:
32019ea8026Sopenharmony_ci                        # deduplicate defines by value to try to reduce the
32119ea8026Sopenharmony_ci                        # number of functions we generate
32219ea8026Sopenharmony_ci                        define_cbs = {}
32319ea8026Sopenharmony_ci                        for i, defines in enumerate(case.permutations):
32419ea8026Sopenharmony_ci                            for k, v in sorted(defines.items()):
32519ea8026Sopenharmony_ci                                if v not in define_cbs:
32619ea8026Sopenharmony_ci                                    name = ('__test__%s__%s__%d'
32719ea8026Sopenharmony_ci                                        % (case.name, k, i))
32819ea8026Sopenharmony_ci                                    define_cbs[v] = name
32919ea8026Sopenharmony_ci                                    f.writeln('intmax_t %s('
33019ea8026Sopenharmony_ci                                        '__attribute__((unused)) '
33119ea8026Sopenharmony_ci                                        'void *data) {' % name)
33219ea8026Sopenharmony_ci                                    f.writeln(4*' '+'return %s;' % v)
33319ea8026Sopenharmony_ci                                    f.writeln('}')
33419ea8026Sopenharmony_ci                                    f.writeln()
33519ea8026Sopenharmony_ci                        f.writeln('const test_define_t '
33619ea8026Sopenharmony_ci                            '__test__%s__defines[]['
33719ea8026Sopenharmony_ci                            'TEST_IMPLICIT_DEFINE_COUNT+%d] = {'
33819ea8026Sopenharmony_ci                            % (case.name, len(suite.defines)))
33919ea8026Sopenharmony_ci                        for defines in case.permutations:
34019ea8026Sopenharmony_ci                            f.writeln(4*' '+'{')
34119ea8026Sopenharmony_ci                            for k, v in sorted(defines.items()):
34219ea8026Sopenharmony_ci                                f.writeln(8*' '+'[%-24s] = {%s, NULL},' % (
34319ea8026Sopenharmony_ci                                    k+'_i', define_cbs[v]))
34419ea8026Sopenharmony_ci                            f.writeln(4*' '+'},')
34519ea8026Sopenharmony_ci                        f.writeln('};')
34619ea8026Sopenharmony_ci                        f.writeln()
34719ea8026Sopenharmony_ci
34819ea8026Sopenharmony_ci                    # create case filter function
34919ea8026Sopenharmony_ci                    if suite.if_ is not None or case.if_ is not None:
35019ea8026Sopenharmony_ci                        f.writeln('bool __test__%s__filter(void) {'
35119ea8026Sopenharmony_ci                            % (case.name))
35219ea8026Sopenharmony_ci                        f.writeln(4*' '+'return %s;'
35319ea8026Sopenharmony_ci                            % ' && '.join('(%s)' % if_
35419ea8026Sopenharmony_ci                                for if_ in [suite.if_, case.if_]
35519ea8026Sopenharmony_ci                                if if_ is not None))
35619ea8026Sopenharmony_ci                        f.writeln('}')
35719ea8026Sopenharmony_ci                        f.writeln()
35819ea8026Sopenharmony_ci
35919ea8026Sopenharmony_ci                    # create case run function
36019ea8026Sopenharmony_ci                    f.writeln('void __test__%s__run('
36119ea8026Sopenharmony_ci                        '__attribute__((unused)) struct lfs_config *cfg) {'
36219ea8026Sopenharmony_ci                        % (case.name))
36319ea8026Sopenharmony_ci                    f.writeln(4*' '+'// test case %s' % case.name)
36419ea8026Sopenharmony_ci                    if case.code_lineno is not None:
36519ea8026Sopenharmony_ci                        f.writeln(4*' '+'#line %d "%s"'
36619ea8026Sopenharmony_ci                            % (case.code_lineno, suite.path))
36719ea8026Sopenharmony_ci                    f.write(case.code)
36819ea8026Sopenharmony_ci                    if case.code_lineno is not None:
36919ea8026Sopenharmony_ci                        f.writeln(4*' '+'#line %d "%s"'
37019ea8026Sopenharmony_ci                            % (f.lineno+1, args['output']))
37119ea8026Sopenharmony_ci                    f.writeln('}')
37219ea8026Sopenharmony_ci                    f.writeln()
37319ea8026Sopenharmony_ci
37419ea8026Sopenharmony_ci            if not args.get('source'):
37519ea8026Sopenharmony_ci                if suite.code is not None:
37619ea8026Sopenharmony_ci                    if suite.code_lineno is not None:
37719ea8026Sopenharmony_ci                        f.writeln('#line %d "%s"'
37819ea8026Sopenharmony_ci                            % (suite.code_lineno, suite.path))
37919ea8026Sopenharmony_ci                    f.write(suite.code)
38019ea8026Sopenharmony_ci                    if suite.code_lineno is not None:
38119ea8026Sopenharmony_ci                        f.writeln('#line %d "%s"'
38219ea8026Sopenharmony_ci                            % (f.lineno+1, args['output']))
38319ea8026Sopenharmony_ci                    f.writeln()
38419ea8026Sopenharmony_ci
38519ea8026Sopenharmony_ci                if suite.defines:
38619ea8026Sopenharmony_ci                    for i, define in enumerate(sorted(suite.defines)):
38719ea8026Sopenharmony_ci                        f.writeln('#ifndef %s' % define)
38819ea8026Sopenharmony_ci                        f.writeln('#define %-24s '
38919ea8026Sopenharmony_ci                            'TEST_IMPLICIT_DEFINE_COUNT+%d' % (define+'_i', i))
39019ea8026Sopenharmony_ci                        f.writeln('#define %-24s '
39119ea8026Sopenharmony_ci                            'TEST_DEFINE(%s)' % (define, define+'_i'))
39219ea8026Sopenharmony_ci                        f.writeln('#endif')
39319ea8026Sopenharmony_ci                    f.writeln()
39419ea8026Sopenharmony_ci
39519ea8026Sopenharmony_ci                # create case functions
39619ea8026Sopenharmony_ci                for case in suite.cases:
39719ea8026Sopenharmony_ci                    if case.in_ is None:
39819ea8026Sopenharmony_ci                        write_case_functions(f, suite, case)
39919ea8026Sopenharmony_ci                    else:
40019ea8026Sopenharmony_ci                        if case.defines:
40119ea8026Sopenharmony_ci                            f.writeln('extern const test_define_t '
40219ea8026Sopenharmony_ci                                '__test__%s__defines[]['
40319ea8026Sopenharmony_ci                                'TEST_IMPLICIT_DEFINE_COUNT+%d];'
40419ea8026Sopenharmony_ci                                % (case.name, len(suite.defines)))
40519ea8026Sopenharmony_ci                        if suite.if_ is not None or case.if_ is not None:
40619ea8026Sopenharmony_ci                            f.writeln('extern bool __test__%s__filter('
40719ea8026Sopenharmony_ci                                'void);'
40819ea8026Sopenharmony_ci                                % (case.name))
40919ea8026Sopenharmony_ci                        f.writeln('extern void __test__%s__run('
41019ea8026Sopenharmony_ci                            'struct lfs_config *cfg);'
41119ea8026Sopenharmony_ci                            % (case.name))
41219ea8026Sopenharmony_ci                        f.writeln()
41319ea8026Sopenharmony_ci
41419ea8026Sopenharmony_ci                # create suite struct
41519ea8026Sopenharmony_ci                #
41619ea8026Sopenharmony_ci                # note we place this in the custom test_suites section with
41719ea8026Sopenharmony_ci                # minimum alignment, otherwise GCC ups the alignment to
41819ea8026Sopenharmony_ci                # 32-bytes for some reason
41919ea8026Sopenharmony_ci                f.writeln('__attribute__((section("_test_suites"), '
42019ea8026Sopenharmony_ci                    'aligned(1)))')
42119ea8026Sopenharmony_ci                f.writeln('const struct test_suite __test__%s__suite = {'
42219ea8026Sopenharmony_ci                    % suite.name)
42319ea8026Sopenharmony_ci                f.writeln(4*' '+'.name = "%s",' % suite.name)
42419ea8026Sopenharmony_ci                f.writeln(4*' '+'.path = "%s",' % suite.path)
42519ea8026Sopenharmony_ci                f.writeln(4*' '+'.flags = %s,'
42619ea8026Sopenharmony_ci                    % (' | '.join(filter(None, [
42719ea8026Sopenharmony_ci                        'TEST_REENTRANT' if suite.reentrant else None]))
42819ea8026Sopenharmony_ci                        or 0))
42919ea8026Sopenharmony_ci                if suite.defines:
43019ea8026Sopenharmony_ci                    # create suite define names
43119ea8026Sopenharmony_ci                    f.writeln(4*' '+'.define_names = (const char *const['
43219ea8026Sopenharmony_ci                        'TEST_IMPLICIT_DEFINE_COUNT+%d]){' % (
43319ea8026Sopenharmony_ci                        len(suite.defines)))
43419ea8026Sopenharmony_ci                    for k in sorted(suite.defines):
43519ea8026Sopenharmony_ci                        f.writeln(8*' '+'[%-24s] = "%s",' % (k+'_i', k))
43619ea8026Sopenharmony_ci                    f.writeln(4*' '+'},')
43719ea8026Sopenharmony_ci                    f.writeln(4*' '+'.define_count = '
43819ea8026Sopenharmony_ci                        'TEST_IMPLICIT_DEFINE_COUNT+%d,' % len(suite.defines))
43919ea8026Sopenharmony_ci                f.writeln(4*' '+'.cases = (const struct test_case[]){')
44019ea8026Sopenharmony_ci                for case in suite.cases:
44119ea8026Sopenharmony_ci                    # create case structs
44219ea8026Sopenharmony_ci                    f.writeln(8*' '+'{')
44319ea8026Sopenharmony_ci                    f.writeln(12*' '+'.name = "%s",' % case.name)
44419ea8026Sopenharmony_ci                    f.writeln(12*' '+'.path = "%s",' % case.path)
44519ea8026Sopenharmony_ci                    f.writeln(12*' '+'.flags = %s,'
44619ea8026Sopenharmony_ci                        % (' | '.join(filter(None, [
44719ea8026Sopenharmony_ci                            'TEST_REENTRANT' if case.reentrant else None]))
44819ea8026Sopenharmony_ci                            or 0))
44919ea8026Sopenharmony_ci                    f.writeln(12*' '+'.permutations = %d,'
45019ea8026Sopenharmony_ci                        % len(case.permutations))
45119ea8026Sopenharmony_ci                    if case.defines:
45219ea8026Sopenharmony_ci                        f.writeln(12*' '+'.defines '
45319ea8026Sopenharmony_ci                            '= (const test_define_t*)__test__%s__defines,'
45419ea8026Sopenharmony_ci                            % (case.name))
45519ea8026Sopenharmony_ci                    if suite.if_ is not None or case.if_ is not None:
45619ea8026Sopenharmony_ci                        f.writeln(12*' '+'.filter = __test__%s__filter,'
45719ea8026Sopenharmony_ci                            % (case.name))
45819ea8026Sopenharmony_ci                    f.writeln(12*' '+'.run = __test__%s__run,'
45919ea8026Sopenharmony_ci                        % (case.name))
46019ea8026Sopenharmony_ci                    f.writeln(8*' '+'},')
46119ea8026Sopenharmony_ci                f.writeln(4*' '+'},')
46219ea8026Sopenharmony_ci                f.writeln(4*' '+'.case_count = %d,' % len(suite.cases))
46319ea8026Sopenharmony_ci                f.writeln('};')
46419ea8026Sopenharmony_ci                f.writeln()
46519ea8026Sopenharmony_ci
46619ea8026Sopenharmony_ci            else:
46719ea8026Sopenharmony_ci                # copy source
46819ea8026Sopenharmony_ci                f.writeln('#line 1 "%s"' % args['source'])
46919ea8026Sopenharmony_ci                with open(args['source']) as sf:
47019ea8026Sopenharmony_ci                    shutil.copyfileobj(sf, f)
47119ea8026Sopenharmony_ci                f.writeln()
47219ea8026Sopenharmony_ci
47319ea8026Sopenharmony_ci                # write any internal tests
47419ea8026Sopenharmony_ci                for suite in suites:
47519ea8026Sopenharmony_ci                    for case in suite.cases:
47619ea8026Sopenharmony_ci                        if (case.in_ is not None
47719ea8026Sopenharmony_ci                                and os.path.normpath(case.in_)
47819ea8026Sopenharmony_ci                                    == os.path.normpath(args['source'])):
47919ea8026Sopenharmony_ci                            # write defines, but note we need to undef any
48019ea8026Sopenharmony_ci                            # new defines since we're in someone else's file
48119ea8026Sopenharmony_ci                            if suite.defines:
48219ea8026Sopenharmony_ci                                for i, define in enumerate(
48319ea8026Sopenharmony_ci                                        sorted(suite.defines)):
48419ea8026Sopenharmony_ci                                    f.writeln('#ifndef %s' % define)
48519ea8026Sopenharmony_ci                                    f.writeln('#define %-24s '
48619ea8026Sopenharmony_ci                                        'TEST_IMPLICIT_DEFINE_COUNT+%d' % (
48719ea8026Sopenharmony_ci                                        define+'_i', i))
48819ea8026Sopenharmony_ci                                    f.writeln('#define %-24s '
48919ea8026Sopenharmony_ci                                        'TEST_DEFINE(%s)' % (
49019ea8026Sopenharmony_ci                                        define, define+'_i'))
49119ea8026Sopenharmony_ci                                    f.writeln('#define '
49219ea8026Sopenharmony_ci                                        '__TEST__%s__NEEDS_UNDEF' % (
49319ea8026Sopenharmony_ci                                        define))
49419ea8026Sopenharmony_ci                                    f.writeln('#endif')
49519ea8026Sopenharmony_ci                                f.writeln()
49619ea8026Sopenharmony_ci
49719ea8026Sopenharmony_ci                            write_case_functions(f, suite, case)
49819ea8026Sopenharmony_ci
49919ea8026Sopenharmony_ci                            if suite.defines:
50019ea8026Sopenharmony_ci                                for define in sorted(suite.defines):
50119ea8026Sopenharmony_ci                                    f.writeln('#ifdef __TEST__%s__NEEDS_UNDEF'
50219ea8026Sopenharmony_ci                                        % define)
50319ea8026Sopenharmony_ci                                    f.writeln('#undef __TEST__%s__NEEDS_UNDEF'
50419ea8026Sopenharmony_ci                                        % define)
50519ea8026Sopenharmony_ci                                    f.writeln('#undef %s' % define)
50619ea8026Sopenharmony_ci                                    f.writeln('#undef %s' % (define+'_i'))
50719ea8026Sopenharmony_ci                                    f.writeln('#endif')
50819ea8026Sopenharmony_ci                                f.writeln()
50919ea8026Sopenharmony_ci
51019ea8026Sopenharmony_cidef find_runner(runner, **args):
51119ea8026Sopenharmony_ci    cmd = runner.copy()
51219ea8026Sopenharmony_ci
51319ea8026Sopenharmony_ci    # run under some external command?
51419ea8026Sopenharmony_ci    if args.get('exec'):
51519ea8026Sopenharmony_ci        cmd[:0] = args['exec']
51619ea8026Sopenharmony_ci
51719ea8026Sopenharmony_ci    # run under valgrind?
51819ea8026Sopenharmony_ci    if args.get('valgrind'):
51919ea8026Sopenharmony_ci        cmd[:0] = args['valgrind_path'] + [
52019ea8026Sopenharmony_ci            '--leak-check=full',
52119ea8026Sopenharmony_ci            '--track-origins=yes',
52219ea8026Sopenharmony_ci            '--error-exitcode=4',
52319ea8026Sopenharmony_ci            '-q']
52419ea8026Sopenharmony_ci
52519ea8026Sopenharmony_ci    # run under perf?
52619ea8026Sopenharmony_ci    if args.get('perf'):
52719ea8026Sopenharmony_ci        cmd[:0] = args['perf_script'] + list(filter(None, [
52819ea8026Sopenharmony_ci            '-R',
52919ea8026Sopenharmony_ci            '--perf-freq=%s' % args['perf_freq']
53019ea8026Sopenharmony_ci                if args.get('perf_freq') else None,
53119ea8026Sopenharmony_ci            '--perf-period=%s' % args['perf_period']
53219ea8026Sopenharmony_ci                if args.get('perf_period') else None,
53319ea8026Sopenharmony_ci            '--perf-events=%s' % args['perf_events']
53419ea8026Sopenharmony_ci                if args.get('perf_events') else None,
53519ea8026Sopenharmony_ci            '--perf-path=%s' % args['perf_path']
53619ea8026Sopenharmony_ci                if args.get('perf_path') else None,
53719ea8026Sopenharmony_ci            '-o%s' % args['perf']]))
53819ea8026Sopenharmony_ci
53919ea8026Sopenharmony_ci    # other context
54019ea8026Sopenharmony_ci    if args.get('geometry'):
54119ea8026Sopenharmony_ci        cmd.append('-G%s' % args['geometry'])
54219ea8026Sopenharmony_ci    if args.get('powerloss'):
54319ea8026Sopenharmony_ci        cmd.append('-P%s' % args['powerloss'])
54419ea8026Sopenharmony_ci    if args.get('disk'):
54519ea8026Sopenharmony_ci        cmd.append('-d%s' % args['disk'])
54619ea8026Sopenharmony_ci    if args.get('trace'):
54719ea8026Sopenharmony_ci        cmd.append('-t%s' % args['trace'])
54819ea8026Sopenharmony_ci    if args.get('trace_backtrace'):
54919ea8026Sopenharmony_ci        cmd.append('--trace-backtrace')
55019ea8026Sopenharmony_ci    if args.get('trace_period'):
55119ea8026Sopenharmony_ci        cmd.append('--trace-period=%s' % args['trace_period'])
55219ea8026Sopenharmony_ci    if args.get('trace_freq'):
55319ea8026Sopenharmony_ci        cmd.append('--trace-freq=%s' % args['trace_freq'])
55419ea8026Sopenharmony_ci    if args.get('read_sleep'):
55519ea8026Sopenharmony_ci        cmd.append('--read-sleep=%s' % args['read_sleep'])
55619ea8026Sopenharmony_ci    if args.get('prog_sleep'):
55719ea8026Sopenharmony_ci        cmd.append('--prog-sleep=%s' % args['prog_sleep'])
55819ea8026Sopenharmony_ci    if args.get('erase_sleep'):
55919ea8026Sopenharmony_ci        cmd.append('--erase-sleep=%s' % args['erase_sleep'])
56019ea8026Sopenharmony_ci
56119ea8026Sopenharmony_ci    # defines?
56219ea8026Sopenharmony_ci    if args.get('define'):
56319ea8026Sopenharmony_ci        for define in args.get('define'):
56419ea8026Sopenharmony_ci            cmd.append('-D%s' % define)
56519ea8026Sopenharmony_ci
56619ea8026Sopenharmony_ci    return cmd
56719ea8026Sopenharmony_ci
56819ea8026Sopenharmony_cidef list_(runner, test_ids=[], **args):
56919ea8026Sopenharmony_ci    cmd = find_runner(runner, **args) + test_ids
57019ea8026Sopenharmony_ci    if args.get('summary'):          cmd.append('--summary')
57119ea8026Sopenharmony_ci    if args.get('list_suites'):      cmd.append('--list-suites')
57219ea8026Sopenharmony_ci    if args.get('list_cases'):       cmd.append('--list-cases')
57319ea8026Sopenharmony_ci    if args.get('list_suite_paths'): cmd.append('--list-suite-paths')
57419ea8026Sopenharmony_ci    if args.get('list_case_paths'):  cmd.append('--list-case-paths')
57519ea8026Sopenharmony_ci    if args.get('list_defines'):     cmd.append('--list-defines')
57619ea8026Sopenharmony_ci    if args.get('list_permutation_defines'):
57719ea8026Sopenharmony_ci                                     cmd.append('--list-permutation-defines')
57819ea8026Sopenharmony_ci    if args.get('list_implicit_defines'):
57919ea8026Sopenharmony_ci                                     cmd.append('--list-implicit-defines')
58019ea8026Sopenharmony_ci    if args.get('list_geometries'):  cmd.append('--list-geometries')
58119ea8026Sopenharmony_ci    if args.get('list_powerlosses'): cmd.append('--list-powerlosses')
58219ea8026Sopenharmony_ci
58319ea8026Sopenharmony_ci    if args.get('verbose'):
58419ea8026Sopenharmony_ci        print(' '.join(shlex.quote(c) for c in cmd))
58519ea8026Sopenharmony_ci    return sp.call(cmd)
58619ea8026Sopenharmony_ci
58719ea8026Sopenharmony_ci
58819ea8026Sopenharmony_cidef find_perms(runner_, ids=[], **args):
58919ea8026Sopenharmony_ci    case_suites = {}
59019ea8026Sopenharmony_ci    expected_case_perms = co.defaultdict(lambda: 0)
59119ea8026Sopenharmony_ci    expected_perms = 0
59219ea8026Sopenharmony_ci    total_perms = 0
59319ea8026Sopenharmony_ci
59419ea8026Sopenharmony_ci    # query cases from the runner
59519ea8026Sopenharmony_ci    cmd = runner_ + ['--list-cases'] + ids
59619ea8026Sopenharmony_ci    if args.get('verbose'):
59719ea8026Sopenharmony_ci        print(' '.join(shlex.quote(c) for c in cmd))
59819ea8026Sopenharmony_ci    proc = sp.Popen(cmd,
59919ea8026Sopenharmony_ci        stdout=sp.PIPE,
60019ea8026Sopenharmony_ci        stderr=sp.PIPE if not args.get('verbose') else None,
60119ea8026Sopenharmony_ci        universal_newlines=True,
60219ea8026Sopenharmony_ci        errors='replace',
60319ea8026Sopenharmony_ci        close_fds=False)
60419ea8026Sopenharmony_ci    pattern = re.compile(
60519ea8026Sopenharmony_ci        '^(?P<case>[^\s]+)'
60619ea8026Sopenharmony_ci            '\s+(?P<flags>[^\s]+)'
60719ea8026Sopenharmony_ci            '\s+(?P<filtered>\d+)/(?P<perms>\d+)')
60819ea8026Sopenharmony_ci    # skip the first line
60919ea8026Sopenharmony_ci    for line in it.islice(proc.stdout, 1, None):
61019ea8026Sopenharmony_ci        m = pattern.match(line)
61119ea8026Sopenharmony_ci        if m:
61219ea8026Sopenharmony_ci            filtered = int(m.group('filtered'))
61319ea8026Sopenharmony_ci            perms = int(m.group('perms'))
61419ea8026Sopenharmony_ci            expected_case_perms[m.group('case')] += filtered
61519ea8026Sopenharmony_ci            expected_perms += filtered
61619ea8026Sopenharmony_ci            total_perms += perms
61719ea8026Sopenharmony_ci    proc.wait()
61819ea8026Sopenharmony_ci    if proc.returncode != 0:
61919ea8026Sopenharmony_ci        if not args.get('verbose'):
62019ea8026Sopenharmony_ci            for line in proc.stderr:
62119ea8026Sopenharmony_ci                sys.stdout.write(line)
62219ea8026Sopenharmony_ci        sys.exit(-1)
62319ea8026Sopenharmony_ci
62419ea8026Sopenharmony_ci    # get which suite each case belongs to via paths
62519ea8026Sopenharmony_ci    cmd = runner_ + ['--list-case-paths'] + ids
62619ea8026Sopenharmony_ci    if args.get('verbose'):
62719ea8026Sopenharmony_ci        print(' '.join(shlex.quote(c) for c in cmd))
62819ea8026Sopenharmony_ci    proc = sp.Popen(cmd,
62919ea8026Sopenharmony_ci        stdout=sp.PIPE,
63019ea8026Sopenharmony_ci        stderr=sp.PIPE if not args.get('verbose') else None,
63119ea8026Sopenharmony_ci        universal_newlines=True,
63219ea8026Sopenharmony_ci        errors='replace',
63319ea8026Sopenharmony_ci        close_fds=False)
63419ea8026Sopenharmony_ci    pattern = re.compile(
63519ea8026Sopenharmony_ci        '^(?P<case>[^\s]+)'
63619ea8026Sopenharmony_ci            '\s+(?P<path>[^:]+):(?P<lineno>\d+)')
63719ea8026Sopenharmony_ci    # skip the first line
63819ea8026Sopenharmony_ci    for line in it.islice(proc.stdout, 1, None):
63919ea8026Sopenharmony_ci        m = pattern.match(line)
64019ea8026Sopenharmony_ci        if m:
64119ea8026Sopenharmony_ci            path = m.group('path')
64219ea8026Sopenharmony_ci            # strip path/suffix here
64319ea8026Sopenharmony_ci            suite = os.path.basename(path)
64419ea8026Sopenharmony_ci            if suite.endswith('.toml'):
64519ea8026Sopenharmony_ci                suite = suite[:-len('.toml')]
64619ea8026Sopenharmony_ci            case_suites[m.group('case')] = suite
64719ea8026Sopenharmony_ci    proc.wait()
64819ea8026Sopenharmony_ci    if proc.returncode != 0:
64919ea8026Sopenharmony_ci        if not args.get('verbose'):
65019ea8026Sopenharmony_ci            for line in proc.stderr:
65119ea8026Sopenharmony_ci                sys.stdout.write(line)
65219ea8026Sopenharmony_ci        sys.exit(-1)
65319ea8026Sopenharmony_ci
65419ea8026Sopenharmony_ci    # figure out expected suite perms
65519ea8026Sopenharmony_ci    expected_suite_perms = co.defaultdict(lambda: 0)
65619ea8026Sopenharmony_ci    for case, suite in case_suites.items():
65719ea8026Sopenharmony_ci        expected_suite_perms[suite] += expected_case_perms[case]
65819ea8026Sopenharmony_ci
65919ea8026Sopenharmony_ci    return (
66019ea8026Sopenharmony_ci        case_suites,
66119ea8026Sopenharmony_ci        expected_suite_perms,
66219ea8026Sopenharmony_ci        expected_case_perms,
66319ea8026Sopenharmony_ci        expected_perms,
66419ea8026Sopenharmony_ci        total_perms)
66519ea8026Sopenharmony_ci
66619ea8026Sopenharmony_cidef find_path(runner_, id, **args):
66719ea8026Sopenharmony_ci    path = None
66819ea8026Sopenharmony_ci    # query from runner
66919ea8026Sopenharmony_ci    cmd = runner_ + ['--list-case-paths', id]
67019ea8026Sopenharmony_ci    if args.get('verbose'):
67119ea8026Sopenharmony_ci        print(' '.join(shlex.quote(c) for c in cmd))
67219ea8026Sopenharmony_ci    proc = sp.Popen(cmd,
67319ea8026Sopenharmony_ci        stdout=sp.PIPE,
67419ea8026Sopenharmony_ci        stderr=sp.PIPE if not args.get('verbose') else None,
67519ea8026Sopenharmony_ci        universal_newlines=True,
67619ea8026Sopenharmony_ci        errors='replace',
67719ea8026Sopenharmony_ci        close_fds=False)
67819ea8026Sopenharmony_ci    pattern = re.compile(
67919ea8026Sopenharmony_ci        '^(?P<case>[^\s]+)'
68019ea8026Sopenharmony_ci            '\s+(?P<path>[^:]+):(?P<lineno>\d+)')
68119ea8026Sopenharmony_ci    # skip the first line
68219ea8026Sopenharmony_ci    for line in it.islice(proc.stdout, 1, None):
68319ea8026Sopenharmony_ci        m = pattern.match(line)
68419ea8026Sopenharmony_ci        if m and path is None:
68519ea8026Sopenharmony_ci            path_ = m.group('path')
68619ea8026Sopenharmony_ci            lineno = int(m.group('lineno'))
68719ea8026Sopenharmony_ci            path = (path_, lineno)
68819ea8026Sopenharmony_ci    proc.wait()
68919ea8026Sopenharmony_ci    if proc.returncode != 0:
69019ea8026Sopenharmony_ci        if not args.get('verbose'):
69119ea8026Sopenharmony_ci            for line in proc.stderr:
69219ea8026Sopenharmony_ci                sys.stdout.write(line)
69319ea8026Sopenharmony_ci        sys.exit(-1)
69419ea8026Sopenharmony_ci
69519ea8026Sopenharmony_ci    return path
69619ea8026Sopenharmony_ci
69719ea8026Sopenharmony_cidef find_defines(runner_, id, **args):
69819ea8026Sopenharmony_ci    # query permutation defines from runner
69919ea8026Sopenharmony_ci    cmd = runner_ + ['--list-permutation-defines', id]
70019ea8026Sopenharmony_ci    if args.get('verbose'):
70119ea8026Sopenharmony_ci        print(' '.join(shlex.quote(c) for c in cmd))
70219ea8026Sopenharmony_ci    proc = sp.Popen(cmd,
70319ea8026Sopenharmony_ci        stdout=sp.PIPE,
70419ea8026Sopenharmony_ci        stderr=sp.PIPE if not args.get('verbose') else None,
70519ea8026Sopenharmony_ci        universal_newlines=True,
70619ea8026Sopenharmony_ci        errors='replace',
70719ea8026Sopenharmony_ci        close_fds=False)
70819ea8026Sopenharmony_ci    defines = co.OrderedDict()
70919ea8026Sopenharmony_ci    pattern = re.compile('^(?P<define>\w+)=(?P<value>.+)')
71019ea8026Sopenharmony_ci    for line in proc.stdout:
71119ea8026Sopenharmony_ci        m = pattern.match(line)
71219ea8026Sopenharmony_ci        if m:
71319ea8026Sopenharmony_ci            define = m.group('define')
71419ea8026Sopenharmony_ci            value = m.group('value')
71519ea8026Sopenharmony_ci            defines[define] = value
71619ea8026Sopenharmony_ci    proc.wait()
71719ea8026Sopenharmony_ci    if proc.returncode != 0:
71819ea8026Sopenharmony_ci        if not args.get('verbose'):
71919ea8026Sopenharmony_ci            for line in proc.stderr:
72019ea8026Sopenharmony_ci                sys.stdout.write(line)
72119ea8026Sopenharmony_ci        sys.exit(-1)
72219ea8026Sopenharmony_ci
72319ea8026Sopenharmony_ci    return defines
72419ea8026Sopenharmony_ci
72519ea8026Sopenharmony_ci
72619ea8026Sopenharmony_ci# Thread-safe CSV writer
72719ea8026Sopenharmony_ciclass TestOutput:
72819ea8026Sopenharmony_ci    def __init__(self, path, head=None, tail=None):
72919ea8026Sopenharmony_ci        self.f = openio(path, 'w+', 1)
73019ea8026Sopenharmony_ci        self.lock = th.Lock()
73119ea8026Sopenharmony_ci        self.head = head or []
73219ea8026Sopenharmony_ci        self.tail = tail or []
73319ea8026Sopenharmony_ci        self.writer = csv.DictWriter(self.f, self.head + self.tail)
73419ea8026Sopenharmony_ci        self.rows = []
73519ea8026Sopenharmony_ci
73619ea8026Sopenharmony_ci    def close(self):
73719ea8026Sopenharmony_ci        self.f.close()
73819ea8026Sopenharmony_ci
73919ea8026Sopenharmony_ci    def __enter__(self):
74019ea8026Sopenharmony_ci        return self
74119ea8026Sopenharmony_ci
74219ea8026Sopenharmony_ci    def __exit__(self, *_):
74319ea8026Sopenharmony_ci        self.f.close()
74419ea8026Sopenharmony_ci
74519ea8026Sopenharmony_ci    def writerow(self, row):
74619ea8026Sopenharmony_ci        with self.lock:
74719ea8026Sopenharmony_ci            self.rows.append(row)
74819ea8026Sopenharmony_ci            if all(k in self.head or k in self.tail for k in row.keys()):
74919ea8026Sopenharmony_ci                # can simply append
75019ea8026Sopenharmony_ci                self.writer.writerow(row)
75119ea8026Sopenharmony_ci            else:
75219ea8026Sopenharmony_ci                # need to rewrite the file
75319ea8026Sopenharmony_ci                self.head.extend(row.keys() - (self.head + self.tail))
75419ea8026Sopenharmony_ci                self.f.seek(0)
75519ea8026Sopenharmony_ci                self.f.truncate()
75619ea8026Sopenharmony_ci                self.writer = csv.DictWriter(self.f, self.head + self.tail)
75719ea8026Sopenharmony_ci                self.writer.writeheader()
75819ea8026Sopenharmony_ci                for row in self.rows:
75919ea8026Sopenharmony_ci                    self.writer.writerow(row)
76019ea8026Sopenharmony_ci
76119ea8026Sopenharmony_ci# A test failure
76219ea8026Sopenharmony_ciclass TestFailure(Exception):
76319ea8026Sopenharmony_ci    def __init__(self, id, returncode, stdout, assert_=None):
76419ea8026Sopenharmony_ci        self.id = id
76519ea8026Sopenharmony_ci        self.returncode = returncode
76619ea8026Sopenharmony_ci        self.stdout = stdout
76719ea8026Sopenharmony_ci        self.assert_ = assert_
76819ea8026Sopenharmony_ci
76919ea8026Sopenharmony_cidef run_stage(name, runner_, ids, stdout_, trace_, output_, **args):
77019ea8026Sopenharmony_ci    # get expected suite/case/perm counts
77119ea8026Sopenharmony_ci    (case_suites,
77219ea8026Sopenharmony_ci        expected_suite_perms,
77319ea8026Sopenharmony_ci        expected_case_perms,
77419ea8026Sopenharmony_ci        expected_perms,
77519ea8026Sopenharmony_ci        total_perms) = find_perms(runner_, ids, **args)
77619ea8026Sopenharmony_ci
77719ea8026Sopenharmony_ci    passed_suite_perms = co.defaultdict(lambda: 0)
77819ea8026Sopenharmony_ci    passed_case_perms = co.defaultdict(lambda: 0)
77919ea8026Sopenharmony_ci    passed_perms = 0
78019ea8026Sopenharmony_ci    powerlosses = 0
78119ea8026Sopenharmony_ci    failures = []
78219ea8026Sopenharmony_ci    killed = False
78319ea8026Sopenharmony_ci
78419ea8026Sopenharmony_ci    pattern = re.compile('^(?:'
78519ea8026Sopenharmony_ci            '(?P<op>running|finished|skipped|powerloss) '
78619ea8026Sopenharmony_ci                '(?P<id>(?P<case>[^:]+)[^\s]*)'
78719ea8026Sopenharmony_ci            '|' '(?P<path>[^:]+):(?P<lineno>\d+):(?P<op_>assert):'
78819ea8026Sopenharmony_ci                ' *(?P<message>.*)'
78919ea8026Sopenharmony_ci        ')$')
79019ea8026Sopenharmony_ci    locals = th.local()
79119ea8026Sopenharmony_ci    children = set()
79219ea8026Sopenharmony_ci
79319ea8026Sopenharmony_ci    def run_runner(runner_, ids=[]):
79419ea8026Sopenharmony_ci        nonlocal passed_suite_perms
79519ea8026Sopenharmony_ci        nonlocal passed_case_perms
79619ea8026Sopenharmony_ci        nonlocal passed_perms
79719ea8026Sopenharmony_ci        nonlocal powerlosses
79819ea8026Sopenharmony_ci        nonlocal locals
79919ea8026Sopenharmony_ci
80019ea8026Sopenharmony_ci        # run the tests!
80119ea8026Sopenharmony_ci        cmd = runner_ + ids
80219ea8026Sopenharmony_ci        if args.get('verbose'):
80319ea8026Sopenharmony_ci            print(' '.join(shlex.quote(c) for c in cmd))
80419ea8026Sopenharmony_ci
80519ea8026Sopenharmony_ci        mpty, spty = pty.openpty()
80619ea8026Sopenharmony_ci        proc = sp.Popen(cmd, stdout=spty, stderr=spty, close_fds=False)
80719ea8026Sopenharmony_ci        os.close(spty)
80819ea8026Sopenharmony_ci        children.add(proc)
80919ea8026Sopenharmony_ci        mpty = os.fdopen(mpty, 'r', 1)
81019ea8026Sopenharmony_ci
81119ea8026Sopenharmony_ci        last_id = None
81219ea8026Sopenharmony_ci        last_stdout = co.deque(maxlen=args.get('context', 5) + 1)
81319ea8026Sopenharmony_ci        last_assert = None
81419ea8026Sopenharmony_ci        try:
81519ea8026Sopenharmony_ci            while True:
81619ea8026Sopenharmony_ci                # parse a line for state changes
81719ea8026Sopenharmony_ci                try:
81819ea8026Sopenharmony_ci                    line = mpty.readline()
81919ea8026Sopenharmony_ci                except OSError as e:
82019ea8026Sopenharmony_ci                    if e.errno != errno.EIO:
82119ea8026Sopenharmony_ci                        raise
82219ea8026Sopenharmony_ci                    break
82319ea8026Sopenharmony_ci                if not line:
82419ea8026Sopenharmony_ci                    break
82519ea8026Sopenharmony_ci                last_stdout.append(line)
82619ea8026Sopenharmony_ci                if stdout_:
82719ea8026Sopenharmony_ci                    try:
82819ea8026Sopenharmony_ci                        stdout_.write(line)
82919ea8026Sopenharmony_ci                        stdout_.flush()
83019ea8026Sopenharmony_ci                    except BrokenPipeError:
83119ea8026Sopenharmony_ci                        pass
83219ea8026Sopenharmony_ci
83319ea8026Sopenharmony_ci                m = pattern.match(line)
83419ea8026Sopenharmony_ci                if m:
83519ea8026Sopenharmony_ci                    op = m.group('op') or m.group('op_')
83619ea8026Sopenharmony_ci                    if op == 'running':
83719ea8026Sopenharmony_ci                        locals.seen_perms += 1
83819ea8026Sopenharmony_ci                        last_id = m.group('id')
83919ea8026Sopenharmony_ci                        last_stdout.clear()
84019ea8026Sopenharmony_ci                        last_assert = None
84119ea8026Sopenharmony_ci                    elif op == 'powerloss':
84219ea8026Sopenharmony_ci                        last_id = m.group('id')
84319ea8026Sopenharmony_ci                        powerlosses += 1
84419ea8026Sopenharmony_ci                    elif op == 'finished':
84519ea8026Sopenharmony_ci                        case = m.group('case')
84619ea8026Sopenharmony_ci                        suite = case_suites[case]
84719ea8026Sopenharmony_ci                        passed_suite_perms[suite] += 1
84819ea8026Sopenharmony_ci                        passed_case_perms[case] += 1
84919ea8026Sopenharmony_ci                        passed_perms += 1
85019ea8026Sopenharmony_ci                        if output_:
85119ea8026Sopenharmony_ci                            # get defines and write to csv
85219ea8026Sopenharmony_ci                            defines = find_defines(
85319ea8026Sopenharmony_ci                                runner_, m.group('id'), **args)
85419ea8026Sopenharmony_ci                            output_.writerow({
85519ea8026Sopenharmony_ci                                'suite': suite,
85619ea8026Sopenharmony_ci                                'case': case,
85719ea8026Sopenharmony_ci                                'test_passed': '1/1',
85819ea8026Sopenharmony_ci                                **defines})
85919ea8026Sopenharmony_ci                    elif op == 'skipped':
86019ea8026Sopenharmony_ci                        locals.seen_perms += 1
86119ea8026Sopenharmony_ci                    elif op == 'assert':
86219ea8026Sopenharmony_ci                        last_assert = (
86319ea8026Sopenharmony_ci                            m.group('path'),
86419ea8026Sopenharmony_ci                            int(m.group('lineno')),
86519ea8026Sopenharmony_ci                            m.group('message'))
86619ea8026Sopenharmony_ci                        # go ahead and kill the process, aborting takes a while
86719ea8026Sopenharmony_ci                        if args.get('keep_going'):
86819ea8026Sopenharmony_ci                            proc.kill()
86919ea8026Sopenharmony_ci        except KeyboardInterrupt:
87019ea8026Sopenharmony_ci            raise TestFailure(last_id, 1, list(last_stdout))
87119ea8026Sopenharmony_ci        finally:
87219ea8026Sopenharmony_ci            children.remove(proc)
87319ea8026Sopenharmony_ci            mpty.close()
87419ea8026Sopenharmony_ci
87519ea8026Sopenharmony_ci        proc.wait()
87619ea8026Sopenharmony_ci        if proc.returncode != 0:
87719ea8026Sopenharmony_ci            raise TestFailure(
87819ea8026Sopenharmony_ci                last_id,
87919ea8026Sopenharmony_ci                proc.returncode,
88019ea8026Sopenharmony_ci                list(last_stdout),
88119ea8026Sopenharmony_ci                last_assert)
88219ea8026Sopenharmony_ci
88319ea8026Sopenharmony_ci    def run_job(runner_, ids=[], start=None, step=None):
88419ea8026Sopenharmony_ci        nonlocal failures
88519ea8026Sopenharmony_ci        nonlocal killed
88619ea8026Sopenharmony_ci        nonlocal locals
88719ea8026Sopenharmony_ci
88819ea8026Sopenharmony_ci        start = start or 0
88919ea8026Sopenharmony_ci        step = step or 1
89019ea8026Sopenharmony_ci        while start < total_perms:
89119ea8026Sopenharmony_ci            job_runner = runner_.copy()
89219ea8026Sopenharmony_ci            if args.get('isolate') or args.get('valgrind'):
89319ea8026Sopenharmony_ci                job_runner.append('-s%s,%s,%s' % (start, start+step, step))
89419ea8026Sopenharmony_ci            else:
89519ea8026Sopenharmony_ci                job_runner.append('-s%s,,%s' % (start, step))
89619ea8026Sopenharmony_ci
89719ea8026Sopenharmony_ci            try:
89819ea8026Sopenharmony_ci                # run the tests
89919ea8026Sopenharmony_ci                locals.seen_perms = 0
90019ea8026Sopenharmony_ci                run_runner(job_runner, ids)
90119ea8026Sopenharmony_ci                assert locals.seen_perms > 0
90219ea8026Sopenharmony_ci                start += locals.seen_perms*step
90319ea8026Sopenharmony_ci
90419ea8026Sopenharmony_ci            except TestFailure as failure:
90519ea8026Sopenharmony_ci                # keep track of failures
90619ea8026Sopenharmony_ci                if output_:
90719ea8026Sopenharmony_ci                    case, _ = failure.id.split(':', 1)
90819ea8026Sopenharmony_ci                    suite = case_suites[case]
90919ea8026Sopenharmony_ci                    # get defines and write to csv
91019ea8026Sopenharmony_ci                    defines = find_defines(runner_, failure.id, **args)
91119ea8026Sopenharmony_ci                    output_.writerow({
91219ea8026Sopenharmony_ci                        'suite': suite,
91319ea8026Sopenharmony_ci                        'case': case,
91419ea8026Sopenharmony_ci                        'test_passed': '0/1',
91519ea8026Sopenharmony_ci                        **defines})
91619ea8026Sopenharmony_ci
91719ea8026Sopenharmony_ci                # race condition for multiple failures?
91819ea8026Sopenharmony_ci                if failures and not args.get('keep_going'):
91919ea8026Sopenharmony_ci                    break
92019ea8026Sopenharmony_ci
92119ea8026Sopenharmony_ci                failures.append(failure)
92219ea8026Sopenharmony_ci
92319ea8026Sopenharmony_ci                if args.get('keep_going') and not killed:
92419ea8026Sopenharmony_ci                    # resume after failed test
92519ea8026Sopenharmony_ci                    assert locals.seen_perms > 0
92619ea8026Sopenharmony_ci                    start += locals.seen_perms*step
92719ea8026Sopenharmony_ci                    continue
92819ea8026Sopenharmony_ci                else:
92919ea8026Sopenharmony_ci                    # stop other tests
93019ea8026Sopenharmony_ci                    killed = True
93119ea8026Sopenharmony_ci                    for child in children.copy():
93219ea8026Sopenharmony_ci                        child.kill()
93319ea8026Sopenharmony_ci                    break
93419ea8026Sopenharmony_ci
93519ea8026Sopenharmony_ci
93619ea8026Sopenharmony_ci    # parallel jobs?
93719ea8026Sopenharmony_ci    runners = []
93819ea8026Sopenharmony_ci    if 'jobs' in args:
93919ea8026Sopenharmony_ci        for job in range(args['jobs']):
94019ea8026Sopenharmony_ci            runners.append(th.Thread(
94119ea8026Sopenharmony_ci                target=run_job, args=(runner_, ids, job, args['jobs']),
94219ea8026Sopenharmony_ci                daemon=True))
94319ea8026Sopenharmony_ci    else:
94419ea8026Sopenharmony_ci        runners.append(th.Thread(
94519ea8026Sopenharmony_ci            target=run_job, args=(runner_, ids, None, None),
94619ea8026Sopenharmony_ci            daemon=True))
94719ea8026Sopenharmony_ci
94819ea8026Sopenharmony_ci    def print_update(done):
94919ea8026Sopenharmony_ci        if not args.get('verbose') and (args['color'] or done):
95019ea8026Sopenharmony_ci            sys.stdout.write('%s%srunning %s%s:%s %s%s' % (
95119ea8026Sopenharmony_ci                '\r\x1b[K' if args['color'] else '',
95219ea8026Sopenharmony_ci                '\x1b[?7l' if not done else '',
95319ea8026Sopenharmony_ci                ('\x1b[32m' if not failures else '\x1b[31m')
95419ea8026Sopenharmony_ci                    if args['color'] else '',
95519ea8026Sopenharmony_ci                name,
95619ea8026Sopenharmony_ci                '\x1b[m' if args['color'] else '',
95719ea8026Sopenharmony_ci                ', '.join(filter(None, [
95819ea8026Sopenharmony_ci                    '%d/%d suites' % (
95919ea8026Sopenharmony_ci                        sum(passed_suite_perms[k] == v
96019ea8026Sopenharmony_ci                            for k, v in expected_suite_perms.items()),
96119ea8026Sopenharmony_ci                        len(expected_suite_perms))
96219ea8026Sopenharmony_ci                        if (not args.get('by_suites')
96319ea8026Sopenharmony_ci                            and not args.get('by_cases')) else None,
96419ea8026Sopenharmony_ci                    '%d/%d cases' % (
96519ea8026Sopenharmony_ci                        sum(passed_case_perms[k] == v
96619ea8026Sopenharmony_ci                            for k, v in expected_case_perms.items()),
96719ea8026Sopenharmony_ci                        len(expected_case_perms))
96819ea8026Sopenharmony_ci                        if not args.get('by_cases') else None,
96919ea8026Sopenharmony_ci                    '%d/%d perms' % (passed_perms, expected_perms),
97019ea8026Sopenharmony_ci                    '%dpls!' % powerlosses
97119ea8026Sopenharmony_ci                        if powerlosses else None,
97219ea8026Sopenharmony_ci                    '%s%d/%d failures%s' % (
97319ea8026Sopenharmony_ci                            '\x1b[31m' if args['color'] else '',
97419ea8026Sopenharmony_ci                            len(failures),
97519ea8026Sopenharmony_ci                            expected_perms,
97619ea8026Sopenharmony_ci                            '\x1b[m' if args['color'] else '')
97719ea8026Sopenharmony_ci                        if failures else None])),
97819ea8026Sopenharmony_ci                '\x1b[?7h' if not done else '\n'))
97919ea8026Sopenharmony_ci            sys.stdout.flush()
98019ea8026Sopenharmony_ci
98119ea8026Sopenharmony_ci    for r in runners:
98219ea8026Sopenharmony_ci        r.start()
98319ea8026Sopenharmony_ci
98419ea8026Sopenharmony_ci    try:
98519ea8026Sopenharmony_ci        while any(r.is_alive() for r in runners):
98619ea8026Sopenharmony_ci            time.sleep(0.01)
98719ea8026Sopenharmony_ci            print_update(False)
98819ea8026Sopenharmony_ci    except KeyboardInterrupt:
98919ea8026Sopenharmony_ci        # this is handled by the runner threads, we just
99019ea8026Sopenharmony_ci        # need to not abort here
99119ea8026Sopenharmony_ci        killed = True
99219ea8026Sopenharmony_ci    finally:
99319ea8026Sopenharmony_ci        print_update(True)
99419ea8026Sopenharmony_ci
99519ea8026Sopenharmony_ci    for r in runners:
99619ea8026Sopenharmony_ci        r.join()
99719ea8026Sopenharmony_ci
99819ea8026Sopenharmony_ci    return (
99919ea8026Sopenharmony_ci        expected_perms,
100019ea8026Sopenharmony_ci        passed_perms,
100119ea8026Sopenharmony_ci        powerlosses,
100219ea8026Sopenharmony_ci        failures,
100319ea8026Sopenharmony_ci        killed)
100419ea8026Sopenharmony_ci
100519ea8026Sopenharmony_ci
100619ea8026Sopenharmony_cidef run(runner, test_ids=[], **args):
100719ea8026Sopenharmony_ci    # query runner for tests
100819ea8026Sopenharmony_ci    runner_ = find_runner(runner, **args)
100919ea8026Sopenharmony_ci    print('using runner: %s' % ' '.join(shlex.quote(c) for c in runner_))
101019ea8026Sopenharmony_ci    (_,
101119ea8026Sopenharmony_ci        expected_suite_perms,
101219ea8026Sopenharmony_ci        expected_case_perms,
101319ea8026Sopenharmony_ci        expected_perms,
101419ea8026Sopenharmony_ci        total_perms) = find_perms(runner_, test_ids, **args)
101519ea8026Sopenharmony_ci    print('found %d suites, %d cases, %d/%d permutations' % (
101619ea8026Sopenharmony_ci        len(expected_suite_perms),
101719ea8026Sopenharmony_ci        len(expected_case_perms),
101819ea8026Sopenharmony_ci        expected_perms,
101919ea8026Sopenharmony_ci        total_perms))
102019ea8026Sopenharmony_ci    print()
102119ea8026Sopenharmony_ci
102219ea8026Sopenharmony_ci    # automatic job detection?
102319ea8026Sopenharmony_ci    if args.get('jobs') == 0:
102419ea8026Sopenharmony_ci        args['jobs'] = len(os.sched_getaffinity(0))
102519ea8026Sopenharmony_ci
102619ea8026Sopenharmony_ci    # truncate and open logs here so they aren't disconnected between tests
102719ea8026Sopenharmony_ci    stdout = None
102819ea8026Sopenharmony_ci    if args.get('stdout'):
102919ea8026Sopenharmony_ci        stdout = openio(args['stdout'], 'w', 1)
103019ea8026Sopenharmony_ci    trace = None
103119ea8026Sopenharmony_ci    if args.get('trace'):
103219ea8026Sopenharmony_ci        trace = openio(args['trace'], 'w', 1)
103319ea8026Sopenharmony_ci    output = None
103419ea8026Sopenharmony_ci    if args.get('output'):
103519ea8026Sopenharmony_ci        output = TestOutput(args['output'],
103619ea8026Sopenharmony_ci            ['suite', 'case'],
103719ea8026Sopenharmony_ci            ['test_passed'])
103819ea8026Sopenharmony_ci
103919ea8026Sopenharmony_ci    # measure runtime
104019ea8026Sopenharmony_ci    start = time.time()
104119ea8026Sopenharmony_ci
104219ea8026Sopenharmony_ci    # spawn runners
104319ea8026Sopenharmony_ci    expected = 0
104419ea8026Sopenharmony_ci    passed = 0
104519ea8026Sopenharmony_ci    powerlosses = 0
104619ea8026Sopenharmony_ci    failures = []
104719ea8026Sopenharmony_ci    for by in (test_ids if test_ids
104819ea8026Sopenharmony_ci            else expected_case_perms.keys() if args.get('by_cases')
104919ea8026Sopenharmony_ci            else expected_suite_perms.keys() if args.get('by_suites')
105019ea8026Sopenharmony_ci            else [None]):
105119ea8026Sopenharmony_ci        # spawn jobs for stage
105219ea8026Sopenharmony_ci        (expected_,
105319ea8026Sopenharmony_ci            passed_,
105419ea8026Sopenharmony_ci            powerlosses_,
105519ea8026Sopenharmony_ci            failures_,
105619ea8026Sopenharmony_ci            killed) = run_stage(
105719ea8026Sopenharmony_ci                by or 'tests',
105819ea8026Sopenharmony_ci                runner_,
105919ea8026Sopenharmony_ci                [by] if by is not None else [],
106019ea8026Sopenharmony_ci                stdout,
106119ea8026Sopenharmony_ci                trace,
106219ea8026Sopenharmony_ci                output,
106319ea8026Sopenharmony_ci                **args)
106419ea8026Sopenharmony_ci        # collect passes/failures
106519ea8026Sopenharmony_ci        expected += expected_
106619ea8026Sopenharmony_ci        passed += passed_
106719ea8026Sopenharmony_ci        powerlosses += powerlosses_
106819ea8026Sopenharmony_ci        failures.extend(failures_)
106919ea8026Sopenharmony_ci        if (failures and not args.get('keep_going')) or killed:
107019ea8026Sopenharmony_ci            break
107119ea8026Sopenharmony_ci
107219ea8026Sopenharmony_ci    stop = time.time()
107319ea8026Sopenharmony_ci
107419ea8026Sopenharmony_ci    if stdout:
107519ea8026Sopenharmony_ci        try:
107619ea8026Sopenharmony_ci            stdout.close()
107719ea8026Sopenharmony_ci        except BrokenPipeError:
107819ea8026Sopenharmony_ci            pass
107919ea8026Sopenharmony_ci    if trace:
108019ea8026Sopenharmony_ci        try:
108119ea8026Sopenharmony_ci            trace.close()
108219ea8026Sopenharmony_ci        except BrokenPipeError:
108319ea8026Sopenharmony_ci            pass
108419ea8026Sopenharmony_ci    if output:
108519ea8026Sopenharmony_ci        output.close()
108619ea8026Sopenharmony_ci
108719ea8026Sopenharmony_ci    # show summary
108819ea8026Sopenharmony_ci    print()
108919ea8026Sopenharmony_ci    print('%sdone:%s %s' % (
109019ea8026Sopenharmony_ci        ('\x1b[32m' if not failures else '\x1b[31m')
109119ea8026Sopenharmony_ci            if args['color'] else '',
109219ea8026Sopenharmony_ci        '\x1b[m' if args['color'] else '',
109319ea8026Sopenharmony_ci        ', '.join(filter(None, [
109419ea8026Sopenharmony_ci            '%d/%d passed' % (passed, expected),
109519ea8026Sopenharmony_ci            '%d/%d failed' % (len(failures), expected),
109619ea8026Sopenharmony_ci            '%dpls!' % powerlosses if powerlosses else None,
109719ea8026Sopenharmony_ci            'in %.2fs' % (stop-start)]))))
109819ea8026Sopenharmony_ci    print()
109919ea8026Sopenharmony_ci
110019ea8026Sopenharmony_ci    # print each failure
110119ea8026Sopenharmony_ci    for failure in failures:
110219ea8026Sopenharmony_ci        assert failure.id is not None, '%s broken? %r' % (
110319ea8026Sopenharmony_ci            ' '.join(shlex.quote(c) for c in runner_),
110419ea8026Sopenharmony_ci            failure)
110519ea8026Sopenharmony_ci
110619ea8026Sopenharmony_ci        # get some extra info from runner
110719ea8026Sopenharmony_ci        path, lineno = find_path(runner_, failure.id, **args)
110819ea8026Sopenharmony_ci        defines = find_defines(runner_, failure.id, **args)
110919ea8026Sopenharmony_ci
111019ea8026Sopenharmony_ci        # show summary of failure
111119ea8026Sopenharmony_ci        print('%s%s:%d:%sfailure:%s %s%s failed' % (
111219ea8026Sopenharmony_ci            '\x1b[01m' if args['color'] else '',
111319ea8026Sopenharmony_ci            path, lineno,
111419ea8026Sopenharmony_ci            '\x1b[01;31m' if args['color'] else '',
111519ea8026Sopenharmony_ci            '\x1b[m' if args['color'] else '',
111619ea8026Sopenharmony_ci            failure.id,
111719ea8026Sopenharmony_ci            ' (%s)' % ', '.join('%s=%s' % (k,v) for k,v in defines.items())
111819ea8026Sopenharmony_ci                if defines else ''))
111919ea8026Sopenharmony_ci
112019ea8026Sopenharmony_ci        if failure.stdout:
112119ea8026Sopenharmony_ci            stdout = failure.stdout
112219ea8026Sopenharmony_ci            if failure.assert_ is not None:
112319ea8026Sopenharmony_ci                stdout = stdout[:-1]
112419ea8026Sopenharmony_ci            for line in stdout[-args.get('context', 5):]:
112519ea8026Sopenharmony_ci                sys.stdout.write(line)
112619ea8026Sopenharmony_ci
112719ea8026Sopenharmony_ci        if failure.assert_ is not None:
112819ea8026Sopenharmony_ci            path, lineno, message = failure.assert_
112919ea8026Sopenharmony_ci            print('%s%s:%d:%sassert:%s %s' % (
113019ea8026Sopenharmony_ci                '\x1b[01m' if args['color'] else '',
113119ea8026Sopenharmony_ci                path, lineno,
113219ea8026Sopenharmony_ci                '\x1b[01;31m' if args['color'] else '',
113319ea8026Sopenharmony_ci                '\x1b[m' if args['color'] else '',
113419ea8026Sopenharmony_ci                message))
113519ea8026Sopenharmony_ci            with open(path) as f:
113619ea8026Sopenharmony_ci                line = next(it.islice(f, lineno-1, None)).strip('\n')
113719ea8026Sopenharmony_ci                print(line)
113819ea8026Sopenharmony_ci        print()
113919ea8026Sopenharmony_ci
114019ea8026Sopenharmony_ci    # drop into gdb?
114119ea8026Sopenharmony_ci    if failures and (args.get('gdb')
114219ea8026Sopenharmony_ci            or args.get('gdb_case')
114319ea8026Sopenharmony_ci            or args.get('gdb_main')
114419ea8026Sopenharmony_ci            or args.get('gdb_pl') is not None
114519ea8026Sopenharmony_ci            or args.get('gdb_pl_before')
114619ea8026Sopenharmony_ci            or args.get('gdb_pl_after')):
114719ea8026Sopenharmony_ci        failure = failures[0]
114819ea8026Sopenharmony_ci        cmd = runner_ + [failure.id]
114919ea8026Sopenharmony_ci
115019ea8026Sopenharmony_ci        if args.get('gdb_main'):
115119ea8026Sopenharmony_ci            # we don't really need the case breakpoint here, but it
115219ea8026Sopenharmony_ci            # can be helpful
115319ea8026Sopenharmony_ci            path, lineno = find_path(runner_, failure.id, **args)
115419ea8026Sopenharmony_ci            cmd[:0] = args['gdb_path'] + [
115519ea8026Sopenharmony_ci                '-ex', 'break main',
115619ea8026Sopenharmony_ci                '-ex', 'break %s:%d' % (path, lineno),
115719ea8026Sopenharmony_ci                '-ex', 'run',
115819ea8026Sopenharmony_ci                '--args']
115919ea8026Sopenharmony_ci        elif args.get('gdb_case'):
116019ea8026Sopenharmony_ci            path, lineno = find_path(runner_, failure.id, **args)
116119ea8026Sopenharmony_ci            cmd[:0] = args['gdb_path'] + [
116219ea8026Sopenharmony_ci                '-ex', 'break %s:%d' % (path, lineno),
116319ea8026Sopenharmony_ci                '-ex', 'run',
116419ea8026Sopenharmony_ci                '--args']
116519ea8026Sopenharmony_ci        elif args.get('gdb_pl') is not None:
116619ea8026Sopenharmony_ci            path, lineno = find_path(runner_, failure.id, **args)
116719ea8026Sopenharmony_ci            cmd[:0] = args['gdb_path'] + [
116819ea8026Sopenharmony_ci                '-ex', 'break %s:%d' % (path, lineno),
116919ea8026Sopenharmony_ci                '-ex', 'ignore 1 %d' % args['gdb_pl'],
117019ea8026Sopenharmony_ci                '-ex', 'run',
117119ea8026Sopenharmony_ci                '--args']
117219ea8026Sopenharmony_ci        elif args.get('gdb_pl_before'):
117319ea8026Sopenharmony_ci            # figure out how many powerlosses there were
117419ea8026Sopenharmony_ci            powerlosses = (
117519ea8026Sopenharmony_ci                sum(1 for _ in re.finditer('[0-9a-f]',
117619ea8026Sopenharmony_ci                    failure.id.split(':', 2)[-1]))
117719ea8026Sopenharmony_ci                if failure.id.count(':') >= 2 else 0)
117819ea8026Sopenharmony_ci            path, lineno = find_path(runner_, failure.id, **args)
117919ea8026Sopenharmony_ci            cmd[:0] = args['gdb_path'] + [
118019ea8026Sopenharmony_ci                '-ex', 'break %s:%d' % (path, lineno),
118119ea8026Sopenharmony_ci                '-ex', 'ignore 1 %d' % max(powerlosses-1, 0),
118219ea8026Sopenharmony_ci                '-ex', 'run',
118319ea8026Sopenharmony_ci                '--args']
118419ea8026Sopenharmony_ci        elif args.get('gdb_pl_after'):
118519ea8026Sopenharmony_ci            # figure out how many powerlosses there were
118619ea8026Sopenharmony_ci            powerlosses = (
118719ea8026Sopenharmony_ci                sum(1 for _ in re.finditer('[0-9a-f]',
118819ea8026Sopenharmony_ci                    failure.id.split(':', 2)[-1]))
118919ea8026Sopenharmony_ci                if failure.id.count(':') >= 2 else 0)
119019ea8026Sopenharmony_ci            path, lineno = find_path(runner_, failure.id, **args)
119119ea8026Sopenharmony_ci            cmd[:0] = args['gdb_path'] + [
119219ea8026Sopenharmony_ci                '-ex', 'break %s:%d' % (path, lineno),
119319ea8026Sopenharmony_ci                '-ex', 'ignore 1 %d' % powerlosses,
119419ea8026Sopenharmony_ci                '-ex', 'run',
119519ea8026Sopenharmony_ci                '--args']
119619ea8026Sopenharmony_ci        elif failure.assert_ is not None:
119719ea8026Sopenharmony_ci            cmd[:0] = args['gdb_path'] + [
119819ea8026Sopenharmony_ci                '-ex', 'run',
119919ea8026Sopenharmony_ci                '-ex', 'frame function raise',
120019ea8026Sopenharmony_ci                '-ex', 'up 2',
120119ea8026Sopenharmony_ci                '--args']
120219ea8026Sopenharmony_ci        else:
120319ea8026Sopenharmony_ci            cmd[:0] = args['gdb_path'] + [
120419ea8026Sopenharmony_ci                '-ex', 'run',
120519ea8026Sopenharmony_ci                '--args']
120619ea8026Sopenharmony_ci
120719ea8026Sopenharmony_ci        # exec gdb interactively
120819ea8026Sopenharmony_ci        if args.get('verbose'):
120919ea8026Sopenharmony_ci            print(' '.join(shlex.quote(c) for c in cmd))
121019ea8026Sopenharmony_ci        os.execvp(cmd[0], cmd)
121119ea8026Sopenharmony_ci
121219ea8026Sopenharmony_ci    return 1 if failures else 0
121319ea8026Sopenharmony_ci
121419ea8026Sopenharmony_ci
121519ea8026Sopenharmony_cidef main(**args):
121619ea8026Sopenharmony_ci    # figure out what color should be
121719ea8026Sopenharmony_ci    if args.get('color') == 'auto':
121819ea8026Sopenharmony_ci        args['color'] = sys.stdout.isatty()
121919ea8026Sopenharmony_ci    elif args.get('color') == 'always':
122019ea8026Sopenharmony_ci        args['color'] = True
122119ea8026Sopenharmony_ci    else:
122219ea8026Sopenharmony_ci        args['color'] = False
122319ea8026Sopenharmony_ci
122419ea8026Sopenharmony_ci    if args.get('compile'):
122519ea8026Sopenharmony_ci        return compile(**args)
122619ea8026Sopenharmony_ci    elif (args.get('summary')
122719ea8026Sopenharmony_ci            or args.get('list_suites')
122819ea8026Sopenharmony_ci            or args.get('list_cases')
122919ea8026Sopenharmony_ci            or args.get('list_suite_paths')
123019ea8026Sopenharmony_ci            or args.get('list_case_paths')
123119ea8026Sopenharmony_ci            or args.get('list_defines')
123219ea8026Sopenharmony_ci            or args.get('list_permutation_defines')
123319ea8026Sopenharmony_ci            or args.get('list_implicit_defines')
123419ea8026Sopenharmony_ci            or args.get('list_geometries')
123519ea8026Sopenharmony_ci            or args.get('list_powerlosses')):
123619ea8026Sopenharmony_ci        return list_(**args)
123719ea8026Sopenharmony_ci    else:
123819ea8026Sopenharmony_ci        return run(**args)
123919ea8026Sopenharmony_ci
124019ea8026Sopenharmony_ci
124119ea8026Sopenharmony_ciif __name__ == "__main__":
124219ea8026Sopenharmony_ci    import argparse
124319ea8026Sopenharmony_ci    import sys
124419ea8026Sopenharmony_ci    argparse.ArgumentParser._handle_conflict_ignore = lambda *_: None
124519ea8026Sopenharmony_ci    argparse._ArgumentGroup._handle_conflict_ignore = lambda *_: None
124619ea8026Sopenharmony_ci    parser = argparse.ArgumentParser(
124719ea8026Sopenharmony_ci        description="Build and run tests.",
124819ea8026Sopenharmony_ci        allow_abbrev=False,
124919ea8026Sopenharmony_ci        conflict_handler='ignore')
125019ea8026Sopenharmony_ci    parser.add_argument(
125119ea8026Sopenharmony_ci        '-v', '--verbose',
125219ea8026Sopenharmony_ci        action='store_true',
125319ea8026Sopenharmony_ci        help="Output commands that run behind the scenes.")
125419ea8026Sopenharmony_ci    parser.add_argument(
125519ea8026Sopenharmony_ci        '--color',
125619ea8026Sopenharmony_ci        choices=['never', 'always', 'auto'],
125719ea8026Sopenharmony_ci        default='auto',
125819ea8026Sopenharmony_ci        help="When to use terminal colors. Defaults to 'auto'.")
125919ea8026Sopenharmony_ci
126019ea8026Sopenharmony_ci    # test flags
126119ea8026Sopenharmony_ci    test_parser = parser.add_argument_group('test options')
126219ea8026Sopenharmony_ci    test_parser.add_argument(
126319ea8026Sopenharmony_ci        'runner',
126419ea8026Sopenharmony_ci        nargs='?',
126519ea8026Sopenharmony_ci        type=lambda x: x.split(),
126619ea8026Sopenharmony_ci        help="Test runner to use for testing. Defaults to %r." % RUNNER_PATH)
126719ea8026Sopenharmony_ci    test_parser.add_argument(
126819ea8026Sopenharmony_ci        'test_ids',
126919ea8026Sopenharmony_ci        nargs='*',
127019ea8026Sopenharmony_ci        help="Description of tests to run.")
127119ea8026Sopenharmony_ci    test_parser.add_argument(
127219ea8026Sopenharmony_ci        '-Y', '--summary',
127319ea8026Sopenharmony_ci        action='store_true',
127419ea8026Sopenharmony_ci        help="Show quick summary.")
127519ea8026Sopenharmony_ci    test_parser.add_argument(
127619ea8026Sopenharmony_ci        '-l', '--list-suites',
127719ea8026Sopenharmony_ci        action='store_true',
127819ea8026Sopenharmony_ci        help="List test suites.")
127919ea8026Sopenharmony_ci    test_parser.add_argument(
128019ea8026Sopenharmony_ci        '-L', '--list-cases',
128119ea8026Sopenharmony_ci        action='store_true',
128219ea8026Sopenharmony_ci        help="List test cases.")
128319ea8026Sopenharmony_ci    test_parser.add_argument(
128419ea8026Sopenharmony_ci        '--list-suite-paths',
128519ea8026Sopenharmony_ci        action='store_true',
128619ea8026Sopenharmony_ci        help="List the path for each test suite.")
128719ea8026Sopenharmony_ci    test_parser.add_argument(
128819ea8026Sopenharmony_ci        '--list-case-paths',
128919ea8026Sopenharmony_ci        action='store_true',
129019ea8026Sopenharmony_ci        help="List the path and line number for each test case.")
129119ea8026Sopenharmony_ci    test_parser.add_argument(
129219ea8026Sopenharmony_ci        '--list-defines',
129319ea8026Sopenharmony_ci        action='store_true',
129419ea8026Sopenharmony_ci        help="List all defines in this test-runner.")
129519ea8026Sopenharmony_ci    test_parser.add_argument(
129619ea8026Sopenharmony_ci        '--list-permutation-defines',
129719ea8026Sopenharmony_ci        action='store_true',
129819ea8026Sopenharmony_ci        help="List explicit defines in this test-runner.")
129919ea8026Sopenharmony_ci    test_parser.add_argument(
130019ea8026Sopenharmony_ci        '--list-implicit-defines',
130119ea8026Sopenharmony_ci        action='store_true',
130219ea8026Sopenharmony_ci        help="List implicit defines in this test-runner.")
130319ea8026Sopenharmony_ci    test_parser.add_argument(
130419ea8026Sopenharmony_ci        '--list-geometries',
130519ea8026Sopenharmony_ci        action='store_true',
130619ea8026Sopenharmony_ci        help="List the available disk geometries.")
130719ea8026Sopenharmony_ci    test_parser.add_argument(
130819ea8026Sopenharmony_ci        '--list-powerlosses',
130919ea8026Sopenharmony_ci        action='store_true',
131019ea8026Sopenharmony_ci        help="List the available power-loss scenarios.")
131119ea8026Sopenharmony_ci    test_parser.add_argument(
131219ea8026Sopenharmony_ci        '-D', '--define',
131319ea8026Sopenharmony_ci        action='append',
131419ea8026Sopenharmony_ci        help="Override a test define.")
131519ea8026Sopenharmony_ci    test_parser.add_argument(
131619ea8026Sopenharmony_ci        '-G', '--geometry',
131719ea8026Sopenharmony_ci        help="Comma-separated list of disk geometries to test.")
131819ea8026Sopenharmony_ci    test_parser.add_argument(
131919ea8026Sopenharmony_ci        '-P', '--powerloss',
132019ea8026Sopenharmony_ci        help="Comma-separated list of power-loss scenarios to test.")
132119ea8026Sopenharmony_ci    test_parser.add_argument(
132219ea8026Sopenharmony_ci        '-d', '--disk',
132319ea8026Sopenharmony_ci        help="Direct block device operations to this file.")
132419ea8026Sopenharmony_ci    test_parser.add_argument(
132519ea8026Sopenharmony_ci        '-t', '--trace',
132619ea8026Sopenharmony_ci        help="Direct trace output to this file.")
132719ea8026Sopenharmony_ci    test_parser.add_argument(
132819ea8026Sopenharmony_ci        '--trace-backtrace',
132919ea8026Sopenharmony_ci        action='store_true',
133019ea8026Sopenharmony_ci        help="Include a backtrace with every trace statement.")
133119ea8026Sopenharmony_ci    test_parser.add_argument(
133219ea8026Sopenharmony_ci        '--trace-period',
133319ea8026Sopenharmony_ci        help="Sample trace output at this period in cycles.")
133419ea8026Sopenharmony_ci    test_parser.add_argument(
133519ea8026Sopenharmony_ci        '--trace-freq',
133619ea8026Sopenharmony_ci        help="Sample trace output at this frequency in hz.")
133719ea8026Sopenharmony_ci    test_parser.add_argument(
133819ea8026Sopenharmony_ci        '-O', '--stdout',
133919ea8026Sopenharmony_ci        help="Direct stdout to this file. Note stderr is already merged here.")
134019ea8026Sopenharmony_ci    test_parser.add_argument(
134119ea8026Sopenharmony_ci        '-o', '--output',
134219ea8026Sopenharmony_ci        help="CSV file to store results.")
134319ea8026Sopenharmony_ci    test_parser.add_argument(
134419ea8026Sopenharmony_ci        '--read-sleep',
134519ea8026Sopenharmony_ci        help="Artificial read delay in seconds.")
134619ea8026Sopenharmony_ci    test_parser.add_argument(
134719ea8026Sopenharmony_ci        '--prog-sleep',
134819ea8026Sopenharmony_ci        help="Artificial prog delay in seconds.")
134919ea8026Sopenharmony_ci    test_parser.add_argument(
135019ea8026Sopenharmony_ci        '--erase-sleep',
135119ea8026Sopenharmony_ci        help="Artificial erase delay in seconds.")
135219ea8026Sopenharmony_ci    test_parser.add_argument(
135319ea8026Sopenharmony_ci        '-j', '--jobs',
135419ea8026Sopenharmony_ci        nargs='?',
135519ea8026Sopenharmony_ci        type=lambda x: int(x, 0),
135619ea8026Sopenharmony_ci        const=0,
135719ea8026Sopenharmony_ci        help="Number of parallel runners to run. 0 runs one runner per core.")
135819ea8026Sopenharmony_ci    test_parser.add_argument(
135919ea8026Sopenharmony_ci        '-k', '--keep-going',
136019ea8026Sopenharmony_ci        action='store_true',
136119ea8026Sopenharmony_ci        help="Don't stop on first error.")
136219ea8026Sopenharmony_ci    test_parser.add_argument(
136319ea8026Sopenharmony_ci        '-i', '--isolate',
136419ea8026Sopenharmony_ci        action='store_true',
136519ea8026Sopenharmony_ci        help="Run each test permutation in a separate process.")
136619ea8026Sopenharmony_ci    test_parser.add_argument(
136719ea8026Sopenharmony_ci        '-b', '--by-suites',
136819ea8026Sopenharmony_ci        action='store_true',
136919ea8026Sopenharmony_ci        help="Step through tests by suite.")
137019ea8026Sopenharmony_ci    test_parser.add_argument(
137119ea8026Sopenharmony_ci        '-B', '--by-cases',
137219ea8026Sopenharmony_ci        action='store_true',
137319ea8026Sopenharmony_ci        help="Step through tests by case.")
137419ea8026Sopenharmony_ci    test_parser.add_argument(
137519ea8026Sopenharmony_ci        '--context',
137619ea8026Sopenharmony_ci        type=lambda x: int(x, 0),
137719ea8026Sopenharmony_ci        default=5,
137819ea8026Sopenharmony_ci        help="Show this many lines of stdout on test failure. "
137919ea8026Sopenharmony_ci            "Defaults to 5.")
138019ea8026Sopenharmony_ci    test_parser.add_argument(
138119ea8026Sopenharmony_ci        '--gdb',
138219ea8026Sopenharmony_ci        action='store_true',
138319ea8026Sopenharmony_ci        help="Drop into gdb on test failure.")
138419ea8026Sopenharmony_ci    test_parser.add_argument(
138519ea8026Sopenharmony_ci        '--gdb-case',
138619ea8026Sopenharmony_ci        action='store_true',
138719ea8026Sopenharmony_ci        help="Drop into gdb on test failure but stop at the beginning "
138819ea8026Sopenharmony_ci            "of the failing test case.")
138919ea8026Sopenharmony_ci    test_parser.add_argument(
139019ea8026Sopenharmony_ci        '--gdb-main',
139119ea8026Sopenharmony_ci        action='store_true',
139219ea8026Sopenharmony_ci        help="Drop into gdb on test failure but stop at the beginning "
139319ea8026Sopenharmony_ci            "of main.")
139419ea8026Sopenharmony_ci    test_parser.add_argument(
139519ea8026Sopenharmony_ci        '--gdb-pl',
139619ea8026Sopenharmony_ci        type=lambda x: int(x, 0),
139719ea8026Sopenharmony_ci        help="Drop into gdb on this specific powerloss.")
139819ea8026Sopenharmony_ci    test_parser.add_argument(
139919ea8026Sopenharmony_ci        '--gdb-pl-before',
140019ea8026Sopenharmony_ci        action='store_true',
140119ea8026Sopenharmony_ci        help="Drop into gdb before the powerloss that caused the failure.")
140219ea8026Sopenharmony_ci    test_parser.add_argument(
140319ea8026Sopenharmony_ci        '--gdb-pl-after',
140419ea8026Sopenharmony_ci        action='store_true',
140519ea8026Sopenharmony_ci        help="Drop into gdb after the powerloss that caused the failure.")
140619ea8026Sopenharmony_ci    test_parser.add_argument(
140719ea8026Sopenharmony_ci        '--gdb-path',
140819ea8026Sopenharmony_ci        type=lambda x: x.split(),
140919ea8026Sopenharmony_ci        default=GDB_PATH,
141019ea8026Sopenharmony_ci        help="Path to the gdb executable, may include flags. "
141119ea8026Sopenharmony_ci            "Defaults to %r." % GDB_PATH)
141219ea8026Sopenharmony_ci    test_parser.add_argument(
141319ea8026Sopenharmony_ci        '--exec',
141419ea8026Sopenharmony_ci        type=lambda e: e.split(),
141519ea8026Sopenharmony_ci        help="Run under another executable.")
141619ea8026Sopenharmony_ci    test_parser.add_argument(
141719ea8026Sopenharmony_ci        '--valgrind',
141819ea8026Sopenharmony_ci        action='store_true',
141919ea8026Sopenharmony_ci        help="Run under Valgrind to find memory errors. Implicitly sets "
142019ea8026Sopenharmony_ci            "--isolate.")
142119ea8026Sopenharmony_ci    test_parser.add_argument(
142219ea8026Sopenharmony_ci        '--valgrind-path',
142319ea8026Sopenharmony_ci        type=lambda x: x.split(),
142419ea8026Sopenharmony_ci        default=VALGRIND_PATH,
142519ea8026Sopenharmony_ci        help="Path to the Valgrind executable, may include flags. "
142619ea8026Sopenharmony_ci            "Defaults to %r." % VALGRIND_PATH)
142719ea8026Sopenharmony_ci    test_parser.add_argument(
142819ea8026Sopenharmony_ci        '-p', '--perf',
142919ea8026Sopenharmony_ci        help="Run under Linux's perf to sample performance counters, writing "
143019ea8026Sopenharmony_ci            "samples to this file.")
143119ea8026Sopenharmony_ci    test_parser.add_argument(
143219ea8026Sopenharmony_ci        '--perf-freq',
143319ea8026Sopenharmony_ci        help="perf sampling frequency. This is passed directly to the perf "
143419ea8026Sopenharmony_ci            "script.")
143519ea8026Sopenharmony_ci    test_parser.add_argument(
143619ea8026Sopenharmony_ci        '--perf-period',
143719ea8026Sopenharmony_ci        help="perf sampling period. This is passed directly to the perf "
143819ea8026Sopenharmony_ci            "script.")
143919ea8026Sopenharmony_ci    test_parser.add_argument(
144019ea8026Sopenharmony_ci        '--perf-events',
144119ea8026Sopenharmony_ci        help="perf events to record. This is passed directly to the perf "
144219ea8026Sopenharmony_ci            "script.")
144319ea8026Sopenharmony_ci    test_parser.add_argument(
144419ea8026Sopenharmony_ci        '--perf-script',
144519ea8026Sopenharmony_ci        type=lambda x: x.split(),
144619ea8026Sopenharmony_ci        default=PERF_SCRIPT,
144719ea8026Sopenharmony_ci        help="Path to the perf script to use. Defaults to %r." % PERF_SCRIPT)
144819ea8026Sopenharmony_ci    test_parser.add_argument(
144919ea8026Sopenharmony_ci        '--perf-path',
145019ea8026Sopenharmony_ci        type=lambda x: x.split(),
145119ea8026Sopenharmony_ci        help="Path to the perf executable, may include flags. This is passed "
145219ea8026Sopenharmony_ci            "directly to the perf script")
145319ea8026Sopenharmony_ci
145419ea8026Sopenharmony_ci    # compilation flags
145519ea8026Sopenharmony_ci    comp_parser = parser.add_argument_group('compilation options')
145619ea8026Sopenharmony_ci    comp_parser.add_argument(
145719ea8026Sopenharmony_ci        'test_paths',
145819ea8026Sopenharmony_ci        nargs='*',
145919ea8026Sopenharmony_ci        help="Description of *.toml files to compile. May be a directory "
146019ea8026Sopenharmony_ci            "or a list of paths.")
146119ea8026Sopenharmony_ci    comp_parser.add_argument(
146219ea8026Sopenharmony_ci        '-c', '--compile',
146319ea8026Sopenharmony_ci        action='store_true',
146419ea8026Sopenharmony_ci        help="Compile a test suite or source file.")
146519ea8026Sopenharmony_ci    comp_parser.add_argument(
146619ea8026Sopenharmony_ci        '-s', '--source',
146719ea8026Sopenharmony_ci        help="Source file to compile, possibly injecting internal tests.")
146819ea8026Sopenharmony_ci    comp_parser.add_argument(
146919ea8026Sopenharmony_ci        '--include',
147019ea8026Sopenharmony_ci        default=HEADER_PATH,
147119ea8026Sopenharmony_ci        help="Inject this header file into every compiled test file. "
147219ea8026Sopenharmony_ci            "Defaults to %r." % HEADER_PATH)
147319ea8026Sopenharmony_ci    comp_parser.add_argument(
147419ea8026Sopenharmony_ci        '-o', '--output',
147519ea8026Sopenharmony_ci        help="Output file.")
147619ea8026Sopenharmony_ci
147719ea8026Sopenharmony_ci    # runner/test_paths overlap, so need to do some munging here
147819ea8026Sopenharmony_ci    args = parser.parse_intermixed_args()
147919ea8026Sopenharmony_ci    args.test_paths = [' '.join(args.runner or [])] + args.test_ids
148019ea8026Sopenharmony_ci    args.runner = args.runner or [RUNNER_PATH]
148119ea8026Sopenharmony_ci
148219ea8026Sopenharmony_ci    sys.exit(main(**{k: v
148319ea8026Sopenharmony_ci        for k, v in vars(args).items()
148419ea8026Sopenharmony_ci        if v is not None}))
1485