diff options
Diffstat (limited to '')
4 files changed, 329 insertions, 0 deletions
diff --git a/tools/testing/selftests/tc-testing/plugin-lib/README-PLUGINS b/tools/testing/selftests/tc-testing/plugin-lib/README-PLUGINS new file mode 100644 index 000000000..aa8a26697 --- /dev/null +++ b/tools/testing/selftests/tc-testing/plugin-lib/README-PLUGINS @@ -0,0 +1,27 @@ +tdc.py will look for plugins in a directory plugins off the cwd. +Make a set of numbered symbolic links from there to the actual plugins. +Eg: + +tdc.py +plugin-lib/ +plugins/ + __init__.py + 10-rootPlugin.py -> ../plugin-lib/rootPlugin.py + 20-valgrindPlugin.py -> ../plugin-lib/valgrindPlugin.py + 30-nsPlugin.py -> ../plugin-lib/nsPlugin.py + + +tdc.py will find them and use them. + + +rootPlugin + Check if the uid is root. If not, bail out. + +valgrindPlugin + Run the command under test with valgrind, and produce an extra set of TAP results for the memory tests. + This plugin will write files to the cwd, called vgnd-xxx.log. These will contain + the valgrind output for test xxx. Any file matching the glob 'vgnd-*.log' will be + deleted at the end of the run. + +nsPlugin + Run all the commands in a network namespace. diff --git a/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py b/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py new file mode 100644 index 000000000..a194b1af2 --- /dev/null +++ b/tools/testing/selftests/tc-testing/plugin-lib/nsPlugin.py @@ -0,0 +1,141 @@ +import os +import signal +from string import Template +import subprocess +import time +from TdcPlugin import TdcPlugin + +from tdc_config import * + +class SubPlugin(TdcPlugin): + def __init__(self): + self.sub_class = 'ns/SubPlugin' + super().__init__() + + def pre_suite(self, testcount, testidlist): + '''run commands before test_runner goes into a test loop''' + super().pre_suite(testcount, testidlist) + + if self.args.namespace: + self._ns_create() + + def post_suite(self, index): + '''run commands after test_runner goes into a test loop''' + super().post_suite(index) + if self.args.verbose: + print('{}.post_suite'.format(self.sub_class)) + + if self.args.namespace: + self._ns_destroy() + + def add_args(self, parser): + super().add_args(parser) + self.argparser_group = self.argparser.add_argument_group( + 'netns', + 'options for nsPlugin(run commands in net namespace)') + self.argparser_group.add_argument( + '-n', '--namespace', action='store_true', + help='Run commands in namespace') + return self.argparser + + def adjust_command(self, stage, command): + super().adjust_command(stage, command) + cmdform = 'list' + cmdlist = list() + + if not self.args.namespace: + return command + + if self.args.verbose: + print('{}.adjust_command'.format(self.sub_class)) + + if not isinstance(command, list): + cmdform = 'str' + cmdlist = command.split() + else: + cmdlist = command + if stage == 'setup' or stage == 'execute' or stage == 'verify' or stage == 'teardown': + if self.args.verbose: + print('adjust_command: stage is {}; inserting netns stuff in command [{}] list [{}]'.format(stage, command, cmdlist)) + cmdlist.insert(0, self.args.NAMES['NS']) + cmdlist.insert(0, 'exec') + cmdlist.insert(0, 'netns') + cmdlist.insert(0, 'ip') + else: + pass + + if cmdform == 'str': + command = ' '.join(cmdlist) + else: + command = cmdlist + + if self.args.verbose: + print('adjust_command: return command [{}]'.format(command)) + return command + + def _ns_create(self): + ''' + Create the network namespace in which the tests will be run and set up + the required network devices for it. + ''' + if self.args.namespace: + cmd = 'ip netns add {}'.format(self.args.NAMES['NS']) + self._exec_cmd('pre', cmd) + cmd = 'ip link add $DEV0 type veth peer name $DEV1' + self._exec_cmd('pre', cmd) + cmd = 'ip link set $DEV1 netns {}'.format(self.args.NAMES['NS']) + self._exec_cmd('pre', cmd) + cmd = 'ip link set $DEV0 up' + self._exec_cmd('pre', cmd) + cmd = 'ip -n {} link set $DEV1 up'.format(self.args.NAMES['NS']) + self._exec_cmd('pre', cmd) + if self.args.device: + cmd = 'ip link set $DEV2 netns {}'.format(self.args.NAMES['NS']) + self._exec_cmd('pre', cmd) + cmd = 'ip -n {} link set $DEV2 up'.format(self.args.NAMES['NS']) + self._exec_cmd('pre', cmd) + + def _ns_destroy(self): + ''' + Destroy the network namespace for testing (and any associated network + devices as well) + ''' + if self.args.namespace: + cmd = 'ip netns delete {}'.format(self.args.NAMES['NS']) + self._exec_cmd('post', cmd) + + def _exec_cmd(self, stage, command): + ''' + Perform any required modifications on an executable command, then run + it in a subprocess and return the results. + ''' + if '$' in command: + command = self._replace_keywords(command) + + self.adjust_command(stage, command) + if self.args.verbose: + print('_exec_cmd: command "{}"'.format(command)) + proc = subprocess.Popen(command, + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=ENVIR) + (rawout, serr) = proc.communicate() + + if proc.returncode != 0 and len(serr) > 0: + foutput = serr.decode("utf-8") + else: + foutput = rawout.decode("utf-8") + + proc.stdout.close() + proc.stderr.close() + return proc, foutput + + def _replace_keywords(self, cmd): + """ + For a given executable command, substitute any known + variables contained within NAMES with the correct values + """ + tcmd = Template(cmd) + subcmd = tcmd.safe_substitute(self.args.NAMES) + return subcmd diff --git a/tools/testing/selftests/tc-testing/plugin-lib/rootPlugin.py b/tools/testing/selftests/tc-testing/plugin-lib/rootPlugin.py new file mode 100644 index 000000000..e36775bd4 --- /dev/null +++ b/tools/testing/selftests/tc-testing/plugin-lib/rootPlugin.py @@ -0,0 +1,19 @@ +import os +import sys +from TdcPlugin import TdcPlugin + +from tdc_config import * + + +class SubPlugin(TdcPlugin): + def __init__(self): + self.sub_class = 'root/SubPlugin' + super().__init__() + + def pre_suite(self, testcount, testidlist): + # run commands before test_runner goes into a test loop + super().pre_suite(testcount, testidlist) + + if os.geteuid(): + print('This script must be run with root privileges', file=sys.stderr) + exit(1) diff --git a/tools/testing/selftests/tc-testing/plugin-lib/valgrindPlugin.py b/tools/testing/selftests/tc-testing/plugin-lib/valgrindPlugin.py new file mode 100644 index 000000000..477a7bd7d --- /dev/null +++ b/tools/testing/selftests/tc-testing/plugin-lib/valgrindPlugin.py @@ -0,0 +1,142 @@ +''' +run the command under test, under valgrind and collect memory leak info +as a separate test. +''' + + +import os +import re +import signal +from string import Template +import subprocess +import time +from TdcPlugin import TdcPlugin + +from tdc_config import * + +def vp_extract_num_from_string(num_as_string_maybe_with_commas): + return int(num_as_string_maybe_with_commas.replace(',','')) + +class SubPlugin(TdcPlugin): + def __init__(self): + self.sub_class = 'valgrind/SubPlugin' + self.tap = '' + super().__init__() + + def pre_suite(self, testcount, testidlist): + '''run commands before test_runner goes into a test loop''' + super().pre_suite(testcount, testidlist) + if self.args.verbose > 1: + print('{}.pre_suite'.format(self.sub_class)) + if self.args.valgrind: + self._add_to_tap('1..{}\n'.format(self.testcount)) + + def post_suite(self, index): + '''run commands after test_runner goes into a test loop''' + super().post_suite(index) + self._add_to_tap('\n|---\n') + if self.args.verbose > 1: + print('{}.post_suite'.format(self.sub_class)) + print('{}'.format(self.tap)) + if self.args.verbose < 4: + subprocess.check_output('rm -f vgnd-*.log', shell=True) + + def add_args(self, parser): + super().add_args(parser) + self.argparser_group = self.argparser.add_argument_group( + 'valgrind', + 'options for valgrindPlugin (run command under test under Valgrind)') + + self.argparser_group.add_argument( + '-V', '--valgrind', action='store_true', + help='Run commands under valgrind') + + return self.argparser + + def adjust_command(self, stage, command): + super().adjust_command(stage, command) + cmdform = 'list' + cmdlist = list() + + if not self.args.valgrind: + return command + + if self.args.verbose > 1: + print('{}.adjust_command'.format(self.sub_class)) + + if not isinstance(command, list): + cmdform = 'str' + cmdlist = command.split() + else: + cmdlist = command + + if stage == 'execute': + if self.args.verbose > 1: + print('adjust_command: stage is {}; inserting valgrind stuff in command [{}] list [{}]'. + format(stage, command, cmdlist)) + cmdlist.insert(0, '--track-origins=yes') + cmdlist.insert(0, '--show-leak-kinds=definite,indirect') + cmdlist.insert(0, '--leak-check=full') + cmdlist.insert(0, '--log-file=vgnd-{}.log'.format(self.args.testid)) + cmdlist.insert(0, '-v') # ask for summary of non-leak errors + cmdlist.insert(0, ENVIR['VALGRIND_BIN']) + else: + pass + + if cmdform == 'str': + command = ' '.join(cmdlist) + else: + command = cmdlist + + if self.args.verbose > 1: + print('adjust_command: return command [{}]'.format(command)) + return command + + def post_execute(self): + if not self.args.valgrind: + return + + self.definitely_lost_re = re.compile( + r'definitely lost:\s+([,0-9]+)\s+bytes in\s+([,0-9]+)\sblocks', re.MULTILINE | re.DOTALL) + self.indirectly_lost_re = re.compile( + r'indirectly lost:\s+([,0-9]+)\s+bytes in\s+([,0-9]+)\s+blocks', re.MULTILINE | re.DOTALL) + self.possibly_lost_re = re.compile( + r'possibly lost:\s+([,0-9]+)bytes in\s+([,0-9]+)\s+blocks', re.MULTILINE | re.DOTALL) + self.non_leak_error_re = re.compile( + r'ERROR SUMMARY:\s+([,0-9]+) errors from\s+([,0-9]+)\s+contexts', re.MULTILINE | re.DOTALL) + + def_num = 0 + ind_num = 0 + pos_num = 0 + nle_num = 0 + + # what about concurrent test runs? Maybe force them to be in different directories? + with open('vgnd-{}.log'.format(self.args.testid)) as vfd: + content = vfd.read() + def_mo = self.definitely_lost_re.search(content) + ind_mo = self.indirectly_lost_re.search(content) + pos_mo = self.possibly_lost_re.search(content) + nle_mo = self.non_leak_error_re.search(content) + + if def_mo: + def_num = int(def_mo.group(2)) + if ind_mo: + ind_num = int(ind_mo.group(2)) + if pos_mo: + pos_num = int(pos_mo.group(2)) + if nle_mo: + nle_num = int(nle_mo.group(1)) + + mem_results = '' + if (def_num > 0) or (ind_num > 0) or (pos_num > 0) or (nle_num > 0): + mem_results += 'not ' + + mem_results += 'ok {} - {}-mem # {}\n'.format( + self.args.test_ordinal, self.args.testid, 'memory leak check') + self._add_to_tap(mem_results) + if mem_results.startswith('not '): + print('{}'.format(content)) + self._add_to_tap(content) + + def _add_to_tap(self, more_tap_output): + self.tap += more_tap_output |