summaryrefslogtreecommitdiffstats
path: root/debuginfo-tests/dexter/dex/utils
diff options
context:
space:
mode:
authorJeremy Morse <jeremy.morse@sony.com>2019-10-31 16:51:53 +0000
committerJeremy Morse <jeremy.morse@sony.com>2019-10-31 16:51:53 +0000
commit984fad243d179564df31c5f9531a52442e24581a (patch)
treeaba85a27f1596d456079f6f5eb69e09408730b49 /debuginfo-tests/dexter/dex/utils
parent34f3c0fc44a5fd8a0f9186002749336e398837cf (diff)
downloadbcm5719-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.py22
-rw-r--r--debuginfo-tests/dexter/dex/utils/Exceptions.py72
-rw-r--r--debuginfo-tests/dexter/dex/utils/ExtArgParse.py148
-rw-r--r--debuginfo-tests/dexter/dex/utils/PrettyOutputBase.py392
-rw-r--r--debuginfo-tests/dexter/dex/utils/ReturnCode.py20
-rw-r--r--debuginfo-tests/dexter/dex/utils/RootDirectory.py15
-rw-r--r--debuginfo-tests/dexter/dex/utils/Timer.py50
-rw-r--r--debuginfo-tests/dexter/dex/utils/UnitTests.py62
-rw-r--r--debuginfo-tests/dexter/dex/utils/Version.py40
-rw-r--r--debuginfo-tests/dexter/dex/utils/Warning.py18
-rw-r--r--debuginfo-tests/dexter/dex/utils/WorkingDirectory.py46
-rw-r--r--debuginfo-tests/dexter/dex/utils/__init__.py21
-rw-r--r--debuginfo-tests/dexter/dex/utils/posix/PrettyOutput.py34
-rw-r--r--debuginfo-tests/dexter/dex/utils/posix/__init__.py6
-rw-r--r--debuginfo-tests/dexter/dex/utils/windows/PrettyOutput.py83
-rw-r--r--debuginfo-tests/dexter/dex/utils/windows/__init__.py6
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
OpenPOWER on IntegriCloud