##// END OF EJS Templates
Catpure tuple-formed dict accesses
Catpure tuple-formed dict accesses

File last commit:

r25117:ea4ab3aa
r25835:e9bfc9fd
Show More
iptestcontroller.py
491 lines | 16.6 KiB | text/x-python | PythonLexer
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 # -*- coding: utf-8 -*-
"""IPython Test Process Controller
This module runs one or more subprocesses which will actually run the IPython
test suite.
"""
MinRK
Don't hang tests if notebook server fails to terminate.
r16509 # Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Start refactoring test machinery
r12608 import argparse
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 import multiprocessing.pool
import os
Jeroen Demeyer
gh-7053: check for OSError during rehashx()
r19132 import stat
Thomas Kluyver
Better coverage reporting
r12612 import shutil
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 import signal
import sys
import subprocess
import time
MinRK
refuse to run js tests with phantom + tornado 4...
r18356 from .iptest import (
have, test_group_names as py_test_group_names, test_sections, StreamCapturer,
)
Thomas Kluyver
Clean up formatting sys info for test report
r13173 from IPython.utils.path import compress_user
Hugo
Remove redundant Python 2 code
r24010 from IPython.utils.py3compat import decode
Thomas Kluyver
Clean up formatting sys info for test report
r13173 from IPython.utils.sysinfo import get_sys_info
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 from IPython.utils.tempdir import TemporaryDirectory
Matthias Bussonnier
Cleanup iptest controller from old logic....
r25080 class TestController:
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 """Run tests in a subprocess
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 """
Thomas Kluyver
Start refactoring test machinery
r12608 #: str, IPython test suite to be executed.
section = None
#: list, command line arguments to be executed
cmd = None
#: dict, extra environment variables to set for the subprocess
env = None
#: list, TemporaryDirectory instances to clear up when the process finishes
dirs = None
#: subprocess.Popen instance
process = None
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 #: str, process stdout+stderr
stdout = None
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 def __init__(self):
self.cmd = []
Thomas Kluyver
Start refactoring test machinery
r12608 self.env = {}
self.dirs = []
Matthias Bussonnier
Also run test with Pytest....
r25117 def setUp(self):
Thomas Kluyver
Refactor TestController API to allow it to display extra info....
r15799 """Create temporary directories etc.
This is only called when we know the test group will be run. Things
created here may be cleaned up by self.cleanup().
"""
pass
Min RK
distinguish capture_output from buffer_output...
r18692 def launch(self, buffer_output=False, capture_output=False):
Thomas Kluyver
Start refactoring test machinery
r12608 # print('*** ENV:', self.env) # dbg
# print('*** CMD:', self.cmd) # dbg
env = os.environ.copy()
env.update(self.env)
Min RK
distinguish capture_output from buffer_output...
r18692 if buffer_output:
capture_output = True
self.stdout_capturer = c = StreamCapturer(echo=not buffer_output)
c.start()
stdout = c.writefd if capture_output else None
stderr = subprocess.STDOUT if capture_output else None
self.process = subprocess.Popen(self.cmd, stdout=stdout,
stderr=stderr, env=env)
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Improve test output
r12610 def wait(self):
Min RK
distinguish capture_output from buffer_output...
r18692 self.process.wait()
self.stdout_capturer.halt()
self.stdout = self.stdout_capturer.get_buffer()
Thomas Kluyver
Improve test output
r12610 return self.process.returncode
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Fix cleanup of test controller
r12609 def cleanup_process(self):
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 """Cleanup on exit by killing any leftover processes."""
Thomas Kluyver
Start refactoring test machinery
r12608 subp = self.process
if subp is None or (subp.poll() is not None):
return # Process doesn't exist, or is already dead.
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Start refactoring test machinery
r12608 try:
print('Cleaning up stale PID: %d' % subp.pid)
subp.kill()
except: # (OSError, WindowsError) ?
# This is just a best effort, if we fail or the process was
# really gone, ignore it.
pass
else:
for i in range(10):
if subp.poll() is None:
time.sleep(0.1)
else:
break
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Start refactoring test machinery
r12608 if subp.poll() is None:
# The process did not die...
print('... failed. Manual cleanup may be required.')
Thomas Kluyver
Separate out machinery for running JS tests
r13279
Thomas Kluyver
Fix cleanup of test controller
r12609 def cleanup(self):
"Kill process if it's still alive, and clean up temporary directories"
self.cleanup_process()
Thomas Kluyver
Start refactoring test machinery
r12608 for td in self.dirs:
td.cleanup()
Thomas Kluyver
Separate out machinery for running JS tests
r13279
Thomas Kluyver
Start refactoring test machinery
r12608 __del__ = cleanup
Jonathan Frederic
Fixed spaces in ipcontroller
r16842
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 class PyTestController(TestController):
"""Run Python tests using IPython.testing.iptest"""
#: str, Python command to execute in subprocess
pycmd = None
Thomas Kluyver
Move configuration of Python test controllers into setup()
r15967 def __init__(self, section, options):
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 """Create new test runner."""
TestController.__init__(self)
self.section = section
# pycmd is put into cmd[2] in PyTestController.launch()
self.cmd = [sys.executable, '-c', None, section]
self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
Thomas Kluyver
Move configuration of Python test controllers into setup()
r15967 self.options = options
Thomas Kluyver
Refactor TestController API to allow it to display extra info....
r15799
def setup(self):
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 ipydir = TemporaryDirectory()
self.dirs.append(ipydir)
self.env['IPYTHONDIR'] = ipydir.name
self.workingdir = workingdir = TemporaryDirectory()
self.dirs.append(workingdir)
self.env['IPTEST_WORKING_DIR'] = workingdir.name
# This means we won't get odd effects from our own matplotlib config
self.env['MPLCONFIGDIR'] = workingdir.name
Jeroen Demeyer
gh-7044: set TMPDIR to workingdir in tests
r19240 # For security reasons (http://bugs.python.org/issue16202), use
# a temporary directory to which other users have no access.
self.env['TMPDIR'] = workingdir.name
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617
Jeroen Demeyer
gh-7053: check for OSError during rehashx()
r19132 # Add a non-accessible directory to PATH (see gh-7053)
noaccess = os.path.join(self.workingdir.name, "_no_access_")
self.noaccess = noaccess
os.mkdir(noaccess, 0)
PATH = os.environ.get('PATH', '')
if PATH:
PATH = noaccess + os.pathsep + PATH
else:
PATH = noaccess
self.env['PATH'] = PATH
Thomas Kluyver
Move configuration of Python test controllers into setup()
r15967 # From options:
if self.options.xunit:
self.add_xunit()
if self.options.coverage:
self.add_coverage()
self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams
self.cmd.extend(self.options.extra_args)
Jeroen Demeyer
gh-7053: check for OSError during rehashx()
r19132 def cleanup(self):
"""
Make the non-accessible directory created in setup() accessible
again, otherwise deleting the workingdir will fail.
"""
os.chmod(self.noaccess, stat.S_IRWXU)
TestController.cleanup(self)
Thomas Kluyver
Separate out machinery for running JS tests
r13279 @property
def will_run(self):
try:
return test_sections[self.section].will_run
except KeyError:
return True
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 def add_xunit(self):
xunit_file = os.path.abspath(self.section + '.xunit.xml')
self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
def add_coverage(self):
Thomas Kluyver
Unify entry points for iptest
r12623 try:
sources = test_sections[self.section].includes
except KeyError:
sources = ['IPython']
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 coverage_rc = ("[run]\n"
"data_file = {data_file}\n"
"source =\n"
" {source}\n"
).format(data_file=os.path.abspath('.coverage.'+self.section),
Thomas Kluyver
Unify entry points for iptest
r12623 source="\n ".join(sources))
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 config_file = os.path.join(self.workingdir.name, '.coveragerc')
with open(config_file, 'w') as f:
f.write(coverage_rc)
self.env['COVERAGE_PROCESS_START'] = config_file
self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
Thomas Kluyver
Refactor TestController API to allow it to display extra info....
r15799 def launch(self, buffer_output=False):
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617 self.cmd[2] = self.pycmd
Thomas Kluyver
Refactor TestController API to allow it to display extra info....
r15799 super(PyTestController, self).launch(buffer_output=buffer_output)
Thomas Kluyver
Separate TestController base class which could be used for JS tests
r12617
Jonathan Frederic
Fixed spaces in ipcontroller
r16842
Paul Ivanov
JSController working, `iptest js` runs casperjs
r13264 def prepare_controllers(options):
Paul Ivanov
simplify prepare_controllers logic
r13262 """Returns two lists of TestController instances, those to run, and those
not to run."""
Paul Ivanov
JSController working, `iptest js` runs casperjs
r13264 testgroups = options.testgroups
Min RK
remove javascript tests
r21241 if not testgroups:
testgroups = py_test_group_names
Paul Ivanov
simplify prepare_controllers logic
r13262
Min RK
remove javascript tests
r21241 controllers = [PyTestController(name, options) for name in testgroups]
Thomas Kluyver
Separate out machinery for running JS tests
r13279
Paul Ivanov
simplify prepare_controllers logic
r13262 to_run = [c for c in controllers if c.will_run]
not_run = [c for c in controllers if not c.will_run]
Thomas Kluyver
Also show which test groups didn't run
r12616 return to_run, not_run
Thomas Kluyver
Start refactoring test machinery
r12608
Thomas Kluyver
Refactor TestController API to allow it to display extra info....
r15799 def do_run(controller, buffer_output=True):
"""Setup and run a test controller.
If buffer_output is True, no output is displayed, to avoid it appearing
interleaved. In this case, the caller is responsible for displaying test
output on failure.
Returns
-------
controller : TestController
The same controller as passed in, as a convenience for using map() type
APIs.
exitcode : int
The exit code of the test subprocess. Non-zero indicates failure.
"""
Thomas Kluyver
Start refactoring test machinery
r12608 try:
try:
Thomas Kluyver
Refactor TestController API to allow it to display extra info....
r15799 controller.setup()
controller.launch(buffer_output=buffer_output)
Thomas Kluyver
Start refactoring test machinery
r12608 except Exception:
import traceback
traceback.print_exc()
return controller, 1 # signal failure
Thomas Kluyver
Separate out machinery for running JS tests
r13279
Thomas Kluyver
Improve test output
r12610 exitcode = controller.wait()
Thomas Kluyver
Start refactoring test machinery
r12608 return controller, exitcode
Thomas Kluyver
Separate out machinery for running JS tests
r13279
Thomas Kluyver
Start refactoring test machinery
r12608 except KeyboardInterrupt:
return controller, -signal.SIGINT
Thomas Kluyver
Improve test output
r12610 finally:
controller.cleanup()
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
def report():
"""Return a string with a summary report of test-related variables."""
Thomas Kluyver
Clean up formatting sys info for test report
r13173 inf = get_sys_info()
out = []
def _add(name, value):
out.append((name, value))
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Clean up formatting sys info for test report
r13173 _add('IPython version', inf['ipython_version'])
_add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
_add('IPython package', compress_user(inf['ipython_path']))
_add('Python version', inf['sys_version'].replace('\n',''))
_add('sys.executable', compress_user(inf['sys_executable']))
_add('Platform', inf['platform'])
width = max(len(n) for (n,v) in out)
out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
avail = []
not_avail = []
for k, is_avail in have.items():
if is_avail:
avail.append(k)
else:
not_avail.append(k)
if avail:
out.append('\nTools and libraries available at test time:\n')
avail.sort()
out.append(' ' + ' '.join(avail)+'\n')
if not_avail:
out.append('\nTools and libraries NOT available at test time:\n')
not_avail.sort()
out.append(' ' + ' '.join(not_avail)+'\n')
return ''.join(out)
Thomas Kluyver
Unify entry points for iptest
r12623 def run_iptestall(options):
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 """Run the entire IPython test suite by calling nose and trial.
This function constructs :class:`IPTester` instances for all IPython
modules and package and then runs each of them. This causes the modules
and packages of IPython to be tested each in their own subprocess using
nose.
Thomas Kluyver
Separate out machinery for running JS tests
r13279
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 Parameters
----------
Thomas Kluyver
Unify entry points for iptest
r12623
All parameters are passed as attributes of the options object.
testgroups : list of str
Run only these sections of the test suite. If empty, run all the available
sections.
fast : int or None
Run the test suite in parallel, using n simultaneous processes. If None
is passed, one process is used per CPU core. Default 1 (i.e. sequential)
inc_slow : bool
Min RK
remove ipython_parallel
r21225 Include slow tests. By default, these tests aren't run.
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Jonathan Frederic
Add --url option to iptest
r18433 url : unicode
Address:port to use when running the JS tests.
Thomas Kluyver
Unify entry points for iptest
r12623 xunit : bool
Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
coverage : bool or str
Measure code coverage from tests. True will store the raw coverage data,
or pass 'html' or 'xml' to get reports.
Thomas Kluyver
Allow passing extra arguments to iptest through for nose
r12638
extra_args : list
Extra arguments to pass to the test subprocesses, e.g. '-v'
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 """
Paul Ivanov
JSController working, `iptest js` runs casperjs
r13264 to_run, not_run = prepare_controllers(options)
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
Thomas Kluyver
Also show which test groups didn't run
r12616 def justify(ltext, rtext, width=70, fill='-'):
ltext += ' '
rtext = (' ' + rtext).rjust(width - len(ltext), fill)
return ltext + rtext
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 # Run all test runners, tracking execution time
failed = []
t_start = time.time()
Thomas Kluyver
Unify entry points for iptest
r12623 print()
if options.fast == 1:
# This actually means sequential, i.e. with 1 job
Thomas Kluyver
Also show which test groups didn't run
r12616 for controller in to_run:
Thomas Kluyver
Shorten leftover references to 'IPython test group'
r15893 print('Test group:', controller.section)
Thomas Kluyver
Hopefully fix ordering of output on ShiningPanda
r12783 sys.stdout.flush() # Show in correct order when output is piped
Thomas Kluyver
Refactor TestController API to allow it to display extra info....
r15799 controller, res = do_run(controller, buffer_output=False)
Thomas Kluyver
Improve test output
r12610 if res:
failed.append(controller)
if res == -signal.SIGINT:
print("Interrupted")
break
print()
else:
Thomas Kluyver
Unify entry points for iptest
r12623 # Run tests concurrently
Thomas Kluyver
Improve test output
r12610 try:
Thomas Kluyver
Unify entry points for iptest
r12623 pool = multiprocessing.pool.ThreadPool(options.fast)
Thomas Kluyver
Also show which test groups didn't run
r12616 for (controller, res) in pool.imap_unordered(do_run, to_run):
res_string = 'OK' if res == 0 else 'FAILED'
Thomas Kluyver
Some gardening on iptest result reporting
r15795 print(justify('Test group: ' + controller.section, res_string))
Thomas Kluyver
Improve test output
r12610 if res:
Hugo
Remove redundant Python 2 code
r24010 print(decode(controller.stdout))
Thomas Kluyver
Improve test output
r12610 failed.append(controller)
if res == -signal.SIGINT:
print("Interrupted")
break
except KeyboardInterrupt:
return
Thomas Kluyver
Separate out machinery for running JS tests
r13279
Thomas Kluyver
Also show which test groups didn't run
r12616 for controller in not_run:
Thomas Kluyver
Shorten leftover references to 'IPython test group'
r15893 print(justify('Test group: ' + controller.section, 'NOT RUN'))
Thomas Kluyver
Also show which test groups didn't run
r12616
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 t_end = time.time()
t_tests = t_end - t_start
Thomas Kluyver
Also show which test groups didn't run
r12616 nrunners = len(to_run)
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 nfail = len(failed)
# summarize results
Thomas Kluyver
Unify entry points for iptest
r12623 print('_'*70)
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 print('Test suite completed for system with the following information:')
print(report())
Thomas Kluyver
More concise test summary info
r12971 took = "Took %.3fs." % t_tests
Thomas Kluyver
Unify entry points for iptest
r12623 print('Status: ', end='')
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 if not failed:
Thomas Kluyver
Restore mention of number of test groups after success
r12972 print('OK (%d test groups).' % nrunners, took)
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 else:
# If anything went wrong, point out what command to rerun manually to
# see the actual errors and individual summary
Thomas Kluyver
Unify entry points for iptest
r12623 failed_sections = [c.section for c in failed]
print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
Thomas Kluyver
More concise test summary info
r12971 nrunners, ', '.join(failed_sections)), took)
Thomas Kluyver
Unify entry points for iptest
r12623 print()
print('You may wish to rerun these, with:')
print(' iptest', *failed_sections)
print()
if options.coverage:
Matthias Bussonnier
enable test coverage on coveralls
r19539 from coverage import coverage, CoverageException
Thomas Kluyver
Better coverage reporting
r12612 cov = coverage(data_file='.coverage')
cov.combine()
cov.save()
# Coverage HTML report
Thomas Kluyver
Unify entry points for iptest
r12623 if options.coverage == 'html':
Thomas Kluyver
Better coverage reporting
r12612 html_dir = 'ipy_htmlcov'
shutil.rmtree(html_dir, ignore_errors=True)
print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
sys.stdout.flush()
# Custom HTML reporter to clean up module names.
from coverage.html import HtmlReporter
class CustomHtmlReporter(HtmlReporter):
def find_code_units(self, morfs):
super(CustomHtmlReporter, self).find_code_units(morfs)
for cu in self.code_units:
nameparts = cu.name.split(os.sep)
if 'IPython' not in nameparts:
continue
ix = nameparts.index('IPython')
cu.name = '.'.join(nameparts[ix:])
# Reimplement the html_report method with our custom reporter
Min RK
update coverage html report to coverage 4.0...
r21705 cov.get_data()
Thomas Kluyver
Fix exclusion of tests directories from coverage reports
r15509 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
Thomas Kluyver
Better coverage reporting
r12612 html_title='IPython test coverage',
)
reporter = CustomHtmlReporter(cov, cov.config)
reporter.report(None)
print('done.')
# Coverage XML report
Thomas Kluyver
Unify entry points for iptest
r12623 elif options.coverage == 'xml':
Matthias Bussonnier
enable test coverage on coveralls
r19539 try:
cov.xml_report(outfile='ipy_coverage.xml')
Matthias Bussonnier
Print that generating coverage report failed when runnign js test only
r19711 except CoverageException as e:
print('Generating coverage report failed. Are you running javascript tests only?')
Matthias Bussonnier
also print traceback
r19715 import traceback
traceback.print_exc()
Thomas Kluyver
Better coverage reporting
r12612
if failed:
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607 # Ensure that our exit code indicates failure
sys.exit(1)
Thomas Kluyver
Restore the ability to run tests from a function.
r13740 argparser = argparse.ArgumentParser(description='Run IPython test suite')
argparser.add_argument('testgroups', nargs='*',
help='Run specified groups of tests. If omitted, run '
'all tests.')
argparser.add_argument('--all', action='store_true',
help='Include slow tests not run by default.')
argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
Thomas Kluyver
Improve description of -j option to iptest
r15894 help='Run test sections in parallel. This starts as many '
'processes as you have cores, or you can specify a number.')
Thomas Kluyver
Restore the ability to run tests from a function.
r13740 argparser.add_argument('--xunit', action='store_true',
help='Produce Xunit XML results')
argparser.add_argument('--coverage', nargs='?', const=True, default=False,
help="Measure test coverage. Specify 'html' or "
"'xml' to get reports.")
Thomas Kluyver
Option to spew subprocess streams during tests...
r13824 argparser.add_argument('--subproc-streams', default='capture',
help="What to do with stdout/stderr from subprocesses. "
"'capture' (default), 'show' and 'discard' are the options.")
Thomas Kluyver
Restore the ability to run tests from a function.
r13740
def default_options():
"""Get an argparse Namespace object with the default arguments, to pass to
:func:`run_iptestall`.
"""
options = argparser.parse_args([])
options.extra_args = []
return options
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
def main():
Konrad Hinsen
Print a warning when iptest is run from the IPython source directory
r15295 # iptest doesn't work correctly if the working directory is the
# root of the IPython source tree. Tell the user to avoid
# frustration.
if os.path.exists(os.path.join(os.getcwd(),
'IPython', 'testing', '__main__.py')):
print("Don't run iptest from the IPython source directory",
file=sys.stderr)
sys.exit(1)
Thomas Kluyver
Allow passing extra arguments to iptest through for nose
r12638 # Arguments after -- should be passed through to nose. Argparse treats
# everything after -- as regular positional arguments, so we separate them
# first.
try:
ix = sys.argv.index('--')
except ValueError:
to_parse = sys.argv[1:]
extra_args = []
else:
to_parse = sys.argv[1:ix]
extra_args = sys.argv[ix+1:]
Thomas Kluyver
Restore the ability to run tests from a function.
r13740 options = argparser.parse_args(to_parse)
Thomas Kluyver
Allow passing extra arguments to iptest through for nose
r12638 options.extra_args = extra_args
Thomas Kluyver
Start refactoring test machinery
r12608
Thomas Kluyver
Unify entry points for iptest
r12623 run_iptestall(options)
Thomas Kluyver
Split out iptestcontroller to control test process.
r12607
if __name__ == '__main__':
main()