162306a36Sopenharmony_ci#!/usr/bin/env python3
262306a36Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci"""
562306a36Sopenharmony_citdc.py - Linux tc (Traffic Control) unit test driver
662306a36Sopenharmony_ci
762306a36Sopenharmony_ciCopyright (C) 2017 Lucas Bates <lucasb@mojatatu.com>
862306a36Sopenharmony_ci"""
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ciimport re
1162306a36Sopenharmony_ciimport os
1262306a36Sopenharmony_ciimport sys
1362306a36Sopenharmony_ciimport argparse
1462306a36Sopenharmony_ciimport importlib
1562306a36Sopenharmony_ciimport json
1662306a36Sopenharmony_ciimport subprocess
1762306a36Sopenharmony_ciimport time
1862306a36Sopenharmony_ciimport traceback
1962306a36Sopenharmony_cifrom collections import OrderedDict
2062306a36Sopenharmony_cifrom string import Template
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cifrom tdc_config import *
2362306a36Sopenharmony_cifrom tdc_helper import *
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ciimport TdcPlugin
2662306a36Sopenharmony_cifrom TdcResults import *
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ciclass PluginDependencyException(Exception):
2962306a36Sopenharmony_ci    def __init__(self, missing_pg):
3062306a36Sopenharmony_ci        self.missing_pg = missing_pg
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciclass PluginMgrTestFail(Exception):
3362306a36Sopenharmony_ci    def __init__(self, stage, output, message):
3462306a36Sopenharmony_ci        self.stage = stage
3562306a36Sopenharmony_ci        self.output = output
3662306a36Sopenharmony_ci        self.message = message
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ciclass PluginMgr:
3962306a36Sopenharmony_ci    def __init__(self, argparser):
4062306a36Sopenharmony_ci        super().__init__()
4162306a36Sopenharmony_ci        self.plugins = {}
4262306a36Sopenharmony_ci        self.plugin_instances = []
4362306a36Sopenharmony_ci        self.failed_plugins = {}
4462306a36Sopenharmony_ci        self.argparser = argparser
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci        # TODO, put plugins in order
4762306a36Sopenharmony_ci        plugindir = os.getenv('TDC_PLUGIN_DIR', './plugins')
4862306a36Sopenharmony_ci        for dirpath, dirnames, filenames in os.walk(plugindir):
4962306a36Sopenharmony_ci            for fn in filenames:
5062306a36Sopenharmony_ci                if (fn.endswith('.py') and
5162306a36Sopenharmony_ci                    not fn == '__init__.py' and
5262306a36Sopenharmony_ci                    not fn.startswith('#') and
5362306a36Sopenharmony_ci                    not fn.startswith('.#')):
5462306a36Sopenharmony_ci                    mn = fn[0:-3]
5562306a36Sopenharmony_ci                    foo = importlib.import_module('plugins.' + mn)
5662306a36Sopenharmony_ci                    self.plugins[mn] = foo
5762306a36Sopenharmony_ci                    self.plugin_instances.append(foo.SubPlugin())
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci    def load_plugin(self, pgdir, pgname):
6062306a36Sopenharmony_ci        pgname = pgname[0:-3]
6162306a36Sopenharmony_ci        foo = importlib.import_module('{}.{}'.format(pgdir, pgname))
6262306a36Sopenharmony_ci        self.plugins[pgname] = foo
6362306a36Sopenharmony_ci        self.plugin_instances.append(foo.SubPlugin())
6462306a36Sopenharmony_ci        self.plugin_instances[-1].check_args(self.args, None)
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci    def get_required_plugins(self, testlist):
6762306a36Sopenharmony_ci        '''
6862306a36Sopenharmony_ci        Get all required plugins from the list of test cases and return
6962306a36Sopenharmony_ci        all unique items.
7062306a36Sopenharmony_ci        '''
7162306a36Sopenharmony_ci        reqs = []
7262306a36Sopenharmony_ci        for t in testlist:
7362306a36Sopenharmony_ci            try:
7462306a36Sopenharmony_ci                if 'requires' in t['plugins']:
7562306a36Sopenharmony_ci                    if isinstance(t['plugins']['requires'], list):
7662306a36Sopenharmony_ci                        reqs.extend(t['plugins']['requires'])
7762306a36Sopenharmony_ci                    else:
7862306a36Sopenharmony_ci                        reqs.append(t['plugins']['requires'])
7962306a36Sopenharmony_ci            except KeyError:
8062306a36Sopenharmony_ci                continue
8162306a36Sopenharmony_ci        reqs = get_unique_item(reqs)
8262306a36Sopenharmony_ci        return reqs
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci    def load_required_plugins(self, reqs, parser, args, remaining):
8562306a36Sopenharmony_ci        '''
8662306a36Sopenharmony_ci        Get all required plugins from the list of test cases and load any plugin
8762306a36Sopenharmony_ci        that is not already enabled.
8862306a36Sopenharmony_ci        '''
8962306a36Sopenharmony_ci        pgd = ['plugin-lib', 'plugin-lib-custom']
9062306a36Sopenharmony_ci        pnf = []
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci        for r in reqs:
9362306a36Sopenharmony_ci            if r not in self.plugins:
9462306a36Sopenharmony_ci                fname = '{}.py'.format(r)
9562306a36Sopenharmony_ci                source_path = []
9662306a36Sopenharmony_ci                for d in pgd:
9762306a36Sopenharmony_ci                    pgpath = '{}/{}'.format(d, fname)
9862306a36Sopenharmony_ci                    if os.path.isfile(pgpath):
9962306a36Sopenharmony_ci                        source_path.append(pgpath)
10062306a36Sopenharmony_ci                if len(source_path) == 0:
10162306a36Sopenharmony_ci                    print('ERROR: unable to find required plugin {}'.format(r))
10262306a36Sopenharmony_ci                    pnf.append(fname)
10362306a36Sopenharmony_ci                    continue
10462306a36Sopenharmony_ci                elif len(source_path) > 1:
10562306a36Sopenharmony_ci                    print('WARNING: multiple copies of plugin {} found, using version found')
10662306a36Sopenharmony_ci                    print('at {}'.format(source_path[0]))
10762306a36Sopenharmony_ci                pgdir = source_path[0]
10862306a36Sopenharmony_ci                pgdir = pgdir.split('/')[0]
10962306a36Sopenharmony_ci                self.load_plugin(pgdir, fname)
11062306a36Sopenharmony_ci        if len(pnf) > 0:
11162306a36Sopenharmony_ci            raise PluginDependencyException(pnf)
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci        parser = self.call_add_args(parser)
11462306a36Sopenharmony_ci        (args, remaining) = parser.parse_known_args(args=remaining, namespace=args)
11562306a36Sopenharmony_ci        return args
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci    def call_pre_suite(self, testcount, testidlist):
11862306a36Sopenharmony_ci        for pgn_inst in self.plugin_instances:
11962306a36Sopenharmony_ci            pgn_inst.pre_suite(testcount, testidlist)
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci    def call_post_suite(self, index):
12262306a36Sopenharmony_ci        for pgn_inst in reversed(self.plugin_instances):
12362306a36Sopenharmony_ci            pgn_inst.post_suite(index)
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci    def call_pre_case(self, caseinfo, *, test_skip=False):
12662306a36Sopenharmony_ci        for pgn_inst in self.plugin_instances:
12762306a36Sopenharmony_ci            try:
12862306a36Sopenharmony_ci                pgn_inst.pre_case(caseinfo, test_skip)
12962306a36Sopenharmony_ci            except Exception as ee:
13062306a36Sopenharmony_ci                print('exception {} in call to pre_case for {} plugin'.
13162306a36Sopenharmony_ci                      format(ee, pgn_inst.__class__))
13262306a36Sopenharmony_ci                print('test_ordinal is {}'.format(test_ordinal))
13362306a36Sopenharmony_ci                print('testid is {}'.format(caseinfo['id']))
13462306a36Sopenharmony_ci                raise
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci    def call_post_case(self):
13762306a36Sopenharmony_ci        for pgn_inst in reversed(self.plugin_instances):
13862306a36Sopenharmony_ci            pgn_inst.post_case()
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci    def call_pre_execute(self):
14162306a36Sopenharmony_ci        for pgn_inst in self.plugin_instances:
14262306a36Sopenharmony_ci            pgn_inst.pre_execute()
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci    def call_post_execute(self):
14562306a36Sopenharmony_ci        for pgn_inst in reversed(self.plugin_instances):
14662306a36Sopenharmony_ci            pgn_inst.post_execute()
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci    def call_add_args(self, parser):
14962306a36Sopenharmony_ci        for pgn_inst in self.plugin_instances:
15062306a36Sopenharmony_ci            parser = pgn_inst.add_args(parser)
15162306a36Sopenharmony_ci        return parser
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci    def call_check_args(self, args, remaining):
15462306a36Sopenharmony_ci        for pgn_inst in self.plugin_instances:
15562306a36Sopenharmony_ci            pgn_inst.check_args(args, remaining)
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci    def call_adjust_command(self, stage, command):
15862306a36Sopenharmony_ci        for pgn_inst in self.plugin_instances:
15962306a36Sopenharmony_ci            command = pgn_inst.adjust_command(stage, command)
16062306a36Sopenharmony_ci        return command
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci    def set_args(self, args):
16362306a36Sopenharmony_ci        self.args = args
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci    @staticmethod
16662306a36Sopenharmony_ci    def _make_argparser(args):
16762306a36Sopenharmony_ci        self.argparser = argparse.ArgumentParser(
16862306a36Sopenharmony_ci            description='Linux TC unit tests')
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cidef replace_keywords(cmd):
17162306a36Sopenharmony_ci    """
17262306a36Sopenharmony_ci    For a given executable command, substitute any known
17362306a36Sopenharmony_ci    variables contained within NAMES with the correct values
17462306a36Sopenharmony_ci    """
17562306a36Sopenharmony_ci    tcmd = Template(cmd)
17662306a36Sopenharmony_ci    subcmd = tcmd.safe_substitute(NAMES)
17762306a36Sopenharmony_ci    return subcmd
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cidef exec_cmd(args, pm, stage, command):
18162306a36Sopenharmony_ci    """
18262306a36Sopenharmony_ci    Perform any required modifications on an executable command, then run
18362306a36Sopenharmony_ci    it in a subprocess and return the results.
18462306a36Sopenharmony_ci    """
18562306a36Sopenharmony_ci    if len(command.strip()) == 0:
18662306a36Sopenharmony_ci        return None, None
18762306a36Sopenharmony_ci    if '$' in command:
18862306a36Sopenharmony_ci        command = replace_keywords(command)
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci    command = pm.call_adjust_command(stage, command)
19162306a36Sopenharmony_ci    if args.verbose > 0:
19262306a36Sopenharmony_ci        print('command "{}"'.format(command))
19362306a36Sopenharmony_ci    proc = subprocess.Popen(command,
19462306a36Sopenharmony_ci        shell=True,
19562306a36Sopenharmony_ci        stdout=subprocess.PIPE,
19662306a36Sopenharmony_ci        stderr=subprocess.PIPE,
19762306a36Sopenharmony_ci        env=ENVIR)
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci    try:
20062306a36Sopenharmony_ci        (rawout, serr) = proc.communicate(timeout=NAMES['TIMEOUT'])
20162306a36Sopenharmony_ci        if proc.returncode != 0 and len(serr) > 0:
20262306a36Sopenharmony_ci            foutput = serr.decode("utf-8", errors="ignore")
20362306a36Sopenharmony_ci        else:
20462306a36Sopenharmony_ci            foutput = rawout.decode("utf-8", errors="ignore")
20562306a36Sopenharmony_ci    except subprocess.TimeoutExpired:
20662306a36Sopenharmony_ci        foutput = "Command \"{}\" timed out\n".format(command)
20762306a36Sopenharmony_ci        proc.returncode = 255
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci    proc.stdout.close()
21062306a36Sopenharmony_ci    proc.stderr.close()
21162306a36Sopenharmony_ci    return proc, foutput
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cidef prepare_env(args, pm, stage, prefix, cmdlist, output = None):
21562306a36Sopenharmony_ci    """
21662306a36Sopenharmony_ci    Execute the setup/teardown commands for a test case.
21762306a36Sopenharmony_ci    Optionally terminate test execution if the command fails.
21862306a36Sopenharmony_ci    """
21962306a36Sopenharmony_ci    if args.verbose > 0:
22062306a36Sopenharmony_ci        print('{}'.format(prefix))
22162306a36Sopenharmony_ci    for cmdinfo in cmdlist:
22262306a36Sopenharmony_ci        if isinstance(cmdinfo, list):
22362306a36Sopenharmony_ci            exit_codes = cmdinfo[1:]
22462306a36Sopenharmony_ci            cmd = cmdinfo[0]
22562306a36Sopenharmony_ci        else:
22662306a36Sopenharmony_ci            exit_codes = [0]
22762306a36Sopenharmony_ci            cmd = cmdinfo
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci        if not cmd:
23062306a36Sopenharmony_ci            continue
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci        (proc, foutput) = exec_cmd(args, pm, stage, cmd)
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci        if proc and (proc.returncode not in exit_codes):
23562306a36Sopenharmony_ci            print('', file=sys.stderr)
23662306a36Sopenharmony_ci            print("{} *** Could not execute: \"{}\"".format(prefix, cmd),
23762306a36Sopenharmony_ci                  file=sys.stderr)
23862306a36Sopenharmony_ci            print("\n{} *** Error message: \"{}\"".format(prefix, foutput),
23962306a36Sopenharmony_ci                  file=sys.stderr)
24062306a36Sopenharmony_ci            print("returncode {}; expected {}".format(proc.returncode,
24162306a36Sopenharmony_ci                                                      exit_codes))
24262306a36Sopenharmony_ci            print("\n{} *** Aborting test run.".format(prefix), file=sys.stderr)
24362306a36Sopenharmony_ci            print("\n\n{} *** stdout ***".format(proc.stdout), file=sys.stderr)
24462306a36Sopenharmony_ci            print("\n\n{} *** stderr ***".format(proc.stderr), file=sys.stderr)
24562306a36Sopenharmony_ci            raise PluginMgrTestFail(
24662306a36Sopenharmony_ci                stage, output,
24762306a36Sopenharmony_ci                '"{}" did not complete successfully'.format(prefix))
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cidef verify_by_json(procout, res, tidx, args, pm):
25062306a36Sopenharmony_ci    try:
25162306a36Sopenharmony_ci        outputJSON = json.loads(procout)
25262306a36Sopenharmony_ci    except json.JSONDecodeError:
25362306a36Sopenharmony_ci        res.set_result(ResultState.fail)
25462306a36Sopenharmony_ci        res.set_failmsg('Cannot decode verify command\'s output. Is it JSON?')
25562306a36Sopenharmony_ci        return res
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci    matchJSON = json.loads(json.dumps(tidx['matchJSON']))
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci    if type(outputJSON) != type(matchJSON):
26062306a36Sopenharmony_ci        failmsg = 'Original output and matchJSON value are not the same type: output: {} != matchJSON: {} '
26162306a36Sopenharmony_ci        failmsg = failmsg.format(type(outputJSON).__name__, type(matchJSON).__name__)
26262306a36Sopenharmony_ci        res.set_result(ResultState.fail)
26362306a36Sopenharmony_ci        res.set_failmsg(failmsg)
26462306a36Sopenharmony_ci        return res
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci    if len(matchJSON) > len(outputJSON):
26762306a36Sopenharmony_ci        failmsg = "Your matchJSON value is an array, and it contains more elements than the command under test\'s output:\ncommand output (length: {}):\n{}\nmatchJSON value (length: {}):\n{}"
26862306a36Sopenharmony_ci        failmsg = failmsg.format(len(outputJSON), outputJSON, len(matchJSON), matchJSON)
26962306a36Sopenharmony_ci        res.set_result(ResultState.fail)
27062306a36Sopenharmony_ci        res.set_failmsg(failmsg)
27162306a36Sopenharmony_ci        return res
27262306a36Sopenharmony_ci    res = find_in_json(res, outputJSON, matchJSON, 0)
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci    return res
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cidef find_in_json(res, outputJSONVal, matchJSONVal, matchJSONKey=None):
27762306a36Sopenharmony_ci    if res.get_result() == ResultState.fail:
27862306a36Sopenharmony_ci        return res
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci    if type(matchJSONVal) == list:
28162306a36Sopenharmony_ci        res = find_in_json_list(res, outputJSONVal, matchJSONVal, matchJSONKey)
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci    elif type(matchJSONVal) == dict:
28462306a36Sopenharmony_ci        res = find_in_json_dict(res, outputJSONVal, matchJSONVal)
28562306a36Sopenharmony_ci    else:
28662306a36Sopenharmony_ci        res = find_in_json_other(res, outputJSONVal, matchJSONVal, matchJSONKey)
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci    if res.get_result() != ResultState.fail:
28962306a36Sopenharmony_ci        res.set_result(ResultState.success)
29062306a36Sopenharmony_ci        return res
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci    return res
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cidef find_in_json_list(res, outputJSONVal, matchJSONVal, matchJSONKey=None):
29562306a36Sopenharmony_ci    if (type(matchJSONVal) != type(outputJSONVal)):
29662306a36Sopenharmony_ci        failmsg = 'Original output and matchJSON value are not the same type: output: {} != matchJSON: {}'
29762306a36Sopenharmony_ci        failmsg = failmsg.format(outputJSONVal, matchJSONVal)
29862306a36Sopenharmony_ci        res.set_result(ResultState.fail)
29962306a36Sopenharmony_ci        res.set_failmsg(failmsg)
30062306a36Sopenharmony_ci        return res
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci    if len(matchJSONVal) > len(outputJSONVal):
30362306a36Sopenharmony_ci        failmsg = "Your matchJSON value is an array, and it contains more elements than the command under test\'s output:\ncommand output (length: {}):\n{}\nmatchJSON value (length: {}):\n{}"
30462306a36Sopenharmony_ci        failmsg = failmsg.format(len(outputJSONVal), outputJSONVal, len(matchJSONVal), matchJSONVal)
30562306a36Sopenharmony_ci        res.set_result(ResultState.fail)
30662306a36Sopenharmony_ci        res.set_failmsg(failmsg)
30762306a36Sopenharmony_ci        return res
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci    for matchJSONIdx, matchJSONVal in enumerate(matchJSONVal):
31062306a36Sopenharmony_ci        res = find_in_json(res, outputJSONVal[matchJSONIdx], matchJSONVal,
31162306a36Sopenharmony_ci                           matchJSONKey)
31262306a36Sopenharmony_ci    return res
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_cidef find_in_json_dict(res, outputJSONVal, matchJSONVal):
31562306a36Sopenharmony_ci    for matchJSONKey, matchJSONVal in matchJSONVal.items():
31662306a36Sopenharmony_ci        if type(outputJSONVal) == dict:
31762306a36Sopenharmony_ci            if matchJSONKey not in outputJSONVal:
31862306a36Sopenharmony_ci                failmsg = 'Key not found in json output: {}: {}\nMatching against output: {}'
31962306a36Sopenharmony_ci                failmsg = failmsg.format(matchJSONKey, matchJSONVal, outputJSONVal)
32062306a36Sopenharmony_ci                res.set_result(ResultState.fail)
32162306a36Sopenharmony_ci                res.set_failmsg(failmsg)
32262306a36Sopenharmony_ci                return res
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci        else:
32562306a36Sopenharmony_ci            failmsg = 'Original output and matchJSON value are not the same type: output: {} != matchJSON: {}'
32662306a36Sopenharmony_ci            failmsg = failmsg.format(type(outputJSON).__name__, type(matchJSON).__name__)
32762306a36Sopenharmony_ci            res.set_result(ResultState.fail)
32862306a36Sopenharmony_ci            res.set_failmsg(failmsg)
32962306a36Sopenharmony_ci            return rest
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci        if type(outputJSONVal) == dict and (type(outputJSONVal[matchJSONKey]) == dict or
33262306a36Sopenharmony_ci                type(outputJSONVal[matchJSONKey]) == list):
33362306a36Sopenharmony_ci            if len(matchJSONVal) > 0:
33462306a36Sopenharmony_ci                res = find_in_json(res, outputJSONVal[matchJSONKey], matchJSONVal, matchJSONKey)
33562306a36Sopenharmony_ci            # handling corner case where matchJSONVal == [] or matchJSONVal == {}
33662306a36Sopenharmony_ci            else:
33762306a36Sopenharmony_ci                res = find_in_json_other(res, outputJSONVal, matchJSONVal, matchJSONKey)
33862306a36Sopenharmony_ci        else:
33962306a36Sopenharmony_ci            res = find_in_json(res, outputJSONVal, matchJSONVal, matchJSONKey)
34062306a36Sopenharmony_ci    return res
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_cidef find_in_json_other(res, outputJSONVal, matchJSONVal, matchJSONKey=None):
34362306a36Sopenharmony_ci    if matchJSONKey in outputJSONVal:
34462306a36Sopenharmony_ci        if matchJSONVal != outputJSONVal[matchJSONKey]:
34562306a36Sopenharmony_ci            failmsg = 'Value doesn\'t match: {}: {} != {}\nMatching against output: {}'
34662306a36Sopenharmony_ci            failmsg = failmsg.format(matchJSONKey, matchJSONVal, outputJSONVal[matchJSONKey], outputJSONVal)
34762306a36Sopenharmony_ci            res.set_result(ResultState.fail)
34862306a36Sopenharmony_ci            res.set_failmsg(failmsg)
34962306a36Sopenharmony_ci            return res
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci    return res
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_cidef run_one_test(pm, args, index, tidx):
35462306a36Sopenharmony_ci    global NAMES
35562306a36Sopenharmony_ci    result = True
35662306a36Sopenharmony_ci    tresult = ""
35762306a36Sopenharmony_ci    tap = ""
35862306a36Sopenharmony_ci    res = TestResult(tidx['id'], tidx['name'])
35962306a36Sopenharmony_ci    if args.verbose > 0:
36062306a36Sopenharmony_ci        print("\t====================\n=====> ", end="")
36162306a36Sopenharmony_ci    print("Test " + tidx["id"] + ": " + tidx["name"])
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci    if 'skip' in tidx:
36462306a36Sopenharmony_ci        if tidx['skip'] == 'yes':
36562306a36Sopenharmony_ci            res = TestResult(tidx['id'], tidx['name'])
36662306a36Sopenharmony_ci            res.set_result(ResultState.skip)
36762306a36Sopenharmony_ci            res.set_errormsg('Test case designated as skipped.')
36862306a36Sopenharmony_ci            pm.call_pre_case(tidx, test_skip=True)
36962306a36Sopenharmony_ci            pm.call_post_execute()
37062306a36Sopenharmony_ci            return res
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci    if 'dependsOn' in tidx:
37362306a36Sopenharmony_ci        if (args.verbose > 0):
37462306a36Sopenharmony_ci            print('probe command for test skip')
37562306a36Sopenharmony_ci        (p, procout) = exec_cmd(args, pm, 'execute', tidx['dependsOn'])
37662306a36Sopenharmony_ci        if p:
37762306a36Sopenharmony_ci            if (p.returncode != 0):
37862306a36Sopenharmony_ci                res = TestResult(tidx['id'], tidx['name'])
37962306a36Sopenharmony_ci                res.set_result(ResultState.skip)
38062306a36Sopenharmony_ci                res.set_errormsg('probe command: test skipped.')
38162306a36Sopenharmony_ci                pm.call_pre_case(tidx, test_skip=True)
38262306a36Sopenharmony_ci                pm.call_post_execute()
38362306a36Sopenharmony_ci                return res
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci    # populate NAMES with TESTID for this test
38662306a36Sopenharmony_ci    NAMES['TESTID'] = tidx['id']
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_ci    pm.call_pre_case(tidx)
38962306a36Sopenharmony_ci    prepare_env(args, pm, 'setup', "-----> prepare stage", tidx["setup"])
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci    if (args.verbose > 0):
39262306a36Sopenharmony_ci        print('-----> execute stage')
39362306a36Sopenharmony_ci    pm.call_pre_execute()
39462306a36Sopenharmony_ci    (p, procout) = exec_cmd(args, pm, 'execute', tidx["cmdUnderTest"])
39562306a36Sopenharmony_ci    if p:
39662306a36Sopenharmony_ci        exit_code = p.returncode
39762306a36Sopenharmony_ci    else:
39862306a36Sopenharmony_ci        exit_code = None
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci    pm.call_post_execute()
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci    if (exit_code is None or exit_code != int(tidx["expExitCode"])):
40362306a36Sopenharmony_ci        print("exit: {!r}".format(exit_code))
40462306a36Sopenharmony_ci        print("exit: {}".format(int(tidx["expExitCode"])))
40562306a36Sopenharmony_ci        #print("exit: {!r} {}".format(exit_code, int(tidx["expExitCode"])))
40662306a36Sopenharmony_ci        res.set_result(ResultState.fail)
40762306a36Sopenharmony_ci        res.set_failmsg('Command exited with {}, expected {}\n{}'.format(exit_code, tidx["expExitCode"], procout))
40862306a36Sopenharmony_ci        print(procout)
40962306a36Sopenharmony_ci    else:
41062306a36Sopenharmony_ci        if args.verbose > 0:
41162306a36Sopenharmony_ci            print('-----> verify stage')
41262306a36Sopenharmony_ci        (p, procout) = exec_cmd(args, pm, 'verify', tidx["verifyCmd"])
41362306a36Sopenharmony_ci        if procout:
41462306a36Sopenharmony_ci            if 'matchJSON' in tidx:
41562306a36Sopenharmony_ci                verify_by_json(procout, res, tidx, args, pm)
41662306a36Sopenharmony_ci            elif 'matchPattern' in tidx:
41762306a36Sopenharmony_ci                match_pattern = re.compile(
41862306a36Sopenharmony_ci                    str(tidx["matchPattern"]), re.DOTALL | re.MULTILINE)
41962306a36Sopenharmony_ci                match_index = re.findall(match_pattern, procout)
42062306a36Sopenharmony_ci                if len(match_index) != int(tidx["matchCount"]):
42162306a36Sopenharmony_ci                    res.set_result(ResultState.fail)
42262306a36Sopenharmony_ci                    res.set_failmsg('Could not match regex pattern. Verify command output:\n{}'.format(procout))
42362306a36Sopenharmony_ci                else:
42462306a36Sopenharmony_ci                    res.set_result(ResultState.success)
42562306a36Sopenharmony_ci            else:
42662306a36Sopenharmony_ci                res.set_result(ResultState.fail)
42762306a36Sopenharmony_ci                res.set_failmsg('Must specify a match option: matchJSON or matchPattern\n{}'.format(procout))
42862306a36Sopenharmony_ci        elif int(tidx["matchCount"]) != 0:
42962306a36Sopenharmony_ci            res.set_result(ResultState.fail)
43062306a36Sopenharmony_ci            res.set_failmsg('No output generated by verify command.')
43162306a36Sopenharmony_ci        else:
43262306a36Sopenharmony_ci            res.set_result(ResultState.success)
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci    prepare_env(args, pm, 'teardown', '-----> teardown stage', tidx['teardown'], procout)
43562306a36Sopenharmony_ci    pm.call_post_case()
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci    index += 1
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci    # remove TESTID from NAMES
44062306a36Sopenharmony_ci    del(NAMES['TESTID'])
44162306a36Sopenharmony_ci    return res
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_cidef test_runner(pm, args, filtered_tests):
44462306a36Sopenharmony_ci    """
44562306a36Sopenharmony_ci    Driver function for the unit tests.
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci    Prints information about the tests being run, executes the setup and
44862306a36Sopenharmony_ci    teardown commands and the command under test itself. Also determines
44962306a36Sopenharmony_ci    success/failure based on the information in the test case and generates
45062306a36Sopenharmony_ci    TAP output accordingly.
45162306a36Sopenharmony_ci    """
45262306a36Sopenharmony_ci    testlist = filtered_tests
45362306a36Sopenharmony_ci    tcount = len(testlist)
45462306a36Sopenharmony_ci    index = 1
45562306a36Sopenharmony_ci    tap = ''
45662306a36Sopenharmony_ci    badtest = None
45762306a36Sopenharmony_ci    stage = None
45862306a36Sopenharmony_ci    emergency_exit = False
45962306a36Sopenharmony_ci    emergency_exit_message = ''
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci    tsr = TestSuiteReport()
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci    try:
46462306a36Sopenharmony_ci        pm.call_pre_suite(tcount, [tidx['id'] for tidx in testlist])
46562306a36Sopenharmony_ci    except Exception as ee:
46662306a36Sopenharmony_ci        ex_type, ex, ex_tb = sys.exc_info()
46762306a36Sopenharmony_ci        print('Exception {} {} (caught in pre_suite).'.
46862306a36Sopenharmony_ci              format(ex_type, ex))
46962306a36Sopenharmony_ci        traceback.print_tb(ex_tb)
47062306a36Sopenharmony_ci        emergency_exit_message = 'EMERGENCY EXIT, call_pre_suite failed with exception {} {}\n'.format(ex_type, ex)
47162306a36Sopenharmony_ci        emergency_exit = True
47262306a36Sopenharmony_ci        stage = 'pre-SUITE'
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci    if emergency_exit:
47562306a36Sopenharmony_ci        pm.call_post_suite(index)
47662306a36Sopenharmony_ci        return emergency_exit_message
47762306a36Sopenharmony_ci    if args.verbose > 1:
47862306a36Sopenharmony_ci        print('give test rig 2 seconds to stabilize')
47962306a36Sopenharmony_ci    time.sleep(2)
48062306a36Sopenharmony_ci    for tidx in testlist:
48162306a36Sopenharmony_ci        if "flower" in tidx["category"] and args.device == None:
48262306a36Sopenharmony_ci            errmsg = "Tests using the DEV2 variable must define the name of a "
48362306a36Sopenharmony_ci            errmsg += "physical NIC with the -d option when running tdc.\n"
48462306a36Sopenharmony_ci            errmsg += "Test has been skipped."
48562306a36Sopenharmony_ci            if args.verbose > 1:
48662306a36Sopenharmony_ci                print(errmsg)
48762306a36Sopenharmony_ci            res = TestResult(tidx['id'], tidx['name'])
48862306a36Sopenharmony_ci            res.set_result(ResultState.skip)
48962306a36Sopenharmony_ci            res.set_errormsg(errmsg)
49062306a36Sopenharmony_ci            tsr.add_resultdata(res)
49162306a36Sopenharmony_ci            index += 1
49262306a36Sopenharmony_ci            continue
49362306a36Sopenharmony_ci        try:
49462306a36Sopenharmony_ci            badtest = tidx  # in case it goes bad
49562306a36Sopenharmony_ci            res = run_one_test(pm, args, index, tidx)
49662306a36Sopenharmony_ci            tsr.add_resultdata(res)
49762306a36Sopenharmony_ci        except PluginMgrTestFail as pmtf:
49862306a36Sopenharmony_ci            ex_type, ex, ex_tb = sys.exc_info()
49962306a36Sopenharmony_ci            stage = pmtf.stage
50062306a36Sopenharmony_ci            message = pmtf.message
50162306a36Sopenharmony_ci            output = pmtf.output
50262306a36Sopenharmony_ci            res = TestResult(tidx['id'], tidx['name'])
50362306a36Sopenharmony_ci            res.set_result(ResultState.skip)
50462306a36Sopenharmony_ci            res.set_errormsg(pmtf.message)
50562306a36Sopenharmony_ci            res.set_failmsg(pmtf.output)
50662306a36Sopenharmony_ci            tsr.add_resultdata(res)
50762306a36Sopenharmony_ci            index += 1
50862306a36Sopenharmony_ci            print(message)
50962306a36Sopenharmony_ci            print('Exception {} {} (caught in test_runner, running test {} {} {} stage {})'.
51062306a36Sopenharmony_ci                  format(ex_type, ex, index, tidx['id'], tidx['name'], stage))
51162306a36Sopenharmony_ci            print('---------------')
51262306a36Sopenharmony_ci            print('traceback')
51362306a36Sopenharmony_ci            traceback.print_tb(ex_tb)
51462306a36Sopenharmony_ci            print('---------------')
51562306a36Sopenharmony_ci            if stage == 'teardown':
51662306a36Sopenharmony_ci                print('accumulated output for this test:')
51762306a36Sopenharmony_ci                if pmtf.output:
51862306a36Sopenharmony_ci                    print(pmtf.output)
51962306a36Sopenharmony_ci            print('---------------')
52062306a36Sopenharmony_ci            break
52162306a36Sopenharmony_ci        index += 1
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci    # if we failed in setup or teardown,
52462306a36Sopenharmony_ci    # fill in the remaining tests with ok-skipped
52562306a36Sopenharmony_ci    count = index
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci    if tcount + 1 != count:
52862306a36Sopenharmony_ci        for tidx in testlist[count - 1:]:
52962306a36Sopenharmony_ci            res = TestResult(tidx['id'], tidx['name'])
53062306a36Sopenharmony_ci            res.set_result(ResultState.skip)
53162306a36Sopenharmony_ci            msg = 'skipped - previous {} failed {} {}'.format(stage,
53262306a36Sopenharmony_ci                index, badtest.get('id', '--Unknown--'))
53362306a36Sopenharmony_ci            res.set_errormsg(msg)
53462306a36Sopenharmony_ci            tsr.add_resultdata(res)
53562306a36Sopenharmony_ci            count += 1
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci    if args.pause:
53862306a36Sopenharmony_ci        print('Want to pause\nPress enter to continue ...')
53962306a36Sopenharmony_ci        if input(sys.stdin):
54062306a36Sopenharmony_ci            print('got something on stdin')
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_ci    pm.call_post_suite(index)
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci    return tsr
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_cidef has_blank_ids(idlist):
54762306a36Sopenharmony_ci    """
54862306a36Sopenharmony_ci    Search the list for empty ID fields and return true/false accordingly.
54962306a36Sopenharmony_ci    """
55062306a36Sopenharmony_ci    return not(all(k for k in idlist))
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_cidef load_from_file(filename):
55462306a36Sopenharmony_ci    """
55562306a36Sopenharmony_ci    Open the JSON file containing the test cases and return them
55662306a36Sopenharmony_ci    as list of ordered dictionary objects.
55762306a36Sopenharmony_ci    """
55862306a36Sopenharmony_ci    try:
55962306a36Sopenharmony_ci        with open(filename) as test_data:
56062306a36Sopenharmony_ci            testlist = json.load(test_data, object_pairs_hook=OrderedDict)
56162306a36Sopenharmony_ci    except json.JSONDecodeError as jde:
56262306a36Sopenharmony_ci        print('IGNORING test case file {}\n\tBECAUSE:  {}'.format(filename, jde))
56362306a36Sopenharmony_ci        testlist = list()
56462306a36Sopenharmony_ci    else:
56562306a36Sopenharmony_ci        idlist = get_id_list(testlist)
56662306a36Sopenharmony_ci        if (has_blank_ids(idlist)):
56762306a36Sopenharmony_ci            for k in testlist:
56862306a36Sopenharmony_ci                k['filename'] = filename
56962306a36Sopenharmony_ci    return testlist
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_cidef args_parse():
57362306a36Sopenharmony_ci    """
57462306a36Sopenharmony_ci    Create the argument parser.
57562306a36Sopenharmony_ci    """
57662306a36Sopenharmony_ci    parser = argparse.ArgumentParser(description='Linux TC unit tests')
57762306a36Sopenharmony_ci    return parser
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cidef set_args(parser):
58162306a36Sopenharmony_ci    """
58262306a36Sopenharmony_ci    Set the command line arguments for tdc.
58362306a36Sopenharmony_ci    """
58462306a36Sopenharmony_ci    parser.add_argument(
58562306a36Sopenharmony_ci        '--outfile', type=str,
58662306a36Sopenharmony_ci        help='Path to the file in which results should be saved. ' +
58762306a36Sopenharmony_ci        'Default target is the current directory.')
58862306a36Sopenharmony_ci    parser.add_argument(
58962306a36Sopenharmony_ci        '-p', '--path', type=str,
59062306a36Sopenharmony_ci        help='The full path to the tc executable to use')
59162306a36Sopenharmony_ci    sg = parser.add_argument_group(
59262306a36Sopenharmony_ci        'selection', 'select which test cases: ' +
59362306a36Sopenharmony_ci        'files plus directories; filtered by categories plus testids')
59462306a36Sopenharmony_ci    ag = parser.add_argument_group(
59562306a36Sopenharmony_ci        'action', 'select action to perform on selected test cases')
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci    sg.add_argument(
59862306a36Sopenharmony_ci        '-D', '--directory', nargs='+', metavar='DIR',
59962306a36Sopenharmony_ci        help='Collect tests from the specified directory(ies) ' +
60062306a36Sopenharmony_ci        '(default [tc-tests])')
60162306a36Sopenharmony_ci    sg.add_argument(
60262306a36Sopenharmony_ci        '-f', '--file', nargs='+', metavar='FILE',
60362306a36Sopenharmony_ci        help='Run tests from the specified file(s)')
60462306a36Sopenharmony_ci    sg.add_argument(
60562306a36Sopenharmony_ci        '-c', '--category', nargs='*', metavar='CATG', default=['+c'],
60662306a36Sopenharmony_ci        help='Run tests only from the specified category/ies, ' +
60762306a36Sopenharmony_ci        'or if no category/ies is/are specified, list known categories.')
60862306a36Sopenharmony_ci    sg.add_argument(
60962306a36Sopenharmony_ci        '-e', '--execute', nargs='+', metavar='ID',
61062306a36Sopenharmony_ci        help='Execute the specified test cases with specified IDs')
61162306a36Sopenharmony_ci    ag.add_argument(
61262306a36Sopenharmony_ci        '-l', '--list', action='store_true',
61362306a36Sopenharmony_ci        help='List all test cases, or those only within the specified category')
61462306a36Sopenharmony_ci    ag.add_argument(
61562306a36Sopenharmony_ci        '-s', '--show', action='store_true', dest='showID',
61662306a36Sopenharmony_ci        help='Display the selected test cases')
61762306a36Sopenharmony_ci    ag.add_argument(
61862306a36Sopenharmony_ci        '-i', '--id', action='store_true', dest='gen_id',
61962306a36Sopenharmony_ci        help='Generate ID numbers for new test cases')
62062306a36Sopenharmony_ci    parser.add_argument(
62162306a36Sopenharmony_ci        '-v', '--verbose', action='count', default=0,
62262306a36Sopenharmony_ci        help='Show the commands that are being run')
62362306a36Sopenharmony_ci    parser.add_argument(
62462306a36Sopenharmony_ci        '--format', default='tap', const='tap', nargs='?',
62562306a36Sopenharmony_ci        choices=['none', 'xunit', 'tap'],
62662306a36Sopenharmony_ci        help='Specify the format for test results. (Default: TAP)')
62762306a36Sopenharmony_ci    parser.add_argument('-d', '--device',
62862306a36Sopenharmony_ci                        help='Execute test cases that use a physical device, ' +
62962306a36Sopenharmony_ci                        'where DEVICE is its name. (If not defined, tests ' +
63062306a36Sopenharmony_ci                        'that require a physical device will be skipped)')
63162306a36Sopenharmony_ci    parser.add_argument(
63262306a36Sopenharmony_ci        '-P', '--pause', action='store_true',
63362306a36Sopenharmony_ci        help='Pause execution just before post-suite stage')
63462306a36Sopenharmony_ci    return parser
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_cidef check_default_settings(args, remaining, pm):
63862306a36Sopenharmony_ci    """
63962306a36Sopenharmony_ci    Process any arguments overriding the default settings,
64062306a36Sopenharmony_ci    and ensure the settings are correct.
64162306a36Sopenharmony_ci    """
64262306a36Sopenharmony_ci    # Allow for overriding specific settings
64362306a36Sopenharmony_ci    global NAMES
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci    if args.path != None:
64662306a36Sopenharmony_ci        NAMES['TC'] = args.path
64762306a36Sopenharmony_ci    if args.device != None:
64862306a36Sopenharmony_ci        NAMES['DEV2'] = args.device
64962306a36Sopenharmony_ci    if 'TIMEOUT' not in NAMES:
65062306a36Sopenharmony_ci        NAMES['TIMEOUT'] = None
65162306a36Sopenharmony_ci    if not os.path.isfile(NAMES['TC']):
65262306a36Sopenharmony_ci        print("The specified tc path " + NAMES['TC'] + " does not exist.")
65362306a36Sopenharmony_ci        exit(1)
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci    pm.call_check_args(args, remaining)
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_cidef get_id_list(alltests):
65962306a36Sopenharmony_ci    """
66062306a36Sopenharmony_ci    Generate a list of all IDs in the test cases.
66162306a36Sopenharmony_ci    """
66262306a36Sopenharmony_ci    return [x["id"] for x in alltests]
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_cidef check_case_id(alltests):
66662306a36Sopenharmony_ci    """
66762306a36Sopenharmony_ci    Check for duplicate test case IDs.
66862306a36Sopenharmony_ci    """
66962306a36Sopenharmony_ci    idl = get_id_list(alltests)
67062306a36Sopenharmony_ci    return [x for x in idl if idl.count(x) > 1]
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_cidef does_id_exist(alltests, newid):
67462306a36Sopenharmony_ci    """
67562306a36Sopenharmony_ci    Check if a given ID already exists in the list of test cases.
67662306a36Sopenharmony_ci    """
67762306a36Sopenharmony_ci    idl = get_id_list(alltests)
67862306a36Sopenharmony_ci    return (any(newid == x for x in idl))
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_cidef generate_case_ids(alltests):
68262306a36Sopenharmony_ci    """
68362306a36Sopenharmony_ci    If a test case has a blank ID field, generate a random hex ID for it
68462306a36Sopenharmony_ci    and then write the test cases back to disk.
68562306a36Sopenharmony_ci    """
68662306a36Sopenharmony_ci    import random
68762306a36Sopenharmony_ci    for c in alltests:
68862306a36Sopenharmony_ci        if (c["id"] == ""):
68962306a36Sopenharmony_ci            while True:
69062306a36Sopenharmony_ci                newid = str('{:04x}'.format(random.randrange(16**4)))
69162306a36Sopenharmony_ci                if (does_id_exist(alltests, newid)):
69262306a36Sopenharmony_ci                    continue
69362306a36Sopenharmony_ci                else:
69462306a36Sopenharmony_ci                    c['id'] = newid
69562306a36Sopenharmony_ci                    break
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci    ufilename = []
69862306a36Sopenharmony_ci    for c in alltests:
69962306a36Sopenharmony_ci        if ('filename' in c):
70062306a36Sopenharmony_ci            ufilename.append(c['filename'])
70162306a36Sopenharmony_ci    ufilename = get_unique_item(ufilename)
70262306a36Sopenharmony_ci    for f in ufilename:
70362306a36Sopenharmony_ci        testlist = []
70462306a36Sopenharmony_ci        for t in alltests:
70562306a36Sopenharmony_ci            if 'filename' in t:
70662306a36Sopenharmony_ci                if t['filename'] == f:
70762306a36Sopenharmony_ci                    del t['filename']
70862306a36Sopenharmony_ci                    testlist.append(t)
70962306a36Sopenharmony_ci        outfile = open(f, "w")
71062306a36Sopenharmony_ci        json.dump(testlist, outfile, indent=4)
71162306a36Sopenharmony_ci        outfile.write("\n")
71262306a36Sopenharmony_ci        outfile.close()
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_cidef filter_tests_by_id(args, testlist):
71562306a36Sopenharmony_ci    '''
71662306a36Sopenharmony_ci    Remove tests from testlist that are not in the named id list.
71762306a36Sopenharmony_ci    If id list is empty, return empty list.
71862306a36Sopenharmony_ci    '''
71962306a36Sopenharmony_ci    newlist = list()
72062306a36Sopenharmony_ci    if testlist and args.execute:
72162306a36Sopenharmony_ci        target_ids = args.execute
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci        if isinstance(target_ids, list) and (len(target_ids) > 0):
72462306a36Sopenharmony_ci            newlist = list(filter(lambda x: x['id'] in target_ids, testlist))
72562306a36Sopenharmony_ci    return newlist
72662306a36Sopenharmony_ci
72762306a36Sopenharmony_cidef filter_tests_by_category(args, testlist):
72862306a36Sopenharmony_ci    '''
72962306a36Sopenharmony_ci    Remove tests from testlist that are not in a named category.
73062306a36Sopenharmony_ci    '''
73162306a36Sopenharmony_ci    answer = list()
73262306a36Sopenharmony_ci    if args.category and testlist:
73362306a36Sopenharmony_ci        test_ids = list()
73462306a36Sopenharmony_ci        for catg in set(args.category):
73562306a36Sopenharmony_ci            if catg == '+c':
73662306a36Sopenharmony_ci                continue
73762306a36Sopenharmony_ci            print('considering category {}'.format(catg))
73862306a36Sopenharmony_ci            for tc in testlist:
73962306a36Sopenharmony_ci                if catg in tc['category'] and tc['id'] not in test_ids:
74062306a36Sopenharmony_ci                    answer.append(tc)
74162306a36Sopenharmony_ci                    test_ids.append(tc['id'])
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci    return answer
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_cidef get_test_cases(args):
74762306a36Sopenharmony_ci    """
74862306a36Sopenharmony_ci    If a test case file is specified, retrieve tests from that file.
74962306a36Sopenharmony_ci    Otherwise, glob for all json files in subdirectories and load from
75062306a36Sopenharmony_ci    each one.
75162306a36Sopenharmony_ci    Also, if requested, filter by category, and add tests matching
75262306a36Sopenharmony_ci    certain ids.
75362306a36Sopenharmony_ci    """
75462306a36Sopenharmony_ci    import fnmatch
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci    flist = []
75762306a36Sopenharmony_ci    testdirs = ['tc-tests']
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci    if args.file:
76062306a36Sopenharmony_ci        # at least one file was specified - remove the default directory
76162306a36Sopenharmony_ci        testdirs = []
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci        for ff in args.file:
76462306a36Sopenharmony_ci            if not os.path.isfile(ff):
76562306a36Sopenharmony_ci                print("IGNORING file " + ff + "\n\tBECAUSE does not exist.")
76662306a36Sopenharmony_ci            else:
76762306a36Sopenharmony_ci                flist.append(os.path.abspath(ff))
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci    if args.directory:
77062306a36Sopenharmony_ci        testdirs = args.directory
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci    for testdir in testdirs:
77362306a36Sopenharmony_ci        for root, dirnames, filenames in os.walk(testdir):
77462306a36Sopenharmony_ci            for filename in fnmatch.filter(filenames, '*.json'):
77562306a36Sopenharmony_ci                candidate = os.path.abspath(os.path.join(root, filename))
77662306a36Sopenharmony_ci                if candidate not in testdirs:
77762306a36Sopenharmony_ci                    flist.append(candidate)
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci    alltestcases = list()
78062306a36Sopenharmony_ci    for casefile in flist:
78162306a36Sopenharmony_ci        alltestcases = alltestcases + (load_from_file(casefile))
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci    allcatlist = get_test_categories(alltestcases)
78462306a36Sopenharmony_ci    allidlist = get_id_list(alltestcases)
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_ci    testcases_by_cats = get_categorized_testlist(alltestcases, allcatlist)
78762306a36Sopenharmony_ci    idtestcases = filter_tests_by_id(args, alltestcases)
78862306a36Sopenharmony_ci    cattestcases = filter_tests_by_category(args, alltestcases)
78962306a36Sopenharmony_ci
79062306a36Sopenharmony_ci    cat_ids = [x['id'] for x in cattestcases]
79162306a36Sopenharmony_ci    if args.execute:
79262306a36Sopenharmony_ci        if args.category:
79362306a36Sopenharmony_ci            alltestcases = cattestcases + [x for x in idtestcases if x['id'] not in cat_ids]
79462306a36Sopenharmony_ci        else:
79562306a36Sopenharmony_ci            alltestcases = idtestcases
79662306a36Sopenharmony_ci    else:
79762306a36Sopenharmony_ci        if cat_ids:
79862306a36Sopenharmony_ci            alltestcases = cattestcases
79962306a36Sopenharmony_ci        else:
80062306a36Sopenharmony_ci            # just accept the existing value of alltestcases,
80162306a36Sopenharmony_ci            # which has been filtered by file/directory
80262306a36Sopenharmony_ci            pass
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci    return allcatlist, allidlist, testcases_by_cats, alltestcases
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_cidef set_operation_mode(pm, parser, args, remaining):
80862306a36Sopenharmony_ci    """
80962306a36Sopenharmony_ci    Load the test case data and process remaining arguments to determine
81062306a36Sopenharmony_ci    what the script should do for this run, and call the appropriate
81162306a36Sopenharmony_ci    function.
81262306a36Sopenharmony_ci    """
81362306a36Sopenharmony_ci    ucat, idlist, testcases, alltests = get_test_cases(args)
81462306a36Sopenharmony_ci
81562306a36Sopenharmony_ci    if args.gen_id:
81662306a36Sopenharmony_ci        if (has_blank_ids(idlist)):
81762306a36Sopenharmony_ci            alltests = generate_case_ids(alltests)
81862306a36Sopenharmony_ci        else:
81962306a36Sopenharmony_ci            print("No empty ID fields found in test files.")
82062306a36Sopenharmony_ci        exit(0)
82162306a36Sopenharmony_ci
82262306a36Sopenharmony_ci    duplicate_ids = check_case_id(alltests)
82362306a36Sopenharmony_ci    if (len(duplicate_ids) > 0):
82462306a36Sopenharmony_ci        print("The following test case IDs are not unique:")
82562306a36Sopenharmony_ci        print(str(set(duplicate_ids)))
82662306a36Sopenharmony_ci        print("Please correct them before continuing.")
82762306a36Sopenharmony_ci        exit(1)
82862306a36Sopenharmony_ci
82962306a36Sopenharmony_ci    if args.showID:
83062306a36Sopenharmony_ci        for atest in alltests:
83162306a36Sopenharmony_ci            print_test_case(atest)
83262306a36Sopenharmony_ci        exit(0)
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci    if isinstance(args.category, list) and (len(args.category) == 0):
83562306a36Sopenharmony_ci        print("Available categories:")
83662306a36Sopenharmony_ci        print_sll(ucat)
83762306a36Sopenharmony_ci        exit(0)
83862306a36Sopenharmony_ci
83962306a36Sopenharmony_ci    if args.list:
84062306a36Sopenharmony_ci        list_test_cases(alltests)
84162306a36Sopenharmony_ci        exit(0)
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci    exit_code = 0 # KSFT_PASS
84462306a36Sopenharmony_ci    if len(alltests):
84562306a36Sopenharmony_ci        req_plugins = pm.get_required_plugins(alltests)
84662306a36Sopenharmony_ci        try:
84762306a36Sopenharmony_ci            args = pm.load_required_plugins(req_plugins, parser, args, remaining)
84862306a36Sopenharmony_ci        except PluginDependencyException as pde:
84962306a36Sopenharmony_ci            print('The following plugins were not found:')
85062306a36Sopenharmony_ci            print('{}'.format(pde.missing_pg))
85162306a36Sopenharmony_ci        catresults = test_runner(pm, args, alltests)
85262306a36Sopenharmony_ci        if catresults.count_failures() != 0:
85362306a36Sopenharmony_ci            exit_code = 1 # KSFT_FAIL
85462306a36Sopenharmony_ci        if args.format == 'none':
85562306a36Sopenharmony_ci            print('Test results output suppression requested\n')
85662306a36Sopenharmony_ci        else:
85762306a36Sopenharmony_ci            print('\nAll test results: \n')
85862306a36Sopenharmony_ci            if args.format == 'xunit':
85962306a36Sopenharmony_ci                suffix = 'xml'
86062306a36Sopenharmony_ci                res = catresults.format_xunit()
86162306a36Sopenharmony_ci            elif args.format == 'tap':
86262306a36Sopenharmony_ci                suffix = 'tap'
86362306a36Sopenharmony_ci                res = catresults.format_tap()
86462306a36Sopenharmony_ci            print(res)
86562306a36Sopenharmony_ci            print('\n\n')
86662306a36Sopenharmony_ci            if not args.outfile:
86762306a36Sopenharmony_ci                fname = 'test-results.{}'.format(suffix)
86862306a36Sopenharmony_ci            else:
86962306a36Sopenharmony_ci                fname = args.outfile
87062306a36Sopenharmony_ci            with open(fname, 'w') as fh:
87162306a36Sopenharmony_ci                fh.write(res)
87262306a36Sopenharmony_ci                fh.close()
87362306a36Sopenharmony_ci                if os.getenv('SUDO_UID') is not None:
87462306a36Sopenharmony_ci                    os.chown(fname, uid=int(os.getenv('SUDO_UID')),
87562306a36Sopenharmony_ci                        gid=int(os.getenv('SUDO_GID')))
87662306a36Sopenharmony_ci    else:
87762306a36Sopenharmony_ci        print('No tests found\n')
87862306a36Sopenharmony_ci        exit_code = 4 # KSFT_SKIP
87962306a36Sopenharmony_ci    exit(exit_code)
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_cidef main():
88262306a36Sopenharmony_ci    """
88362306a36Sopenharmony_ci    Start of execution; set up argument parser and get the arguments,
88462306a36Sopenharmony_ci    and start operations.
88562306a36Sopenharmony_ci    """
88662306a36Sopenharmony_ci    parser = args_parse()
88762306a36Sopenharmony_ci    parser = set_args(parser)
88862306a36Sopenharmony_ci    pm = PluginMgr(parser)
88962306a36Sopenharmony_ci    parser = pm.call_add_args(parser)
89062306a36Sopenharmony_ci    (args, remaining) = parser.parse_known_args()
89162306a36Sopenharmony_ci    args.NAMES = NAMES
89262306a36Sopenharmony_ci    pm.set_args(args)
89362306a36Sopenharmony_ci    check_default_settings(args, remaining, pm)
89462306a36Sopenharmony_ci    if args.verbose > 2:
89562306a36Sopenharmony_ci        print('args is {}'.format(args))
89662306a36Sopenharmony_ci
89762306a36Sopenharmony_ci    set_operation_mode(pm, parser, args, remaining)
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ciif __name__ == "__main__":
90062306a36Sopenharmony_ci    main()
901