| | """Generic testing tools. |
| | |
| | Authors |
| | ------- |
| | - Fernando Perez <Fernando.Perez@berkeley.edu> |
| | """ |
| |
|
| |
|
| | |
| | |
| |
|
| | import os |
| | from pathlib import Path |
| | import re |
| | import sys |
| | import tempfile |
| | import unittest |
| |
|
| | from contextlib import contextmanager |
| | from io import StringIO |
| | from subprocess import Popen, PIPE |
| | from unittest.mock import patch |
| |
|
| | from traitlets.config.loader import Config |
| | from IPython.utils.process import get_output_error_code |
| | from IPython.utils.text import list_strings |
| | from IPython.utils.io import temp_pyfile, Tee |
| | from IPython.utils import py3compat |
| |
|
| | from . import decorators as dec |
| | from . import skipdoctest |
| |
|
| |
|
| | |
| | |
| | doctest_deco = skipdoctest.skip_doctest if sys.platform == 'win32' else dec.null_deco |
| |
|
| | @doctest_deco |
| | def full_path(startPath,files): |
| | """Make full paths for all the listed files, based on startPath. |
| | |
| | Only the base part of startPath is kept, since this routine is typically |
| | used with a script's ``__file__`` variable as startPath. The base of startPath |
| | is then prepended to all the listed files, forming the output list. |
| | |
| | Parameters |
| | ---------- |
| | startPath : string |
| | Initial path to use as the base for the results. This path is split |
| | using os.path.split() and only its first component is kept. |
| | |
| | files : string or list |
| | One or more files. |
| | |
| | Examples |
| | -------- |
| | |
| | >>> full_path('/foo/bar.py',['a.txt','b.txt']) |
| | ['/foo/a.txt', '/foo/b.txt'] |
| | |
| | >>> full_path('/foo',['a.txt','b.txt']) |
| | ['/a.txt', '/b.txt'] |
| | |
| | If a single file is given, the output is still a list:: |
| | |
| | >>> full_path('/foo','a.txt') |
| | ['/a.txt'] |
| | """ |
| |
|
| | files = list_strings(files) |
| | base = os.path.split(startPath)[0] |
| | return [ os.path.join(base,f) for f in files ] |
| |
|
| |
|
| | def parse_test_output(txt): |
| | """Parse the output of a test run and return errors, failures. |
| | |
| | Parameters |
| | ---------- |
| | txt : str |
| | Text output of a test run, assumed to contain a line of one of the |
| | following forms:: |
| | |
| | 'FAILED (errors=1)' |
| | 'FAILED (failures=1)' |
| | 'FAILED (errors=1, failures=1)' |
| | |
| | Returns |
| | ------- |
| | nerr, nfail |
| | number of errors and failures. |
| | """ |
| |
|
| | err_m = re.search(r'^FAILED \(errors=(\d+)\)', txt, re.MULTILINE) |
| | if err_m: |
| | nerr = int(err_m.group(1)) |
| | nfail = 0 |
| | return nerr, nfail |
| |
|
| | fail_m = re.search(r'^FAILED \(failures=(\d+)\)', txt, re.MULTILINE) |
| | if fail_m: |
| | nerr = 0 |
| | nfail = int(fail_m.group(1)) |
| | return nerr, nfail |
| |
|
| | both_m = re.search(r'^FAILED \(errors=(\d+), failures=(\d+)\)', txt, |
| | re.MULTILINE) |
| | if both_m: |
| | nerr = int(both_m.group(1)) |
| | nfail = int(both_m.group(2)) |
| | return nerr, nfail |
| |
|
| | |
| | return 0, 0 |
| |
|
| |
|
| | |
| | parse_test_output.__test__ = False |
| |
|
| |
|
| | def default_argv(): |
| | """Return a valid default argv for creating testing instances of ipython""" |
| |
|
| | return ['--quick', |
| | |
| | '--colors=NoColor', '--no-term-title','--no-banner', |
| | '--autocall=0'] |
| |
|
| |
|
| | def default_config(): |
| | """Return a config object with good defaults for testing.""" |
| | config = Config() |
| | config.TerminalInteractiveShell.colors = 'NoColor' |
| | config.TerminalTerminalInteractiveShell.term_title = False, |
| | config.TerminalInteractiveShell.autocall = 0 |
| | f = tempfile.NamedTemporaryFile(suffix=u'test_hist.sqlite', delete=False) |
| | config.HistoryManager.hist_file = Path(f.name) |
| | f.close() |
| | config.HistoryManager.db_cache_size = 10000 |
| | return config |
| |
|
| |
|
| | def get_ipython_cmd(as_string=False): |
| | """ |
| | Return appropriate IPython command line name. By default, this will return |
| | a list that can be used with subprocess.Popen, for example, but passing |
| | `as_string=True` allows for returning the IPython command as a string. |
| | |
| | Parameters |
| | ---------- |
| | as_string: bool |
| | Flag to allow to return the command as a string. |
| | """ |
| | ipython_cmd = [sys.executable, "-m", "IPython"] |
| |
|
| | if as_string: |
| | ipython_cmd = " ".join(ipython_cmd) |
| |
|
| | return ipython_cmd |
| |
|
| | def ipexec(fname, options=None, commands=()): |
| | """Utility to call 'ipython filename'. |
| | |
| | Starts IPython with a minimal and safe configuration to make startup as fast |
| | as possible. |
| | |
| | Note that this starts IPython in a subprocess! |
| | |
| | Parameters |
| | ---------- |
| | fname : str, Path |
| | Name of file to be executed (should have .py or .ipy extension). |
| | |
| | options : optional, list |
| | Extra command-line flags to be passed to IPython. |
| | |
| | commands : optional, list |
| | Commands to send in on stdin |
| | |
| | Returns |
| | ------- |
| | ``(stdout, stderr)`` of ipython subprocess. |
| | """ |
| | __tracebackhide__ = True |
| |
|
| | if options is None: |
| | options = [] |
| |
|
| | cmdargs = default_argv() + options |
| |
|
| | test_dir = os.path.dirname(__file__) |
| |
|
| | ipython_cmd = get_ipython_cmd() |
| | |
| | full_fname = os.path.join(test_dir, fname) |
| | full_cmd = ipython_cmd + cmdargs + ['--', full_fname] |
| | env = os.environ.copy() |
| | |
| | |
| | env['PYTHONWARNINGS'] = 'ignore' |
| | |
| | |
| | env.pop("PYCHARM_HOSTED", None) |
| | for k, v in env.items(): |
| | |
| | |
| | if not isinstance(v, str): |
| | print(k, v) |
| | p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env) |
| | out, err = p.communicate(input=py3compat.encode('\n'.join(commands)) or None) |
| | out, err = py3compat.decode(out), py3compat.decode(err) |
| | |
| | |
| | if out: |
| | out = re.sub(r'\x1b\[[^h]+h', '', out) |
| | return out, err |
| |
|
| |
|
| | def ipexec_validate(fname, expected_out, expected_err='', |
| | options=None, commands=()): |
| | """Utility to call 'ipython filename' and validate output/error. |
| | |
| | This function raises an AssertionError if the validation fails. |
| | |
| | Note that this starts IPython in a subprocess! |
| | |
| | Parameters |
| | ---------- |
| | fname : str, Path |
| | Name of the file to be executed (should have .py or .ipy extension). |
| | |
| | expected_out : str |
| | Expected stdout of the process. |
| | |
| | expected_err : optional, str |
| | Expected stderr of the process. |
| | |
| | options : optional, list |
| | Extra command-line flags to be passed to IPython. |
| | |
| | Returns |
| | ------- |
| | None |
| | """ |
| | __tracebackhide__ = True |
| |
|
| | out, err = ipexec(fname, options, commands) |
| | |
| | |
| | |
| | |
| | if err: |
| | if expected_err: |
| | assert "\n".join(err.strip().splitlines()) == "\n".join( |
| | expected_err.strip().splitlines() |
| | ) |
| | else: |
| | raise ValueError('Running file %r produced error: %r' % |
| | (fname, err)) |
| | |
| | assert "\n".join(out.strip().splitlines()) == "\n".join( |
| | expected_out.strip().splitlines() |
| | ) |
| |
|
| |
|
| | class TempFileMixin(unittest.TestCase): |
| | """Utility class to create temporary Python/IPython files. |
| | |
| | Meant as a mixin class for test cases.""" |
| |
|
| | def mktmp(self, src, ext='.py'): |
| | """Make a valid python temp file.""" |
| | fname = temp_pyfile(src, ext) |
| | if not hasattr(self, 'tmps'): |
| | self.tmps=[] |
| | self.tmps.append(fname) |
| | self.fname = fname |
| |
|
| | def tearDown(self): |
| | |
| | |
| | if hasattr(self, 'tmps'): |
| | for fname in self.tmps: |
| | |
| | |
| | try: |
| | os.unlink(fname) |
| | except: |
| | |
| | |
| | pass |
| |
|
| | def __enter__(self): |
| | return self |
| |
|
| | def __exit__(self, exc_type, exc_value, traceback): |
| | self.tearDown() |
| |
|
| |
|
| | pair_fail_msg = ("Testing {0}\n\n" |
| | "In:\n" |
| | " {1!r}\n" |
| | "Expected:\n" |
| | " {2!r}\n" |
| | "Got:\n" |
| | " {3!r}\n") |
| | def check_pairs(func, pairs): |
| | """Utility function for the common case of checking a function with a |
| | sequence of input/output pairs. |
| | |
| | Parameters |
| | ---------- |
| | func : callable |
| | The function to be tested. Should accept a single argument. |
| | pairs : iterable |
| | A list of (input, expected_output) tuples. |
| | |
| | Returns |
| | ------- |
| | None. Raises an AssertionError if any output does not match the expected |
| | value. |
| | """ |
| | __tracebackhide__ = True |
| |
|
| | name = getattr(func, "func_name", getattr(func, "__name__", "<unknown>")) |
| | for inp, expected in pairs: |
| | out = func(inp) |
| | assert out == expected, pair_fail_msg.format(name, inp, expected, out) |
| |
|
| |
|
| | MyStringIO = StringIO |
| |
|
| | _re_type = type(re.compile(r'')) |
| |
|
| | notprinted_msg = """Did not find {0!r} in printed output (on {1}): |
| | ------- |
| | {2!s} |
| | ------- |
| | """ |
| |
|
| | class AssertPrints(object): |
| | """Context manager for testing that code prints certain text. |
| | |
| | Examples |
| | -------- |
| | >>> with AssertPrints("abc", suppress=False): |
| | ... print("abcd") |
| | ... print("def") |
| | ... |
| | abcd |
| | def |
| | """ |
| | def __init__(self, s, channel='stdout', suppress=True): |
| | self.s = s |
| | if isinstance(self.s, (str, _re_type)): |
| | self.s = [self.s] |
| | self.channel = channel |
| | self.suppress = suppress |
| |
|
| | def __enter__(self): |
| | self.orig_stream = getattr(sys, self.channel) |
| | self.buffer = MyStringIO() |
| | self.tee = Tee(self.buffer, channel=self.channel) |
| | setattr(sys, self.channel, self.buffer if self.suppress else self.tee) |
| |
|
| | def __exit__(self, etype, value, traceback): |
| | __tracebackhide__ = True |
| |
|
| | try: |
| | if value is not None: |
| | |
| | return False |
| | self.tee.flush() |
| | setattr(sys, self.channel, self.orig_stream) |
| | printed = self.buffer.getvalue() |
| | for s in self.s: |
| | if isinstance(s, _re_type): |
| | assert s.search(printed), notprinted_msg.format(s.pattern, self.channel, printed) |
| | else: |
| | assert s in printed, notprinted_msg.format(s, self.channel, printed) |
| | return False |
| | finally: |
| | self.tee.close() |
| |
|
| | printed_msg = """Found {0!r} in printed output (on {1}): |
| | ------- |
| | {2!s} |
| | ------- |
| | """ |
| |
|
| | class AssertNotPrints(AssertPrints): |
| | """Context manager for checking that certain output *isn't* produced. |
| | |
| | Counterpart of AssertPrints""" |
| | def __exit__(self, etype, value, traceback): |
| | __tracebackhide__ = True |
| |
|
| | try: |
| | if value is not None: |
| | |
| | self.tee.close() |
| | return False |
| | self.tee.flush() |
| | setattr(sys, self.channel, self.orig_stream) |
| | printed = self.buffer.getvalue() |
| | for s in self.s: |
| | if isinstance(s, _re_type): |
| | assert not s.search(printed),printed_msg.format( |
| | s.pattern, self.channel, printed) |
| | else: |
| | assert s not in printed, printed_msg.format( |
| | s, self.channel, printed) |
| | return False |
| | finally: |
| | self.tee.close() |
| |
|
| | @contextmanager |
| | def mute_warn(): |
| | from IPython.utils import warn |
| | save_warn = warn.warn |
| | warn.warn = lambda *a, **kw: None |
| | try: |
| | yield |
| | finally: |
| | warn.warn = save_warn |
| |
|
| | @contextmanager |
| | def make_tempfile(name): |
| | """Create an empty, named, temporary file for the duration of the context.""" |
| | open(name, "w", encoding="utf-8").close() |
| | try: |
| | yield |
| | finally: |
| | os.unlink(name) |
| |
|
| | def fake_input(inputs): |
| | """Temporarily replace the input() function to return the given values |
| | |
| | Use as a context manager: |
| | |
| | with fake_input(['result1', 'result2']): |
| | ... |
| | |
| | Values are returned in order. If input() is called again after the last value |
| | was used, EOFError is raised. |
| | """ |
| | it = iter(inputs) |
| | def mock_input(prompt=''): |
| | try: |
| | return next(it) |
| | except StopIteration as e: |
| | raise EOFError('No more inputs given') from e |
| |
|
| | return patch('builtins.input', mock_input) |
| |
|
| | def help_output_test(subcommand=''): |
| | """test that `ipython [subcommand] -h` works""" |
| | cmd = get_ipython_cmd() + [subcommand, '-h'] |
| | out, err, rc = get_output_error_code(cmd) |
| | assert rc == 0, err |
| | assert "Traceback" not in err |
| | assert "Options" in out |
| | assert "--help-all" in out |
| | return out, err |
| |
|
| |
|
| | def help_all_output_test(subcommand=''): |
| | """test that `ipython [subcommand] --help-all` works""" |
| | cmd = get_ipython_cmd() + [subcommand, '--help-all'] |
| | out, err, rc = get_output_error_code(cmd) |
| | assert rc == 0, err |
| | assert "Traceback" not in err |
| | assert "Options" in out |
| | assert "Class" in out |
| | return out, err |
| |
|
| |
|