Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/tc-testing: localize test resources

As of today, the current tdc architecture creates one netns and uses it
to run all tests. This assumption was embedded into the nsPlugin which
carried over as how the tests were written.

The tdc tests are by definition self contained and can,
theoretically, run in parallel. Even though in the kernel they will
serialize over the rtnl lock, we should expect a significant speedup of the
total wall time for the entire test suite, which is hitting close to
1100 tests at this point.

A first step to achieve this goal is to remove sharing of global resources like
veth/dummy interfaces and the netns. In this patch we 'localize' these
resources on a per test basis. Each test gets it's own netns, VETH/dummy interfaces.
The resources are spawned in the pre_suite phase, where tdc will prepare
all netns and interfaces for all tests. This is done in order to avoid
concurrency issues with netns / interfaces spawning and commands using
them. As tdc progresses, the resources are deleted after each test finishes
executing.

Tests that don't use the nsPlugin still run under the root namespace,
but are now required to manage any external resources like interfaces.
These cannot be parallelized as their definition doesn't allow it.
On the other hand, when using the nsPlugin, tests don't need to create
dummy/veth interfaces as these are handled already.

Tested-by: Davide Caratti <dcaratti@redhat.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

authored by

Pedro Tammela and committed by
Paolo Abeni
98cfbe42 d387e34f

+229 -98
+2 -2
tools/testing/selftests/tc-testing/TdcPlugin.py
··· 5 5 super().__init__() 6 6 print(' -- {}.__init__'.format(self.sub_class)) 7 7 8 - def pre_suite(self, testcount, testidlist): 8 + def pre_suite(self, testcount, testlist): 9 9 '''run commands before test_runner goes into a test loop''' 10 10 self.testcount = testcount 11 - self.testidlist = testidlist 11 + self.testlist = testlist 12 12 if self.args.verbose > 1: 13 13 print(' -- {}.pre_suite'.format(self.sub_class)) 14 14
+134 -51
tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py
··· 3 3 from string import Template 4 4 import subprocess 5 5 import time 6 + from functools import cached_property 6 7 from TdcPlugin import TdcPlugin 7 8 8 9 from tdc_config import * ··· 13 12 self.sub_class = 'ns/SubPlugin' 14 13 super().__init__() 15 14 16 - def pre_suite(self, testcount, testidlist): 17 - '''run commands before test_runner goes into a test loop''' 18 - super().pre_suite(testcount, testidlist) 15 + def pre_suite(self, testcount, testlist): 16 + super().pre_suite(testcount, testlist) 19 17 20 - if self.args.namespace: 21 - self._ns_create() 22 - else: 23 - self._ports_create() 18 + print("Setting up namespaces and devices...") 24 19 25 - def post_suite(self, index): 26 - '''run commands after test_runner goes into a test loop''' 27 - super().post_suite(index) 20 + original = self.args.NAMES 21 + 22 + for t in testlist: 23 + if 'skip' in t and t['skip'] == 'yes': 24 + continue 25 + 26 + if 'nsPlugin' not in t['plugins']: 27 + continue 28 + 29 + shadow = {} 30 + shadow['IP'] = original['IP'] 31 + shadow['TC'] = original['TC'] 32 + shadow['NS'] = '{}-{}'.format(original['NS'], t['random']) 33 + shadow['DEV0'] = '{}id{}'.format(original['DEV0'], t['id']) 34 + shadow['DEV1'] = '{}id{}'.format(original['DEV1'], t['id']) 35 + shadow['DUMMY'] = '{}id{}'.format(original['DUMMY'], t['id']) 36 + shadow['DEV2'] = original['DEV2'] 37 + self.args.NAMES = shadow 38 + 39 + if self.args.namespace: 40 + self._ns_create() 41 + else: 42 + self._ports_create() 43 + 44 + self.args.NAMES = original 45 + 46 + def pre_case(self, caseinfo, test_skip): 28 47 if self.args.verbose: 29 - print('{}.post_suite'.format(self.sub_class)) 48 + print('{}.pre_case'.format(self.sub_class)) 49 + 50 + if test_skip: 51 + return 52 + 53 + # Make sure the netns is visible in the fs 54 + while True: 55 + self._proc_check() 56 + try: 57 + ns = self.args.NAMES['NS'] 58 + f = open('/run/netns/{}'.format(ns)) 59 + f.close() 60 + break 61 + except: 62 + continue 63 + 64 + def post_case(self): 65 + if self.args.verbose: 66 + print('{}.post_case'.format(self.sub_class)) 30 67 31 68 if self.args.namespace: 32 69 self._ns_destroy() 33 70 else: 34 71 self._ports_destroy() 72 + 73 + def post_suite(self, index): 74 + if self.args.verbose: 75 + print('{}.post_suite'.format(self.sub_class)) 76 + 77 + # Make sure we don't leak resources 78 + for f in os.listdir('/run/netns/'): 79 + cmd = self._replace_keywords("$IP netns del {}".format(f)) 80 + 81 + if self.args.verbose > 3: 82 + print('_exec_cmd: command "{}"'.format(cmd)) 83 + 84 + subprocess.run(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 35 85 36 86 def add_args(self, parser): 37 87 super().add_args(parser) ··· 129 77 print('adjust_command: return command [{}]'.format(command)) 130 78 return command 131 79 132 - def _ports_create(self): 133 - cmd = '$IP link add $DEV0 type veth peer name $DEV1' 134 - self._exec_cmd('pre', cmd) 135 - cmd = '$IP link set $DEV0 up' 136 - self._exec_cmd('pre', cmd) 80 + def _ports_create_cmds(self): 81 + cmds = [] 82 + 83 + cmds.append(self._replace_keywords('link add $DEV0 type veth peer name $DEV1')) 84 + cmds.append(self._replace_keywords('link set $DEV0 up')) 85 + cmds.append(self._replace_keywords('link add $DUMMY type dummy')) 137 86 if not self.args.namespace: 138 - cmd = '$IP link set $DEV1 up' 139 - self._exec_cmd('pre', cmd) 87 + cmds.append(self._replace_keywords('link set $DEV1 up')) 88 + 89 + return cmds 90 + 91 + def _ports_create(self): 92 + self._exec_cmd_batched('pre', self._ports_create_cmds()) 93 + 94 + def _ports_destroy_cmd(self): 95 + return self._replace_keywords('link del $DEV0') 140 96 141 97 def _ports_destroy(self): 142 - cmd = '$IP link del $DEV0' 143 - self._exec_cmd('post', cmd) 98 + self._exec_cmd('post', self._ports_destroy_cmd()) 99 + 100 + def _ns_create_cmds(self): 101 + cmds = [] 102 + 103 + if self.args.namespace: 104 + ns = self.args.NAMES['NS'] 105 + 106 + cmds.append(self._replace_keywords('netns add {}'.format(ns))) 107 + cmds.append(self._replace_keywords('link set $DEV1 netns {}'.format(ns))) 108 + cmds.append(self._replace_keywords('link set $DUMMY netns {}'.format(ns))) 109 + cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV1 up'.format(ns))) 110 + cmds.append(self._replace_keywords('netns exec {} $IP link set $DUMMY up'.format(ns))) 111 + 112 + if self.args.device: 113 + cmds.append(self._replace_keywords('link set $DEV2 netns {}'.format(ns))) 114 + cmds.append(self._replace_keywords('netns exec {} $IP link set $DEV2 up'.format(ns))) 115 + 116 + return cmds 144 117 145 118 def _ns_create(self): 146 119 ''' ··· 173 96 the required network devices for it. 174 97 ''' 175 98 self._ports_create() 176 - if self.args.namespace: 177 - cmd = '$IP netns add {}'.format(self.args.NAMES['NS']) 178 - self._exec_cmd('pre', cmd) 179 - cmd = '$IP link set $DEV1 netns {}'.format(self.args.NAMES['NS']) 180 - self._exec_cmd('pre', cmd) 181 - cmd = '$IP -n {} link set $DEV1 up'.format(self.args.NAMES['NS']) 182 - self._exec_cmd('pre', cmd) 183 - if self.args.device: 184 - cmd = '$IP link set $DEV2 netns {}'.format(self.args.NAMES['NS']) 185 - self._exec_cmd('pre', cmd) 186 - cmd = '$IP -n {} link set $DEV2 up'.format(self.args.NAMES['NS']) 187 - self._exec_cmd('pre', cmd) 99 + self._exec_cmd_batched('pre', self._ns_create_cmds()) 100 + 101 + def _ns_destroy_cmd(self): 102 + return self._replace_keywords('netns delete {}'.format(self.args.NAMES['NS'])) 188 103 189 104 def _ns_destroy(self): 190 105 ''' ··· 184 115 devices as well) 185 116 ''' 186 117 if self.args.namespace: 187 - cmd = '$IP netns delete {}'.format(self.args.NAMES['NS']) 188 - self._exec_cmd('post', cmd) 118 + self._exec_cmd('post', self._ns_destroy_cmd()) 119 + self._ports_destroy() 120 + 121 + @cached_property 122 + def _proc(self): 123 + ip = self._replace_keywords("$IP -b -") 124 + proc = subprocess.Popen(ip, 125 + shell=True, 126 + stdin=subprocess.PIPE, 127 + env=ENVIR) 128 + 129 + return proc 130 + 131 + def _proc_check(self): 132 + proc = self._proc 133 + 134 + proc.poll() 135 + 136 + if proc.returncode is not None and proc.returncode != 0: 137 + raise RuntimeError("iproute2 exited with an error code") 189 138 190 139 def _exec_cmd(self, stage, command): 191 140 ''' 192 141 Perform any required modifications on an executable command, then run 193 142 it in a subprocess and return the results. 194 143 ''' 195 - if '$' in command: 196 - command = self._replace_keywords(command) 197 144 198 - self.adjust_command(stage, command) 199 - if self.args.verbose: 145 + if self.args.verbose > 3: 200 146 print('_exec_cmd: command "{}"'.format(command)) 201 - proc = subprocess.Popen(command, 202 - shell=True, 203 - stdout=subprocess.PIPE, 204 - stderr=subprocess.PIPE, 205 - env=ENVIR) 206 - (rawout, serr) = proc.communicate() 207 147 208 - if proc.returncode != 0 and len(serr) > 0: 209 - foutput = serr.decode("utf-8") 210 - else: 211 - foutput = rawout.decode("utf-8") 148 + proc = self._proc 212 149 213 - proc.stdout.close() 214 - proc.stderr.close() 215 - return proc, foutput 150 + proc.stdin.write((command + '\n').encode()) 151 + proc.stdin.flush() 152 + 153 + if self.args.verbose > 3: 154 + print('_exec_cmd proc: {}'.format(proc)) 155 + 156 + self._proc_check() 157 + 158 + def _exec_cmd_batched(self, stage, commands): 159 + for cmd in commands: 160 + self._exec_cmd(stage, cmd) 216 161 217 162 def _replace_keywords(self, cmd): 218 163 """
+2 -2
tools/testing/selftests/tc-testing/plugin-lib/rootPlugin.py
··· 10 10 self.sub_class = 'root/SubPlugin' 11 11 super().__init__() 12 12 13 - def pre_suite(self, testcount, testidlist): 13 + def pre_suite(self, testcount, testlist): 14 14 # run commands before test_runner goes into a test loop 15 - super().pre_suite(testcount, testidlist) 15 + super().pre_suite(testcount, testlist) 16 16 17 17 if os.geteuid(): 18 18 print('This script must be run with root privileges', file=sys.stderr)
+3 -2
tools/testing/selftests/tc-testing/plugin-lib/valgrindPlugin.py
··· 25 25 self._tsr = TestSuiteReport() 26 26 super().__init__() 27 27 28 - def pre_suite(self, testcount, testidlist): 28 + def pre_suite(self, testcount, testist): 29 29 '''run commands before test_runner goes into a test loop''' 30 - super().pre_suite(testcount, testidlist) 30 + self.testidlist = [tidx['id'] for tidx in testlist] 31 + super().pre_suite(testcount, testlist) 31 32 if self.args.verbose > 1: 32 33 print('{}.pre_suite'.format(self.sub_class)) 33 34 if self.args.valgrind:
+88 -41
tools/testing/selftests/tc-testing/tdc.py
··· 16 16 import subprocess 17 17 import time 18 18 import traceback 19 + import random 19 20 from collections import OrderedDict 20 21 from string import Template 21 22 ··· 39 38 class PluginMgr: 40 39 def __init__(self, argparser): 41 40 super().__init__() 42 - self.plugins = {} 41 + self.plugins = set() 43 42 self.plugin_instances = [] 44 43 self.failed_plugins = {} 45 44 self.argparser = argparser 46 45 47 - # TODO, put plugins in order 48 46 plugindir = os.getenv('TDC_PLUGIN_DIR', './plugins') 49 47 for dirpath, dirnames, filenames in os.walk(plugindir): 50 48 for fn in filenames: ··· 53 53 not fn.startswith('.#')): 54 54 mn = fn[0:-3] 55 55 foo = importlib.import_module('plugins.' + mn) 56 - self.plugins[mn] = foo 57 - self.plugin_instances.append(foo.SubPlugin()) 56 + self.plugins.add(mn) 57 + self.plugin_instances[mn] = foo.SubPlugin() 58 58 59 59 def load_plugin(self, pgdir, pgname): 60 60 pgname = pgname[0:-3] 61 + self.plugins.add(pgname) 62 + 61 63 foo = importlib.import_module('{}.{}'.format(pgdir, pgname)) 62 - self.plugins[pgname] = foo 63 - self.plugin_instances.append(foo.SubPlugin()) 64 - self.plugin_instances[-1].check_args(self.args, None) 64 + 65 + # nsPlugin must always be the first one 66 + if pgname == "nsPlugin": 67 + self.plugin_instances.insert(0, (pgname, foo.SubPlugin())) 68 + self.plugin_instances[0][1].check_args(self.args, None) 69 + else: 70 + self.plugin_instances.append((pgname, foo.SubPlugin())) 71 + self.plugin_instances[-1][1].check_args(self.args, None) 65 72 66 73 def get_required_plugins(self, testlist): 67 74 ''' 68 75 Get all required plugins from the list of test cases and return 69 76 all unique items. 70 77 ''' 71 - reqs = [] 78 + reqs = set() 72 79 for t in testlist: 73 80 try: 74 81 if 'requires' in t['plugins']: 75 82 if isinstance(t['plugins']['requires'], list): 76 - reqs.extend(t['plugins']['requires']) 83 + reqs.update(set(t['plugins']['requires'])) 77 84 else: 78 - reqs.append(t['plugins']['requires']) 85 + reqs.add(t['plugins']['requires']) 86 + t['plugins'] = t['plugins']['requires'] 87 + else: 88 + t['plugins'] = [] 79 89 except KeyError: 90 + t['plugins'] = [] 80 91 continue 81 - reqs = get_unique_item(reqs) 92 + 82 93 return reqs 83 94 84 95 def load_required_plugins(self, reqs, parser, args, remaining): ··· 126 115 return args 127 116 128 117 def call_pre_suite(self, testcount, testidlist): 129 - for pgn_inst in self.plugin_instances: 118 + for (_, pgn_inst) in self.plugin_instances: 130 119 pgn_inst.pre_suite(testcount, testidlist) 131 120 132 121 def call_post_suite(self, index): 133 - for pgn_inst in reversed(self.plugin_instances): 122 + for (_, pgn_inst) in reversed(self.plugin_instances): 134 123 pgn_inst.post_suite(index) 135 124 136 125 def call_pre_case(self, caseinfo, *, test_skip=False): 137 - for pgn_inst in self.plugin_instances: 126 + for (pgn, pgn_inst) in self.plugin_instances: 127 + if pgn not in caseinfo['plugins']: 128 + continue 138 129 try: 139 130 pgn_inst.pre_case(caseinfo, test_skip) 140 131 except Exception as ee: ··· 146 133 print('testid is {}'.format(caseinfo['id'])) 147 134 raise 148 135 149 - def call_post_case(self): 150 - for pgn_inst in reversed(self.plugin_instances): 136 + def call_post_case(self, caseinfo): 137 + for (pgn, pgn_inst) in reversed(self.plugin_instances): 138 + if pgn not in caseinfo['plugins']: 139 + continue 151 140 pgn_inst.post_case() 152 141 153 - def call_pre_execute(self): 154 - for pgn_inst in self.plugin_instances: 142 + def call_pre_execute(self, caseinfo): 143 + for (pgn, pgn_inst) in self.plugin_instances: 144 + if pgn not in caseinfo['plugins']: 145 + continue 155 146 pgn_inst.pre_execute() 156 147 157 - def call_post_execute(self): 158 - for pgn_inst in reversed(self.plugin_instances): 148 + def call_post_execute(self, caseinfo): 149 + for (pgn, pgn_inst) in reversed(self.plugin_instances): 150 + if pgn not in caseinfo['plugins']: 151 + continue 159 152 pgn_inst.post_execute() 160 153 161 154 def call_add_args(self, parser): 162 - for pgn_inst in self.plugin_instances: 155 + for (pgn, pgn_inst) in self.plugin_instances: 163 156 parser = pgn_inst.add_args(parser) 164 157 return parser 165 158 166 159 def call_check_args(self, args, remaining): 167 - for pgn_inst in self.plugin_instances: 160 + for (pgn, pgn_inst) in self.plugin_instances: 168 161 pgn_inst.check_args(args, remaining) 169 162 170 - def call_adjust_command(self, stage, command): 171 - for pgn_inst in self.plugin_instances: 163 + def call_adjust_command(self, caseinfo, stage, command): 164 + for (pgn, pgn_inst) in self.plugin_instances: 165 + if pgn not in caseinfo['plugins']: 166 + continue 172 167 command = pgn_inst.adjust_command(stage, command) 173 168 return command 174 169 ··· 198 177 return subcmd 199 178 200 179 201 - def exec_cmd(args, pm, stage, command): 180 + def exec_cmd(caseinfo, args, pm, stage, command): 202 181 """ 203 182 Perform any required modifications on an executable command, then run 204 183 it in a subprocess and return the results. ··· 208 187 if '$' in command: 209 188 command = replace_keywords(command) 210 189 211 - command = pm.call_adjust_command(stage, command) 190 + command = pm.call_adjust_command(caseinfo, stage, command) 212 191 if args.verbose > 0: 213 192 print('command "{}"'.format(command)) 193 + 214 194 proc = subprocess.Popen(command, 215 195 shell=True, 216 196 stdout=subprocess.PIPE, ··· 233 211 return proc, foutput 234 212 235 213 236 - def prepare_env(args, pm, stage, prefix, cmdlist, output = None): 214 + def prepare_env(caseinfo, args, pm, stage, prefix, cmdlist, output = None): 237 215 """ 238 216 Execute the setup/teardown commands for a test case. 239 217 Optionally terminate test execution if the command fails. ··· 251 229 if not cmd: 252 230 continue 253 231 254 - (proc, foutput) = exec_cmd(args, pm, stage, cmd) 232 + (proc, foutput) = exec_cmd(caseinfo, args, pm, stage, cmd) 255 233 256 234 if proc and (proc.returncode not in exit_codes): 257 235 print('', file=sys.stderr) ··· 374 352 375 353 def run_one_test(pm, args, index, tidx): 376 354 global NAMES 355 + ns = NAMES['NS'] 356 + dev0 = NAMES['DEV0'] 357 + dev1 = NAMES['DEV1'] 358 + dummy = NAMES['DUMMY'] 377 359 result = True 378 360 tresult = "" 379 361 tap = "" ··· 392 366 res.set_result(ResultState.skip) 393 367 res.set_errormsg('Test case designated as skipped.') 394 368 pm.call_pre_case(tidx, test_skip=True) 395 - pm.call_post_execute() 369 + pm.call_post_execute(tidx) 396 370 return res 397 371 398 372 if 'dependsOn' in tidx: 399 373 if (args.verbose > 0): 400 374 print('probe command for test skip') 401 - (p, procout) = exec_cmd(args, pm, 'execute', tidx['dependsOn']) 375 + (p, procout) = exec_cmd(tidx, args, pm, 'execute', tidx['dependsOn']) 402 376 if p: 403 377 if (p.returncode != 0): 404 378 res = TestResult(tidx['id'], tidx['name']) 405 379 res.set_result(ResultState.skip) 406 380 res.set_errormsg('probe command: test skipped.') 407 381 pm.call_pre_case(tidx, test_skip=True) 408 - pm.call_post_execute() 382 + pm.call_post_execute(tidx) 409 383 return res 410 384 411 385 # populate NAMES with TESTID for this test 412 386 NAMES['TESTID'] = tidx['id'] 387 + NAMES['NS'] = '{}-{}'.format(NAMES['NS'], tidx['random']) 388 + NAMES['DEV0'] = '{}id{}'.format(NAMES['DEV0'], tidx['id']) 389 + NAMES['DEV1'] = '{}id{}'.format(NAMES['DEV1'], tidx['id']) 390 + NAMES['DUMMY'] = '{}id{}'.format(NAMES['DUMMY'], tidx['id']) 413 391 414 392 pm.call_pre_case(tidx) 415 - prepare_env(args, pm, 'setup', "-----> prepare stage", tidx["setup"]) 393 + prepare_env(tidx, args, pm, 'setup', "-----> prepare stage", tidx["setup"]) 416 394 417 395 if (args.verbose > 0): 418 396 print('-----> execute stage') 419 - pm.call_pre_execute() 420 - (p, procout) = exec_cmd(args, pm, 'execute', tidx["cmdUnderTest"]) 397 + pm.call_pre_execute(tidx) 398 + (p, procout) = exec_cmd(tidx, args, pm, 'execute', tidx["cmdUnderTest"]) 421 399 if p: 422 400 exit_code = p.returncode 423 401 else: 424 402 exit_code = None 425 403 426 - pm.call_post_execute() 404 + pm.call_post_execute(tidx) 427 405 428 406 if (exit_code is None or exit_code != int(tidx["expExitCode"])): 429 407 print("exit: {!r}".format(exit_code)) ··· 439 409 else: 440 410 if args.verbose > 0: 441 411 print('-----> verify stage') 442 - (p, procout) = exec_cmd(args, pm, 'verify', tidx["verifyCmd"]) 412 + (p, procout) = exec_cmd(tidx, args, pm, 'verify', tidx["verifyCmd"]) 443 413 if procout: 444 414 if 'matchJSON' in tidx: 445 415 verify_by_json(procout, res, tidx, args, pm) ··· 461 431 else: 462 432 res.set_result(ResultState.success) 463 433 464 - prepare_env(args, pm, 'teardown', '-----> teardown stage', tidx['teardown'], procout) 465 - pm.call_post_case() 434 + prepare_env(tidx, args, pm, 'teardown', '-----> teardown stage', tidx['teardown'], procout) 435 + pm.call_post_case(tidx) 466 436 467 437 index += 1 468 438 469 439 # remove TESTID from NAMES 470 440 del(NAMES['TESTID']) 441 + 442 + # Restore names 443 + NAMES['NS'] = ns 444 + NAMES['DEV0'] = dev0 445 + NAMES['DEV1'] = dev1 446 + NAMES['DUMMY'] = dummy 447 + 471 448 return res 472 449 473 450 def test_runner(pm, args, filtered_tests): ··· 498 461 tsr = TestSuiteReport() 499 462 500 463 try: 501 - pm.call_pre_suite(tcount, [tidx['id'] for tidx in testlist]) 464 + pm.call_pre_suite(tcount, testlist) 502 465 except Exception as ee: 503 466 ex_type, ex, ex_tb = sys.exc_info() 504 467 print('Exception {} {} (caught in pre_suite).'. ··· 698 661 """ 699 662 return [x["id"] for x in alltests] 700 663 701 - 702 664 def check_case_id(alltests): 703 665 """ 704 666 Check for duplicate test case IDs. ··· 719 683 If a test case has a blank ID field, generate a random hex ID for it 720 684 and then write the test cases back to disk. 721 685 """ 722 - import random 723 686 for c in alltests: 724 687 if (c["id"] == ""): 725 688 while True: ··· 777 742 778 743 return answer 779 744 745 + def set_random(alltests): 746 + for tidx in alltests: 747 + tidx['random'] = random.getrandbits(32) 780 748 781 749 def get_test_cases(args): 782 750 """ ··· 878 840 list_test_cases(alltests) 879 841 exit(0) 880 842 843 + set_random(alltests) 844 + 881 845 exit_code = 0 # KSFT_PASS 882 846 if len(alltests): 883 847 req_plugins = pm.get_required_plugins(alltests) ··· 923 883 Start of execution; set up argument parser and get the arguments, 924 884 and start operations. 925 885 """ 886 + import resource 887 + 888 + if sys.version_info.major < 3 or sys.version_info.minor < 8: 889 + sys.exit("tdc requires at least python 3.8") 890 + 891 + resource.setrlimit(resource.RLIMIT_NOFILE, (1048576, 1048576)) 892 + 926 893 parser = args_parse() 927 894 parser = set_args(parser) 928 895 pm = PluginMgr(parser)