summaryrefslogtreecommitdiffstats
path: root/debuginfo-tests/dexter/dex/tools/test/Tool.py
diff options
context:
space:
mode:
Diffstat (limited to 'debuginfo-tests/dexter/dex/tools/test/Tool.py')
-rw-r--r--debuginfo-tests/dexter/dex/tools/test/Tool.py244
1 files changed, 244 insertions, 0 deletions
diff --git a/debuginfo-tests/dexter/dex/tools/test/Tool.py b/debuginfo-tests/dexter/dex/tools/test/Tool.py
new file mode 100644
index 00000000000..fcd009c5081
--- /dev/null
+++ b/debuginfo-tests/dexter/dex/tools/test/Tool.py
@@ -0,0 +1,244 @@
+# DExTer : Debugging Experience Tester
+# ~~~~~~ ~ ~~ ~ ~~
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""Test tool."""
+
+import os
+import csv
+import pickle
+import shutil
+
+from dex.builder import run_external_build_script
+from dex.debugger.Debuggers import get_debugger_steps
+from dex.heuristic import Heuristic
+from dex.tools import TestToolBase
+from dex.utils.Exceptions import DebuggerException
+from dex.utils.Exceptions import BuildScriptException, HeuristicException
+from dex.utils.PrettyOutputBase import Stream
+from dex.utils.ReturnCode import ReturnCode
+from dex.dextIR import BuilderIR
+
+
+class TestCase(object):
+ def __init__(self, context, name, heuristic, error):
+ self.context = context
+ self.name = name
+ self.heuristic = heuristic
+ self.error = error
+
+ @property
+ def penalty(self):
+ try:
+ return self.heuristic.penalty
+ except AttributeError:
+ return float('nan')
+
+ @property
+ def max_penalty(self):
+ try:
+ return self.heuristic.max_penalty
+ except AttributeError:
+ return float('nan')
+
+ @property
+ def score(self):
+ try:
+ return self.heuristic.score
+ except AttributeError:
+ return float('nan')
+
+ def __str__(self):
+ if self.error and self.context.options.verbose:
+ verbose_error = str(self.error)
+ else:
+ verbose_error = ''
+
+ if self.error:
+ script_error = (' : {}'.format(
+ self.error.script_error.splitlines()[0].decode()) if getattr(
+ self.error, 'script_error', None) else '')
+
+ error = ' [{}{}]'.format(
+ str(self.error).splitlines()[0], script_error)
+ else:
+ error = ''
+
+ try:
+ summary = self.heuristic.summary_string
+ except AttributeError:
+ summary = '<r>nan/nan (nan)</>'
+ return '{}: {}{}\n{}'.format(self.name, summary, error, verbose_error)
+
+
+class Tool(TestToolBase):
+ """Run the specified DExTer test(s) with the specified compiler and linker
+ options and produce a dextIR file as well as printing out the debugging
+ experience score calculated by the DExTer heuristic.
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(Tool, self).__init__(*args, **kwargs)
+ self._test_cases = []
+
+ @property
+ def name(self):
+ return 'DExTer test'
+
+ def add_tool_arguments(self, parser, defaults):
+ parser.add_argument('--fail-lt',
+ type=float,
+ default=0.0, # By default TEST always succeeds.
+ help='exit with status FAIL(2) if the test result'
+ ' is less than this value.',
+ metavar='<float>')
+ super(Tool, self).add_tool_arguments(parser, defaults)
+
+ def _build_test_case(self):
+ """Build an executable from the test source with the given --builder
+ script and flags (--cflags, --ldflags) in the working directory.
+ Or, if the --binary option has been given, copy the executable provided
+ into the working directory and rename it to match the --builder output.
+ """
+
+ options = self.context.options
+ if options.binary:
+ # Copy user's binary into the tmp working directory
+ shutil.copy(options.binary, options.executable)
+ builderIR = BuilderIR(
+ name='binary',
+ cflags=[options.binary],
+ ldflags='')
+ else:
+ options = self.context.options
+ compiler_options = [options.cflags for _ in options.source_files]
+ linker_options = options.ldflags
+ _, _, builderIR = run_external_build_script(
+ self.context,
+ script_path=self.build_script,
+ source_files=options.source_files,
+ compiler_options=compiler_options,
+ linker_options=linker_options,
+ executable_file=options.executable)
+ return builderIR
+
+ def _get_steps(self, builderIR):
+ """Generate a list of debugger steps from a test case.
+ """
+ steps = get_debugger_steps(self.context)
+ steps.builder = builderIR
+ return steps
+
+ def _get_results_basename(self, test_name):
+ def splitall(x):
+ while len(x) > 0:
+ x, y = os.path.split(x)
+ yield y
+ all_components = reversed([x for x in splitall(test_name)])
+ return '_'.join(all_components)
+
+ def _get_results_path(self, test_name):
+ """Returns the path to the test results directory for the test denoted
+ by test_name.
+ """
+ return os.path.join(self.context.options.results_directory,
+ self._get_results_basename(test_name))
+
+ def _get_results_text_path(self, test_name):
+ """Returns path results .txt file for test denoted by test_name.
+ """
+ test_results_path = self._get_results_path(test_name)
+ return '{}.txt'.format(test_results_path)
+
+ def _get_results_pickle_path(self, test_name):
+ """Returns path results .dextIR file for test denoted by test_name.
+ """
+ test_results_path = self._get_results_path(test_name)
+ return '{}.dextIR'.format(test_results_path)
+
+ def _record_steps(self, test_name, steps):
+ """Write out the set of steps out to the test's .txt and .json
+ results file.
+ """
+ output_text_path = self._get_results_text_path(test_name)
+ with open(output_text_path, 'w') as fp:
+ self.context.o.auto(str(steps), stream=Stream(fp))
+
+ output_dextIR_path = self._get_results_pickle_path(test_name)
+ with open(output_dextIR_path, 'wb') as fp:
+ pickle.dump(steps, fp, protocol=pickle.HIGHEST_PROTOCOL)
+
+ def _record_score(self, test_name, heuristic):
+ """Write out the test's heuristic score to the results .txt file.
+ """
+ output_text_path = self._get_results_text_path(test_name)
+ with open(output_text_path, 'a') as fp:
+ self.context.o.auto(heuristic.verbose_output, stream=Stream(fp))
+
+ def _record_test_and_display(self, test_case):
+ """Output test case to o stream and record test case internally for
+ handling later.
+ """
+ self.context.o.auto(test_case)
+ self._test_cases.append(test_case)
+
+ def _record_failed_test(self, test_name, exception):
+ """Instantiate a failed test case with failure exception and
+ store internally.
+ """
+ test_case = TestCase(self.context, test_name, None, exception)
+ self._record_test_and_display(test_case)
+
+ def _record_successful_test(self, test_name, steps, heuristic):
+ """Instantiate a successful test run, store test for handling later.
+ Display verbose output for test case if required.
+ """
+ test_case = TestCase(self.context, test_name, heuristic, None)
+ self._record_test_and_display(test_case)
+ if self.context.options.verbose:
+ self.context.o.auto('\n{}\n'.format(steps))
+ self.context.o.auto(heuristic.verbose_output)
+
+ def _run_test(self, test_name):
+ """Attempt to run test files specified in options.source_files. Store
+ result internally in self._test_cases.
+ """
+ try:
+ builderIR = self._build_test_case()
+ steps = self._get_steps(builderIR)
+ self._record_steps(test_name, steps)
+ heuristic_score = Heuristic(self.context, steps)
+ self._record_score(test_name, heuristic_score)
+ except (BuildScriptException, DebuggerException,
+ HeuristicException) as e:
+ self._record_failed_test(test_name, e)
+ return
+
+ self._record_successful_test(test_name, steps, heuristic_score)
+ return
+
+ def _handle_results(self) -> ReturnCode:
+ return_code = ReturnCode.OK
+ options = self.context.options
+
+ if not options.verbose:
+ self.context.o.auto('\n')
+
+ summary_path = os.path.join(options.results_directory, 'summary.csv')
+ with open(summary_path, mode='w', newline='') as fp:
+ writer = csv.writer(fp, delimiter=',')
+ writer.writerow(['Test Case', 'Score', 'Error'])
+
+ for test_case in self._test_cases:
+ if (test_case.score < options.fail_lt or
+ test_case.error is not None):
+ return_code = ReturnCode.FAIL
+
+ writer.writerow([
+ test_case.name, '{:.4f}'.format(test_case.score),
+ test_case.error
+ ])
+
+ return return_code
OpenPOWER on IntegriCloud