diff options
author | Jeremy Morse <jeremy.morse@sony.com> | 2019-10-31 16:51:53 +0000 |
---|---|---|
committer | Jeremy Morse <jeremy.morse@sony.com> | 2019-10-31 16:51:53 +0000 |
commit | 984fad243d179564df31c5f9531a52442e24581a (patch) | |
tree | aba85a27f1596d456079f6f5eb69e09408730b49 /debuginfo-tests/dexter/dex/utils | |
parent | 34f3c0fc44a5fd8a0f9186002749336e398837cf (diff) | |
download | bcm5719-llvm-984fad243d179564df31c5f9531a52442e24581a.tar.gz bcm5719-llvm-984fad243d179564df31c5f9531a52442e24581a.zip |
Reapply "Import Dexter to debuginfo-tests""
This reverts commit cb935f345683194e42e6e883d79c5a16479acd74.
Discussion in D68708 advises that green dragon is being briskly
refurbished, and it's good to have this patch up testing it.
Diffstat (limited to 'debuginfo-tests/dexter/dex/utils')
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/Environment.py | 22 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/Exceptions.py | 72 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/ExtArgParse.py | 148 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/PrettyOutputBase.py | 392 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/ReturnCode.py | 20 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/RootDirectory.py | 15 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/Timer.py | 50 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/UnitTests.py | 62 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/Version.py | 40 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/Warning.py | 18 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/WorkingDirectory.py | 46 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/__init__.py | 21 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/posix/PrettyOutput.py | 34 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/posix/__init__.py | 6 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/windows/PrettyOutput.py | 83 | ||||
-rw-r--r-- | debuginfo-tests/dexter/dex/utils/windows/__init__.py | 6 |
16 files changed, 1035 insertions, 0 deletions
diff --git a/debuginfo-tests/dexter/dex/utils/Environment.py b/debuginfo-tests/dexter/dex/utils/Environment.py new file mode 100644 index 00000000000..d2df2522440 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/Environment.py @@ -0,0 +1,22 @@ +# 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 +"""Utility functions for querying the current environment.""" + +import os + + +def is_native_windows(): + return os.name == 'nt' + + +def has_pywin32(): + try: + import win32com.client # pylint:disable=unused-variable + import win32api # pylint:disable=unused-variable + return True + except ImportError: + return False diff --git a/debuginfo-tests/dexter/dex/utils/Exceptions.py b/debuginfo-tests/dexter/dex/utils/Exceptions.py new file mode 100644 index 00000000000..39c0c2f1695 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/Exceptions.py @@ -0,0 +1,72 @@ +# 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 +"""Provides Dexter-specific exception types.""" + + +class Dexception(Exception): + """All dexter-specific exceptions derive from this.""" + pass + + +class Error(Dexception): + """Error. Prints 'error: <message>' without a traceback.""" + pass + + +class DebuggerException(Dexception): + """Any error from using the debugger.""" + + def __init__(self, msg, orig_exception=None): + super(DebuggerException, self).__init__(msg) + self.msg = msg + self.orig_exception = orig_exception + + def __str__(self): + return str(self.msg) + + +class LoadDebuggerException(DebuggerException): + """If specified debugger cannot be loaded.""" + pass + + +class NotYetLoadedDebuggerException(LoadDebuggerException): + """If specified debugger has not yet been attempted to load.""" + + def __init__(self): + super(NotYetLoadedDebuggerException, + self).__init__('not loaded', orig_exception=None) + + +class CommandParseError(Dexception): + """If a command instruction cannot be successfully parsed.""" + + def __init__(self, *args, **kwargs): + super(CommandParseError, self).__init__(*args, **kwargs) + self.filename = None + self.lineno = None + self.info = None + self.src = None + self.caret = None + + +class ToolArgumentError(Dexception): + """If a tool argument is invalid.""" + pass + + +class BuildScriptException(Dexception): + """If there is an error in a build script file.""" + + def __init__(self, *args, **kwargs): + self.script_error = kwargs.pop('script_error', None) + super(BuildScriptException, self).__init__(*args, **kwargs) + + +class HeuristicException(Dexception): + """If there was a problem with the heuristic.""" + pass diff --git a/debuginfo-tests/dexter/dex/utils/ExtArgParse.py b/debuginfo-tests/dexter/dex/utils/ExtArgParse.py new file mode 100644 index 00000000000..9fa08fb066e --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/ExtArgParse.py @@ -0,0 +1,148 @@ +# 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 +"""Extended Argument Parser. Extends the argparse module with some extra +functionality, to hopefully aid user-friendliness. +""" + +import argparse +import difflib +import unittest + +from dex.utils import PrettyOutput +from dex.utils.Exceptions import Error + +# re-export all of argparse +for argitem in argparse.__all__: + vars()[argitem] = getattr(argparse, argitem) + + +def _did_you_mean(val, possibles): + close_matches = difflib.get_close_matches(val, possibles) + did_you_mean = '' + if close_matches: + did_you_mean = 'did you mean {}?'.format(' or '.join( + "<y>'{}'</>".format(c) for c in close_matches[:2])) + return did_you_mean + + +def _colorize(message): + lines = message.splitlines() + for i, line in enumerate(lines): + lines[i] = lines[i].replace('usage:', '<g>usage:</>') + if line.endswith(':'): + lines[i] = '<g>{}</>'.format(line) + return '\n'.join(lines) + + +class ExtArgumentParser(argparse.ArgumentParser): + def error(self, message): + """Use the Dexception Error mechanism (including auto-colored output). + """ + raise Error('{}\n\n{}'.format(message, self.format_usage())) + + # pylint: disable=redefined-builtin + def _print_message(self, message, file=None): + if message: + if file and file.name == '<stdout>': + file = PrettyOutput.stdout + else: + file = PrettyOutput.stderr + + self.context.o.auto(message, file) + + # pylint: enable=redefined-builtin + + def format_usage(self): + return _colorize(super(ExtArgumentParser, self).format_usage()) + + def format_help(self): + return _colorize(super(ExtArgumentParser, self).format_help() + '\n\n') + + @property + def _valid_visible_options(self): + """A list of all non-suppressed command line flags.""" + return [ + item for sublist in vars(self)['_actions'] + for item in sublist.option_strings + if sublist.help != argparse.SUPPRESS + ] + + def parse_args(self, args=None, namespace=None): + """Add 'did you mean' output to errors.""" + args, argv = self.parse_known_args(args, namespace) + if argv: + errors = [] + for arg in argv: + if arg in self._valid_visible_options: + error = "unexpected argument: <y>'{}'</>".format(arg) + else: + error = "unrecognized argument: <y>'{}'</>".format(arg) + dym = _did_you_mean(arg, self._valid_visible_options) + if dym: + error += ' ({})'.format(dym) + errors.append(error) + self.error('\n '.join(errors)) + + return args + + def add_argument(self, *args, **kwargs): + """Automatically add the default value to help text.""" + if 'default' in kwargs: + default = kwargs['default'] + if default is None: + default = kwargs.pop('display_default', None) + + if (default and isinstance(default, (str, int, float)) + and default != argparse.SUPPRESS): + assert ( + 'choices' not in kwargs or default in kwargs['choices']), ( + "default value '{}' is not one of allowed choices: {}". + format(default, kwargs['choices'])) + if 'help' in kwargs and kwargs['help'] != argparse.SUPPRESS: + assert isinstance(kwargs['help'], str), type(kwargs['help']) + kwargs['help'] = ('{} (default:{})'.format( + kwargs['help'], default)) + + super(ExtArgumentParser, self).add_argument(*args, **kwargs) + + def __init__(self, context, *args, **kwargs): + self.context = context + super(ExtArgumentParser, self).__init__(*args, **kwargs) + + +class TestExtArgumentParser(unittest.TestCase): + def test_did_you_mean(self): + parser = ExtArgumentParser(None) + parser.add_argument('--foo') + parser.add_argument('--qoo', help=argparse.SUPPRESS) + parser.add_argument('jam', nargs='?') + + parser.parse_args(['--foo', '0']) + + expected = (r"^unrecognized argument\: <y>'\-\-doo'</>\s+" + r"\(did you mean <y>'\-\-foo'</>\?\)\n" + r"\s*<g>usage:</>") + with self.assertRaisesRegex(Error, expected): + parser.parse_args(['--doo']) + + parser.add_argument('--noo') + + expected = (r"^unrecognized argument\: <y>'\-\-doo'</>\s+" + r"\(did you mean <y>'\-\-noo'</> or <y>'\-\-foo'</>\?\)\n" + r"\s*<g>usage:</>") + with self.assertRaisesRegex(Error, expected): + parser.parse_args(['--doo']) + + expected = (r"^unrecognized argument\: <y>'\-\-bar'</>\n" + r"\s*<g>usage:</>") + with self.assertRaisesRegex(Error, expected): + parser.parse_args(['--bar']) + + expected = (r"^unexpected argument\: <y>'\-\-foo'</>\n" + r"\s*<g>usage:</>") + with self.assertRaisesRegex(Error, expected): + parser.parse_args(['--', 'x', '--foo']) diff --git a/debuginfo-tests/dexter/dex/utils/PrettyOutputBase.py b/debuginfo-tests/dexter/dex/utils/PrettyOutputBase.py new file mode 100644 index 00000000000..d21db89a6ae --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/PrettyOutputBase.py @@ -0,0 +1,392 @@ +# 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 +"""Provides formatted/colored console output on both Windows and Linux. + +Do not use this module directly, but instead use via the appropriate platform- +specific module. +""" + +import abc +import re +import sys +import threading +import unittest + +from io import StringIO + +from dex.utils.Exceptions import Error + + +class _NullLock(object): + def __enter__(self): + return None + + def __exit__(self, *params): + pass + + +_lock = threading.Lock() +_null_lock = _NullLock() + + +class PreserveAutoColors(object): + def __init__(self, pretty_output): + self.pretty_output = pretty_output + self.orig_values = {} + self.properties = [ + 'auto_reds', 'auto_yellows', 'auto_greens', 'auto_blues' + ] + + def __enter__(self): + for p in self.properties: + self.orig_values[p] = getattr(self.pretty_output, p)[:] + return self + + def __exit__(self, *args): + for p in self.properties: + setattr(self.pretty_output, p, self.orig_values[p]) + + +class Stream(object): + def __init__(self, py_, os_=None): + self.py = py_ + self.os = os_ + self.orig_color = None + self.color_enabled = self.py.isatty() + + +class PrettyOutputBase(object, metaclass=abc.ABCMeta): + stdout = Stream(sys.stdout) + stderr = Stream(sys.stderr) + + def __init__(self): + self.auto_reds = [] + self.auto_yellows = [] + self.auto_greens = [] + self.auto_blues = [] + self._stack = [] + + def __enter__(self): + return self + + def __exit__(self, *args): + pass + + def _set_valid_stream(self, stream): + if stream is None: + return self.__class__.stdout + return stream + + def _write(self, text, stream): + text = str(text) + + # Users can embed color control tags in their output + # (e.g. <r>hello</> <y>world</> would write the word 'hello' in red and + # 'world' in yellow). + # This function parses these tags using a very simple recursive + # descent. + colors = { + 'r': self.red, + 'y': self.yellow, + 'g': self.green, + 'b': self.blue, + 'd': self.default, + 'a': self.auto, + } + + # Find all tags (whether open or close) + tags = [ + t for t in re.finditer('<([{}/])>'.format(''.join(colors)), text) + ] + + if not tags: + # No tags. Just write the text to the current stream and return. + # 'unmangling' any tags that have been mangled so that they won't + # render as colors (for example in error output from this + # function). + stream = self._set_valid_stream(stream) + stream.py.write(text.replace(r'\>', '>')) + return + + open_tags = [i for i in tags if i.group(1) != '/'] + close_tags = [i for i in tags if i.group(1) == '/'] + + if (len(open_tags) != len(close_tags) + or any(o.start() >= c.start() + for (o, c) in zip(open_tags, close_tags))): + raise Error('open/close tag mismatch in "{}"'.format( + text.rstrip()).replace('>', r'\>')) + + open_tag = open_tags.pop(0) + + # We know that the tags balance correctly, so figure out where the + # corresponding close tag is to the current open tag. + tag_nesting = 1 + close_tag = None + for tag in tags[1:]: + if tag.group(1) == '/': + tag_nesting -= 1 + else: + tag_nesting += 1 + if tag_nesting == 0: + close_tag = tag + break + else: + assert False, text + + # Use the method on the top of the stack for text prior to the open + # tag. + before = text[:open_tag.start()] + if before: + self._stack[-1](before, lock=_null_lock, stream=stream) + + # Use the specified color for the tag itself. + color = open_tag.group(1) + within = text[open_tag.end():close_tag.start()] + if within: + colors[color](within, lock=_null_lock, stream=stream) + + # Use the method on the top of the stack for text after the close tag. + after = text[close_tag.end():] + if after: + self._stack[-1](after, lock=_null_lock, stream=stream) + + def flush(self, stream): + stream = self._set_valid_stream(stream) + stream.py.flush() + + def auto(self, text, stream=None, lock=_lock): + text = str(text) + stream = self._set_valid_stream(stream) + lines = text.splitlines(True) + + with lock: + for line in lines: + # This is just being cute for the sake of cuteness, but why + # not? + line = line.replace('DExTer', '<r>D<y>E<g>x<b>T</></>e</>r</>') + + # Apply the appropriate color method if the expression matches + # any of + # the patterns we have set up. + for fn, regexs in ((self.red, self.auto_reds), + (self.yellow, self.auto_yellows), + (self.green, + self.auto_greens), (self.blue, + self.auto_blues)): + if any(re.search(regex, line) for regex in regexs): + fn(line, stream=stream, lock=_null_lock) + break + else: + self.default(line, stream=stream, lock=_null_lock) + + def _call_color_impl(self, fn, impl, text, *args, **kwargs): + try: + self._stack.append(fn) + return impl(text, *args, **kwargs) + finally: + fn = self._stack.pop() + + @abc.abstractmethod + def red_impl(self, text, stream=None, **kwargs): + pass + + def red(self, *args, **kwargs): + return self._call_color_impl(self.red, self.red_impl, *args, **kwargs) + + @abc.abstractmethod + def yellow_impl(self, text, stream=None, **kwargs): + pass + + def yellow(self, *args, **kwargs): + return self._call_color_impl(self.yellow, self.yellow_impl, *args, + **kwargs) + + @abc.abstractmethod + def green_impl(self, text, stream=None, **kwargs): + pass + + def green(self, *args, **kwargs): + return self._call_color_impl(self.green, self.green_impl, *args, + **kwargs) + + @abc.abstractmethod + def blue_impl(self, text, stream=None, **kwargs): + pass + + def blue(self, *args, **kwargs): + return self._call_color_impl(self.blue, self.blue_impl, *args, + **kwargs) + + @abc.abstractmethod + def default_impl(self, text, stream=None, **kwargs): + pass + + def default(self, *args, **kwargs): + return self._call_color_impl(self.default, self.default_impl, *args, + **kwargs) + + def colortest(self): + from itertools import combinations, permutations + + fns = ((self.red, 'rrr'), (self.yellow, 'yyy'), (self.green, 'ggg'), + (self.blue, 'bbb'), (self.default, 'ddd')) + + for l in range(1, len(fns) + 1): + for comb in combinations(fns, l): + for perm in permutations(comb): + for stream in (None, self.__class__.stderr): + perm[0][0]('stdout ' + if stream is None else 'stderr ', stream) + for fn, string in perm: + fn(string, stream) + self.default('\n', stream) + + tests = [ + (self.auto, 'default1<r>red2</>default3'), + (self.red, 'red1<r>red2</>red3'), + (self.blue, 'blue1<r>red2</>blue3'), + (self.red, 'red1<y>yellow2</>red3'), + (self.auto, 'default1<y>yellow2<r>red3</></>'), + (self.auto, 'default1<g>green2<r>red3</></>'), + (self.auto, 'default1<g>green2<r>red3</>green4</>default5'), + (self.auto, 'default1<g>green2</>default3<g>green4</>default5'), + (self.auto, '<r>red1<g>green2</>red3<g>green4</>red5</>'), + (self.auto, '<r>red1<y><g>green2</>yellow3</>green4</>default5'), + (self.auto, '<r><y><g><b><d>default1</></><r></></></>red2</>'), + (self.auto, '<r>red1</>default2<r>red3</><g>green4</>default5'), + (self.blue, '<r>red1</>blue2<r><r>red3</><g><g>green</></></>'), + (self.blue, '<r>r<r>r<y>y<r><r><r><r>r</></></></></></></>b'), + ] + + for fn, text in tests: + for stream in (None, self.__class__.stderr): + stream_name = 'stdout' if stream is None else 'stderr' + fn('{} {}\n'.format(stream_name, text), stream) + + +class TestPrettyOutput(unittest.TestCase): + class MockPrettyOutput(PrettyOutputBase): + def red_impl(self, text, stream=None, **kwargs): + self._write('[R]{}[/R]'.format(text), stream) + + def yellow_impl(self, text, stream=None, **kwargs): + self._write('[Y]{}[/Y]'.format(text), stream) + + def green_impl(self, text, stream=None, **kwargs): + self._write('[G]{}[/G]'.format(text), stream) + + def blue_impl(self, text, stream=None, **kwargs): + self._write('[B]{}[/B]'.format(text), stream) + + def default_impl(self, text, stream=None, **kwargs): + self._write('[D]{}[/D]'.format(text), stream) + + def test_red(self): + with TestPrettyOutput.MockPrettyOutput() as o: + stream = Stream(StringIO()) + o.red('hello', stream) + self.assertEqual(stream.py.getvalue(), '[R]hello[/R]') + + def test_yellow(self): + with TestPrettyOutput.MockPrettyOutput() as o: + stream = Stream(StringIO()) + o.yellow('hello', stream) + self.assertEqual(stream.py.getvalue(), '[Y]hello[/Y]') + + def test_green(self): + with TestPrettyOutput.MockPrettyOutput() as o: + stream = Stream(StringIO()) + o.green('hello', stream) + self.assertEqual(stream.py.getvalue(), '[G]hello[/G]') + + def test_blue(self): + with TestPrettyOutput.MockPrettyOutput() as o: + stream = Stream(StringIO()) + o.blue('hello', stream) + self.assertEqual(stream.py.getvalue(), '[B]hello[/B]') + + def test_default(self): + with TestPrettyOutput.MockPrettyOutput() as o: + stream = Stream(StringIO()) + o.default('hello', stream) + self.assertEqual(stream.py.getvalue(), '[D]hello[/D]') + + def test_auto(self): + with TestPrettyOutput.MockPrettyOutput() as o: + stream = Stream(StringIO()) + o.auto_reds.append('foo') + o.auto('bar\n', stream) + o.auto('foo\n', stream) + o.auto('baz\n', stream) + self.assertEqual(stream.py.getvalue(), + '[D]bar\n[/D][R]foo\n[/R][D]baz\n[/D]') + + stream = Stream(StringIO()) + o.auto('bar\nfoo\nbaz\n', stream) + self.assertEqual(stream.py.getvalue(), + '[D]bar\n[/D][R]foo\n[/R][D]baz\n[/D]') + + stream = Stream(StringIO()) + o.auto('barfoobaz\nbardoobaz\n', stream) + self.assertEqual(stream.py.getvalue(), + '[R]barfoobaz\n[/R][D]bardoobaz\n[/D]') + + o.auto_greens.append('doo') + stream = Stream(StringIO()) + o.auto('barfoobaz\nbardoobaz\n', stream) + self.assertEqual(stream.py.getvalue(), + '[R]barfoobaz\n[/R][G]bardoobaz\n[/G]') + + def test_PreserveAutoColors(self): + with TestPrettyOutput.MockPrettyOutput() as o: + o.auto_reds.append('foo') + with PreserveAutoColors(o): + o.auto_greens.append('bar') + stream = Stream(StringIO()) + o.auto('foo\nbar\nbaz\n', stream) + self.assertEqual(stream.py.getvalue(), + '[R]foo\n[/R][G]bar\n[/G][D]baz\n[/D]') + + stream = Stream(StringIO()) + o.auto('foo\nbar\nbaz\n', stream) + self.assertEqual(stream.py.getvalue(), + '[R]foo\n[/R][D]bar\n[/D][D]baz\n[/D]') + + stream = Stream(StringIO()) + o.yellow('<a>foo</>bar<a>baz</>', stream) + self.assertEqual( + stream.py.getvalue(), + '[Y][Y][/Y][R]foo[/R][Y][Y]bar[/Y][D]baz[/D][Y][/Y][/Y][/Y]') + + def test_tags(self): + with TestPrettyOutput.MockPrettyOutput() as o: + stream = Stream(StringIO()) + o.auto('<r>hi</>', stream) + self.assertEqual(stream.py.getvalue(), + '[D][D][/D][R]hi[/R][D][/D][/D]') + + stream = Stream(StringIO()) + o.auto('<r><y>a</>b</>c', stream) + self.assertEqual( + stream.py.getvalue(), + '[D][D][/D][R][R][/R][Y]a[/Y][R]b[/R][/R][D]c[/D][/D]') + + with self.assertRaisesRegex(Error, 'tag mismatch'): + o.auto('<r>hi', stream) + + with self.assertRaisesRegex(Error, 'tag mismatch'): + o.auto('hi</>', stream) + + with self.assertRaisesRegex(Error, 'tag mismatch'): + o.auto('<r><y>hi</>', stream) + + with self.assertRaisesRegex(Error, 'tag mismatch'): + o.auto('<r><y>hi</><r></>', stream) + + with self.assertRaisesRegex(Error, 'tag mismatch'): + o.auto('</>hi<r>', stream) diff --git a/debuginfo-tests/dexter/dex/utils/ReturnCode.py b/debuginfo-tests/dexter/dex/utils/ReturnCode.py new file mode 100644 index 00000000000..487d225d1b6 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/ReturnCode.py @@ -0,0 +1,20 @@ +# 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 + +from enum import Enum + + +class ReturnCode(Enum): + """Used to indicate whole program success status.""" + + OK = 0 + _ERROR = 1 # Unhandled exceptions result in exit(1) by default. + # Usage of _ERROR is discouraged: + # If the program cannot run, raise an exception. + # If the program runs successfully but the result is + # "failure" based on the inputs, return FAIL + FAIL = 2 diff --git a/debuginfo-tests/dexter/dex/utils/RootDirectory.py b/debuginfo-tests/dexter/dex/utils/RootDirectory.py new file mode 100644 index 00000000000..57f204c79ac --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/RootDirectory.py @@ -0,0 +1,15 @@ +# 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 +"""Utility functions related to DExTer's directory layout.""" + +import os + + +def get_root_directory(): + root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + assert os.path.basename(root) == 'dex', root + return root diff --git a/debuginfo-tests/dexter/dex/utils/Timer.py b/debuginfo-tests/dexter/dex/utils/Timer.py new file mode 100644 index 00000000000..63726f1a757 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/Timer.py @@ -0,0 +1,50 @@ +# 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 +"""RAII-style timer class to be used with a 'with' statement to get wall clock +time for the contained code. +""" + +import sys +import time + + +def _indent(indent): + return '| ' * indent + + +class Timer(object): + fn = sys.stdout.write + display = False + indent = 0 + + def __init__(self, name=None): + self.name = name + self.start = self.now + + def __enter__(self): + Timer.indent += 1 + if Timer.display and self.name: + indent = _indent(Timer.indent - 1) + ' _' + Timer.fn('{}\n'.format(_indent(Timer.indent - 1))) + Timer.fn('{} start {}\n'.format(indent, self.name)) + return self + + def __exit__(self, *args): + if Timer.display and self.name: + indent = _indent(Timer.indent - 1) + '|_' + Timer.fn('{} {} time taken: {:0.1f}s\n'.format( + indent, self.name, self.elapsed)) + Timer.fn('{}\n'.format(_indent(Timer.indent - 1))) + Timer.indent -= 1 + + @property + def elapsed(self): + return self.now - self.start + + @property + def now(self): + return time.time() diff --git a/debuginfo-tests/dexter/dex/utils/UnitTests.py b/debuginfo-tests/dexter/dex/utils/UnitTests.py new file mode 100644 index 00000000000..5a8a0a6aeb9 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/UnitTests.py @@ -0,0 +1,62 @@ +# 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 +"""Unit test harness.""" + +from fnmatch import fnmatch +import os +import unittest + +from io import StringIO + +from dex.utils import is_native_windows, has_pywin32 +from dex.utils import PreserveAutoColors, PrettyOutput +from dex.utils import Timer + + +class DexTestLoader(unittest.TestLoader): + def _match_path(self, path, full_path, pattern): + """Don't try to import platform-specific modules for the wrong platform + during test discovery. + """ + d = os.path.basename(os.path.dirname(full_path)) + if is_native_windows(): + if d == 'posix': + return False + if d == 'windows': + return has_pywin32() + else: + if d == 'windows': + return False + return fnmatch(path, pattern) + + +def unit_tests_ok(context): + unittest.TestCase.maxDiff = None # remove size limit from diff output. + + with Timer('unit tests'): + suite = DexTestLoader().discover( + context.root_directory, pattern='*.py') + stream = StringIO() + result = unittest.TextTestRunner(verbosity=2, stream=stream).run(suite) + + ok = result.wasSuccessful() + if not ok or context.options.unittest == 'show-all': + with PreserveAutoColors(context.o): + context.o.auto_reds.extend( + [r'FAIL(ED|\:)', r'\.\.\.\s(FAIL|ERROR)$']) + context.o.auto_greens.extend([r'^OK$', r'\.\.\.\sok$']) + context.o.auto_blues.extend([r'^Ran \d+ test']) + context.o.default('\n') + for line in stream.getvalue().splitlines(True): + context.o.auto(line, stream=PrettyOutput.stderr) + + return ok + + +class TestUnitTests(unittest.TestCase): + def test_sanity(self): + self.assertEqual(1, 1) diff --git a/debuginfo-tests/dexter/dex/utils/Version.py b/debuginfo-tests/dexter/dex/utils/Version.py new file mode 100644 index 00000000000..1a257fa7107 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/Version.py @@ -0,0 +1,40 @@ +# 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 +"""DExTer version output.""" + +import os +from subprocess import CalledProcessError, check_output, STDOUT +import sys + +from dex import __version__ + + +def _git_version(): + dir_ = os.path.dirname(__file__) + try: + branch = (check_output( + ['git', 'rev-parse', '--abbrev-ref', 'HEAD'], + stderr=STDOUT, + cwd=dir_).rstrip().decode('utf-8')) + hash_ = check_output( + ['git', 'rev-parse', 'HEAD'], stderr=STDOUT, + cwd=dir_).rstrip().decode('utf-8') + repo = check_output( + ['git', 'remote', 'get-url', 'origin'], stderr=STDOUT, + cwd=dir_).rstrip().decode('utf-8') + return '[{} {}] ({})'.format(branch, hash_, repo) + except (OSError, CalledProcessError): + pass + return None + + +def version(name): + lines = [] + lines.append(' '.join( + [s for s in [name, __version__, _git_version()] if s])) + lines.append(' using Python {}'.format(sys.version)) + return '\n'.join(lines) diff --git a/debuginfo-tests/dexter/dex/utils/Warning.py b/debuginfo-tests/dexter/dex/utils/Warning.py new file mode 100644 index 00000000000..402861aaed5 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/Warning.py @@ -0,0 +1,18 @@ +# 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 +"""Utility functions for producing command line warnings.""" + + +def warn(context, msg, flag=None): + if context.options.no_warnings: + return + + msg = msg.rstrip() + if flag: + msg = '{} <y>[{}]</>'.format(msg, flag) + + context.o.auto('warning: <d>{}</>\n'.format(msg)) diff --git a/debuginfo-tests/dexter/dex/utils/WorkingDirectory.py b/debuginfo-tests/dexter/dex/utils/WorkingDirectory.py new file mode 100644 index 00000000000..e1862f2db72 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/WorkingDirectory.py @@ -0,0 +1,46 @@ +# 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 +"""Create/set a temporary working directory for some operations.""" + +import os +import shutil +import tempfile +import time + +from dex.utils.Exceptions import Error + + +class WorkingDirectory(object): + def __init__(self, context, *args, **kwargs): + self.context = context + self.orig_cwd = os.getcwd() + + dir_ = kwargs.get('dir', None) + if dir_ and not os.path.isdir(dir_): + os.makedirs(dir_, exist_ok=True) + self.path = tempfile.mkdtemp(*args, **kwargs) + + def __enter__(self): + os.chdir(self.path) + return self + + def __exit__(self, *args): + os.chdir(self.orig_cwd) + if self.context.options.save_temps: + self.context.o.blue('"{}" left in place [--save-temps]\n'.format( + self.path)) + return + + exception = AssertionError('should never be raised') + for _ in range(100): + try: + shutil.rmtree(self.path) + return + except OSError as e: + exception = e + time.sleep(0.1) + raise Error(exception) diff --git a/debuginfo-tests/dexter/dex/utils/__init__.py b/debuginfo-tests/dexter/dex/utils/__init__.py new file mode 100644 index 00000000000..ac08139d568 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/__init__.py @@ -0,0 +1,21 @@ +# 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 +"""Generic non-dexter-specific utility classes and functions.""" + +import os + +from dex.utils.Environment import is_native_windows, has_pywin32 +from dex.utils.PrettyOutputBase import PreserveAutoColors +from dex.utils.RootDirectory import get_root_directory +from dex.utils.Timer import Timer +from dex.utils.Warning import warn +from dex.utils.WorkingDirectory import WorkingDirectory + +if is_native_windows(): + from dex.utils.windows.PrettyOutput import PrettyOutput +else: + from dex.utils.posix.PrettyOutput import PrettyOutput diff --git a/debuginfo-tests/dexter/dex/utils/posix/PrettyOutput.py b/debuginfo-tests/dexter/dex/utils/posix/PrettyOutput.py new file mode 100644 index 00000000000..82cfed5dfd6 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/posix/PrettyOutput.py @@ -0,0 +1,34 @@ +# 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 +"""Provides POSIX implementation of formatted/colored console output.""" + +from ..PrettyOutputBase import PrettyOutputBase, _lock + + +class PrettyOutput(PrettyOutputBase): + def _color(self, text, color, stream, lock=_lock): + """Use ANSI escape codes to provide color on Linux.""" + stream = self._set_valid_stream(stream) + with lock: + if stream.color_enabled: + text = '\033[{}m{}\033[0m'.format(color, text) + self._write(text, stream) + + def red_impl(self, text, stream=None, **kwargs): + self._color(text, 91, stream, **kwargs) + + def yellow_impl(self, text, stream=None, **kwargs): + self._color(text, 93, stream, **kwargs) + + def green_impl(self, text, stream=None, **kwargs): + self._color(text, 92, stream, **kwargs) + + def blue_impl(self, text, stream=None, **kwargs): + self._color(text, 96, stream, **kwargs) + + def default_impl(self, text, stream=None, **kwargs): + self._color(text, 0, stream, **kwargs) diff --git a/debuginfo-tests/dexter/dex/utils/posix/__init__.py b/debuginfo-tests/dexter/dex/utils/posix/__init__.py new file mode 100644 index 00000000000..1194affd891 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/posix/__init__.py @@ -0,0 +1,6 @@ +# 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 diff --git a/debuginfo-tests/dexter/dex/utils/windows/PrettyOutput.py b/debuginfo-tests/dexter/dex/utils/windows/PrettyOutput.py new file mode 100644 index 00000000000..657406a59ac --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/windows/PrettyOutput.py @@ -0,0 +1,83 @@ +# 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 +"""Provides Windows implementation of formatted/colored console output.""" + +import sys + +import ctypes +import ctypes.wintypes + +from ..PrettyOutputBase import PrettyOutputBase, Stream, _lock, _null_lock + + +class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): + # pylint: disable=protected-access + _fields_ = [('dwSize', ctypes.wintypes._COORD), ('dwCursorPosition', + ctypes.wintypes._COORD), + ('wAttributes', + ctypes.c_ushort), ('srWindow', ctypes.wintypes._SMALL_RECT), + ('dwMaximumWindowSize', ctypes.wintypes._COORD)] + # pylint: enable=protected-access + + +class PrettyOutput(PrettyOutputBase): + + stdout = Stream(sys.stdout, ctypes.windll.kernel32.GetStdHandle(-11)) + stderr = Stream(sys.stderr, ctypes.windll.kernel32.GetStdHandle(-12)) + + def __enter__(self): + info = _CONSOLE_SCREEN_BUFFER_INFO() + + for s in (PrettyOutput.stdout, PrettyOutput.stderr): + ctypes.windll.kernel32.GetConsoleScreenBufferInfo( + s.os, ctypes.byref(info)) + s.orig_color = info.wAttributes + + return self + + def __exit__(self, *args): + self._restore_orig_color(PrettyOutput.stdout) + self._restore_orig_color(PrettyOutput.stderr) + + def _restore_orig_color(self, stream, lock=_lock): + if not stream.color_enabled: + return + + with lock: + stream = self._set_valid_stream(stream) + self.flush(stream) + if stream.orig_color: + ctypes.windll.kernel32.SetConsoleTextAttribute( + stream.os, stream.orig_color) + + def _color(self, text, color, stream, lock=_lock): + stream = self._set_valid_stream(stream) + with lock: + try: + if stream.color_enabled: + ctypes.windll.kernel32.SetConsoleTextAttribute( + stream.os, color) + self._write(text, stream) + finally: + if stream.color_enabled: + self._restore_orig_color(stream, lock=_null_lock) + + def red_impl(self, text, stream=None, **kwargs): + self._color(text, 12, stream, **kwargs) + + def yellow_impl(self, text, stream=None, **kwargs): + self._color(text, 14, stream, **kwargs) + + def green_impl(self, text, stream=None, **kwargs): + self._color(text, 10, stream, **kwargs) + + def blue_impl(self, text, stream=None, **kwargs): + self._color(text, 11, stream, **kwargs) + + def default_impl(self, text, stream=None, **kwargs): + stream = self._set_valid_stream(stream) + self._color(text, stream.orig_color, stream, **kwargs) diff --git a/debuginfo-tests/dexter/dex/utils/windows/__init__.py b/debuginfo-tests/dexter/dex/utils/windows/__init__.py new file mode 100644 index 00000000000..1194affd891 --- /dev/null +++ b/debuginfo-tests/dexter/dex/utils/windows/__init__.py @@ -0,0 +1,6 @@ +# 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 |