iptest.py
285 lines
| 9.7 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r1574 | # -*- coding: utf-8 -*- | |
"""IPython Test Suite Runner. | |||
Fernando Perez
|
r1851 | ||
Brian Granger
|
r1972 | This module provides a main entry point to a user script to test IPython | |
itself from the command line. There are two ways of running this script: | |||
1. With the syntax `iptest all`. This runs our entire test suite by | |||
calling this script (with different arguments) or trial recursively. This | |||
causes modules and package to be tested in different processes, using nose | |||
or trial where appropriate. | |||
2. With the regular nose syntax, like `iptest -vvs IPython`. In this form | |||
the script simply calls nose, but with special command line flags and | |||
plugins loaded. | |||
For now, this script requires that both nose and twisted are installed. This | |||
will change in the future. | |||
Fernando Perez
|
r1574 | """ | |
Fernando Perez
|
r1851 | #----------------------------------------------------------------------------- | |
# Module imports | |||
#----------------------------------------------------------------------------- | |||
Brian Granger
|
r1972 | import os | |
import os.path as path | |||
Fernando Perez
|
r1574 | import sys | |
Brian Granger
|
r1972 | import subprocess | |
import time | |||
Fernando Perez
|
r1574 | import warnings | |
import nose.plugins.builtin | |||
Fernando Perez
|
r1851 | from nose.core import TestProgram | |
Fernando Perez
|
r1574 | ||
Brian Granger
|
r2039 | from IPython.utils.platutils import find_cmd | |
Fernando Perez
|
r1574 | from IPython.testing.plugin.ipdoctest import IPythonDoctest | |
Brian Granger
|
r1979 | pjoin = path.join | |
Fernando Perez
|
r1851 | #----------------------------------------------------------------------------- | |
Administrator
|
r1981 | # Logic for skipping doctests | |
Fernando Perez
|
r1851 | #----------------------------------------------------------------------------- | |
Administrator
|
r1981 | def test_for(mod): | |
"""Test to see if mod is importable.""" | |||
try: | |||
__import__(mod) | |||
except ImportError: | |||
return False | |||
else: | |||
return True | |||
have_curses = test_for('_curses') | |||
have_wx = test_for('wx') | |||
have_zi = test_for('zope.interface') | |||
have_twisted = test_for('twisted') | |||
have_foolscap = test_for('foolscap') | |||
have_objc = test_for('objc') | |||
have_pexpect = test_for('pexpect') | |||
Brian Granger
|
r2083 | have_gtk = test_for('gtk') | |
have_gobject = test_for('gobject') | |||
Administrator
|
r1981 | ||
Administrator
|
r1980 | ||
Brian Granger
|
r2079 | def make_exclude(): | |
# For the IPythonDoctest plugin, we need to exclude certain patterns that cause | |||
# testing problems. We should strive to minimize the number of skipped | |||
# modules, since this means untested code. As the testing machinery | |||
# solidifies, this list should eventually become empty. | |||
EXCLUDE = [pjoin('IPython', 'external'), | |||
pjoin('IPython', 'frontend', 'process', 'winprocess.py'), | |||
pjoin('IPython_doctest_plugin'), | |||
pjoin('IPython', 'extensions', 'ipy_'), | |||
pjoin('IPython', 'extensions', 'clearcmd'), | |||
pjoin('IPython', 'extensions', 'PhysicalQInteractive'), | |||
pjoin('IPython', 'extensions', 'scitedirector'), | |||
pjoin('IPython', 'extensions', 'numeric_formats'), | |||
pjoin('IPython', 'testing', 'attic'), | |||
pjoin('IPython', 'testing', 'tools'), | |||
Brian Granger
|
r2083 | pjoin('IPython', 'testing', 'mkdoctests'), | |
pjoin('IPython', 'lib', 'inputhook') | |||
Brian Granger
|
r2079 | ] | |
if not have_wx: | |||
EXCLUDE.append(pjoin('IPython', 'extensions', 'igrid')) | |||
EXCLUDE.append(pjoin('IPython', 'gui')) | |||
EXCLUDE.append(pjoin('IPython', 'frontend', 'wx')) | |||
Brian Granger
|
r2083 | EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookwx')) | |
if not have_gtk or not have_gobject: | |||
EXCLUDE.append(pjoin('IPython', 'lib', 'inputhookgtk')) | |||
Brian Granger
|
r2079 | ||
if not have_objc: | |||
EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa')) | |||
if not have_curses: | |||
EXCLUDE.append(pjoin('IPython', 'extensions', 'ibrowse')) | |||
if not sys.platform == 'win32': | |||
EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_win32')) | |||
# These have to be skipped on win32 because the use echo, rm, cd, etc. | |||
# See ticket https://bugs.launchpad.net/bugs/366982 | |||
if sys.platform == 'win32': | |||
EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip')) | |||
EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample')) | |||
if not os.name == 'posix': | |||
EXCLUDE.append(pjoin('IPython', 'utils', 'platutils_posix')) | |||
if not have_pexpect: | |||
EXCLUDE.append(pjoin('IPython', 'scripts', 'irunner')) | |||
# Skip shell always because of a bug in FakeModule. | |||
EXCLUDE.append(pjoin('IPython', 'core', 'shell')) | |||
# This is needed for the reg-exp to match on win32 in the ipdoctest plugin. | |||
if sys.platform == 'win32': | |||
EXCLUDE = [s.replace('\\','\\\\') for s in EXCLUDE] | |||
return EXCLUDE | |||
Administrator
|
r1980 | ||
Fernando Perez
|
r1851 | #----------------------------------------------------------------------------- | |
# Functions and classes | |||
#----------------------------------------------------------------------------- | |||
Brian Granger
|
r1972 | def run_iptest(): | |
"""Run the IPython test suite using nose. | |||
This function is called when this script is **not** called with the form | |||
`iptest all`. It simply calls nose with appropriate command line flags | |||
and accepts all of the standard nose arguments. | |||
Fernando Perez
|
r1574 | """ | |
warnings.filterwarnings('ignore', | |||
'This will be removed soon. Use IPython.testing.util instead') | |||
Brian Granger
|
r1888 | argv = sys.argv + [ | |
# Loading ipdoctest causes problems with Twisted. | |||
# I am removing this as a temporary fix to get the | |||
# test suite back into working shape. Our nose | |||
# plugin needs to be gone through with a fine | |||
# toothed comb to find what is causing the problem. | |||
Fernando Perez
|
r1968 | '--with-ipdoctest', | |
Fernando Perez
|
r1910 | '--ipdoctest-tests','--ipdoctest-extension=txt', | |
Fernando Perez
|
r1761 | '--detailed-errors', | |
Fernando Perez
|
r1574 | ||
Fernando Perez
|
r1761 | # We add --exe because of setuptools' imbecility (it | |
# blindly does chmod +x on ALL files). Nose does the | |||
# right thing and it tries to avoid executables, | |||
# setuptools unfortunately forces our hand here. This | |||
# has been discussed on the distutils list and the | |||
# setuptools devs refuse to fix this problem! | |||
'--exe', | |||
] | |||
Fernando Perez
|
r1574 | ||
Fernando Perez
|
r1848 | # Detect if any tests were required by explicitly calling an IPython | |
# submodule or giving a specific path | |||
has_tests = False | |||
Fernando Perez
|
r1574 | for arg in sys.argv: | |
Fernando Perez
|
r1848 | if 'IPython' in arg or arg.endswith('.py') or \ | |
(':' in arg and '.py' in arg): | |||
has_tests = True | |||
Fernando Perez
|
r1574 | break | |
Fernando Perez
|
r1910 | ||
Fernando Perez
|
r1848 | # If nothing was specifically requested, test full IPython | |
if not has_tests: | |||
Fernando Perez
|
r1574 | argv.append('IPython') | |
Fernando Perez
|
r1910 | # Construct list of plugins, omitting the existing doctest plugin, which | |
# ours replaces (and extends). | |||
Brian Granger
|
r2079 | EXCLUDE = make_exclude() | |
Fernando Perez
|
r1851 | plugins = [IPythonDoctest(EXCLUDE)] | |
Fernando Perez
|
r1761 | for p in nose.plugins.builtin.plugins: | |
plug = p() | |||
if plug.name == 'doctest': | |||
continue | |||
plugins.append(plug) | |||
Fernando Perez
|
r1574 | TestProgram(argv=argv,plugins=plugins) | |
Brian Granger
|
r1972 | ||
class IPTester(object): | |||
"""Call that calls iptest or trial in a subprocess. | |||
""" | |||
def __init__(self,runner='iptest',params=None): | |||
""" """ | |||
if runner == 'iptest': | |||
self.runner = ['iptest','-v'] | |||
else: | |||
Administrator
|
r1980 | self.runner = [find_cmd('trial')] | |
Brian Granger
|
r1972 | if params is None: | |
params = [] | |||
if isinstance(params,str): | |||
params = [params] | |||
self.params = params | |||
# Assemble call | |||
self.call_args = self.runner+self.params | |||
def run(self): | |||
"""Run the stored commands""" | |||
return subprocess.call(self.call_args) | |||
def make_runners(): | |||
Brian Granger
|
r2079 | """Define the top-level packages that need to be tested. | |
Brian Granger
|
r1972 | """ | |
Administrator
|
r1986 | ||
Brian Granger
|
r2079 | nose_packages = ['config', 'core', 'extensions', | |
'frontend', 'lib', 'quarantine', | |||
'scripts', 'testing', 'utils'] | |||
trial_packages = ['kernel'] | |||
Brian Granger
|
r1972 | ||
Administrator
|
r1981 | if have_wx: | |
Brian Granger
|
r2079 | nose_packages.append('gui') | |
Administrator
|
r1981 | ||
Brian Granger
|
r2079 | nose_packages = ['IPython.%s' % m for m in nose_packages ] | |
trial_packages = ['IPython.%s' % m for m in trial_packages ] | |||
Brian Granger
|
r1972 | ||
# Make runners | |||
Brian Granger
|
r2079 | runners = dict() | |
Brian Granger
|
r1974 | ||
Brian Granger
|
r2079 | nose_runners = dict(zip(nose_packages, [IPTester(params=v) for v in nose_packages])) | |
Administrator
|
r1981 | if have_zi and have_twisted and have_foolscap: | |
Brian Granger
|
r2079 | trial_runners = dict(zip(trial_packages, [IPTester('trial',params=v) for v in trial_packages])) | |
runners.update(nose_runners) | |||
runners.update(trial_runners) | |||
Brian Granger
|
r1972 | ||
return runners | |||
def run_iptestall(): | |||
"""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 or twisted.trial appropriately. | |||
""" | |||
Brian Granger
|
r2079 | ||
Brian Granger
|
r1972 | runners = make_runners() | |
Brian Granger
|
r2079 | ||
Brian Granger
|
r1972 | # Run all test runners, tracking execution time | |
failed = {} | |||
t_start = time.time() | |||
for name,runner in runners.iteritems(): | |||
print '*'*77 | |||
Brian Granger
|
r2079 | print 'IPython test set:', name | |
Brian Granger
|
r1972 | res = runner.run() | |
if res: | |||
failed[name] = res | |||
t_end = time.time() | |||
t_tests = t_end - t_start | |||
nrunners = len(runners) | |||
nfail = len(failed) | |||
# summarize results | |||
print '*'*77 | |||
print 'Ran %s test sets in %.3fs' % (nrunners, t_tests) | |||
if not failed: | |||
print 'OK' | |||
else: | |||
# If anything went wrong, point out what command to rerun manually to | |||
# see the actual errors and individual summary | |||
print 'ERROR - %s out of %s test sets failed.' % (nfail, nrunners) | |||
for name in failed: | |||
failed_runner = runners[name] | |||
print '-'*40 | |||
print 'Runner failed:',name | |||
print 'You may wish to rerun this one individually, with:' | |||
print ' '.join(failed_runner.call_args) | |||
def main(): | |||
Brian Granger
|
r1990 | if len(sys.argv) == 1: | |
Brian Granger
|
r1972 | run_iptestall() | |
else: | |||
Brian Granger
|
r1990 | if sys.argv[1] == 'all': | |
run_iptestall() | |||
else: | |||
run_iptest() | |||
Brian Granger
|
r1972 | ||
if __name__ == '__main__': | |||
main() |