iptest.py
346 lines
| 12.5 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 | ||
Fernando Perez
|
r2111 | import tempfile | ||
Brian Granger
|
r1972 | import time | ||
Fernando Perez
|
r1574 | import warnings | ||
import nose.plugins.builtin | ||||
Fernando Perez
|
r1851 | from nose.core import TestProgram | ||
Fernando Perez
|
r1574 | |||
Administrator
|
r1980 | from IPython.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') | ||||
Fernando Perez
|
r2091 | have_wx_aui = test_for('wx.aui') | ||
Administrator
|
r1981 | 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') | ||||
Fernando Perez
|
r1851 | # 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. | ||||
Brian Granger
|
r1979 | EXCLUDE = [pjoin('IPython', 'external'), | ||
pjoin('IPython', 'frontend', 'process', 'winprocess.py'), | ||||
pjoin('IPython_doctest_plugin'), | ||||
pjoin('IPython', 'Gnuplot'), | ||||
pjoin('IPython', 'Extensions', 'ipy_'), | ||||
Fernando Perez
|
r2091 | pjoin('IPython', 'Extensions', 'PhysicalQInput'), | ||
Administrator
|
r1980 | pjoin('IPython', 'Extensions', 'PhysicalQInteractive'), | ||
Fernando Perez
|
r2091 | pjoin('IPython', 'Extensions', 'InterpreterPasteInput'), | ||
Brian Granger
|
r1979 | pjoin('IPython', 'Extensions', 'scitedirector'), | ||
pjoin('IPython', 'Extensions', 'numeric_formats'), | ||||
pjoin('IPython', 'testing', 'attic'), | ||||
Brian Granger
|
r1982 | pjoin('IPython', 'testing', 'tutils'), | ||
Administrator
|
r1984 | pjoin('IPython', 'testing', 'tools'), | ||
Fernando Perez
|
r2091 | pjoin('IPython', 'testing', 'mkdoctests'), | ||
Fernando Perez
|
r1851 | ] | ||
Administrator
|
r1981 | if not have_wx: | ||
Administrator
|
r1980 | EXCLUDE.append(pjoin('IPython', 'Extensions', 'igrid')) | ||
Administrator
|
r1981 | EXCLUDE.append(pjoin('IPython', 'gui')) | ||
EXCLUDE.append(pjoin('IPython', 'frontend', 'wx')) | ||||
Fernando Perez
|
r2091 | if not have_wx_aui: | ||
EXCLUDE.append(pjoin('IPython', 'gui', 'wx', 'wxIPython')) | ||||
Administrator
|
r1981 | if not have_objc: | ||
EXCLUDE.append(pjoin('IPython', 'frontend', 'cocoa')) | ||||
Administrator
|
r1980 | |||
Administrator
|
r1981 | if not have_curses: | ||
Administrator
|
r1980 | EXCLUDE.append(pjoin('IPython', 'Extensions', 'ibrowse')) | ||
Administrator
|
r1981 | if not sys.platform == 'win32': | ||
EXCLUDE.append(pjoin('IPython', 'platutils_win32')) | ||||
Brian Granger
|
r1985 | # These have to be skipped on win32 because the use echo, rm, cd, etc. | ||
# See ticket https://bugs.launchpad.net/bugs/366982 | ||||
Administrator
|
r1984 | if sys.platform == 'win32': | ||
EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'test_exampleip')) | ||||
EXCLUDE.append(pjoin('IPython', 'testing', 'plugin', 'dtexample')) | ||||
Administrator
|
r1981 | if not os.name == 'posix': | ||
EXCLUDE.append(pjoin('IPython', 'platutils_posix')) | ||||
if not have_pexpect: | ||||
EXCLUDE.append(pjoin('IPython', 'irunner')) | ||||
Administrator
|
r1980 | |||
Fernando Perez
|
r2133 | if not have_twisted: | ||
EXCLUDE.append(pjoin('IPython', 'frontend', 'asyncfrontendbase')) | ||||
EXCLUDE.append(pjoin('IPython', 'frontend', 'prefilterfrontend')) | ||||
EXCLUDE.append(pjoin('IPython', 'frontend', 'frontendbase')) | ||||
EXCLUDE.append(pjoin('IPython', 'frontend', 'linefrontendbase')) | ||||
EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', 'test_linefrontend')) | ||||
EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', 'test_frontendbase')) | ||||
EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', | ||||
'test_prefilterfrontend')) | ||||
EXCLUDE.append(pjoin('IPython', 'frontend', 'tests', | ||||
'test_asyncfrontendbase')) | ||||
EXCLUDE.append(pjoin('IPython', 'kernel', 'error')) | ||||
EXCLUDE.append(pjoin('IPython', 'testing', 'parametric')) | ||||
EXCLUDE.append(pjoin('IPython', 'testing', 'util')) | ||||
EXCLUDE.append(pjoin('IPython', 'testing', 'tests', 'test_decorators_trial')) | ||||
Administrator
|
r1980 | # 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] | ||||
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). | ||||
Fernando Perez
|
r1851 | plugins = [IPythonDoctest(EXCLUDE)] | ||
Fernando Perez
|
r1761 | for p in nose.plugins.builtin.plugins: | ||
plug = p() | ||||
if plug.name == 'doctest': | ||||
continue | ||||
#print '*** adding plugin:',plug.name # dbg | ||||
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 | ||||
Fernando Perez
|
r2111 | if sys.platform == 'win32': | ||
def run(self): | ||||
"""Run the stored commands""" | ||||
# On Windows, cd to temporary directory to run tests. Otherwise, | ||||
# Twisted's trial may not be able to execute 'trial IPython', since | ||||
# it will confuse the IPython module name with the ipython | ||||
# execution scripts, because the windows file system isn't case | ||||
# sensitive. | ||||
# We also use os.system instead of subprocess.call, because I was | ||||
# having problems with subprocess and I just don't know enough | ||||
# about win32 to debug this reliably. Os.system may be the 'old | ||||
# fashioned' way to do it, but it works just fine. If someone | ||||
# later can clean this up that's fine, as long as the tests run | ||||
# reliably in win32. | ||||
curdir = os.getcwd() | ||||
os.chdir(tempfile.gettempdir()) | ||||
stat = os.system(' '.join(self.call_args)) | ||||
os.chdir(curdir) | ||||
return stat | ||||
else: | ||||
def run(self): | ||||
"""Run the stored commands""" | ||||
return subprocess.call(self.call_args) | ||||
Brian Granger
|
r1972 | |||
def make_runners(): | ||||
"""Define the modules and packages that need to be tested. | ||||
""" | ||||
# This omits additional top-level modules that should not be doctested. | ||||
# XXX: Shell.py is also ommited because of a bug in the skip_doctest | ||||
# decorator. See ticket https://bugs.launchpad.net/bugs/366209 | ||||
top_mod = \ | ||||
['background_jobs.py', 'ColorANSI.py', 'completer.py', 'ConfigLoader.py', | ||||
'CrashHandler.py', 'Debugger.py', 'deep_reload.py', 'demo.py', | ||||
'DPyGetOpt.py', 'dtutils.py', 'excolors.py', 'FakeModule.py', | ||||
'generics.py', 'genutils.py', 'history.py', 'hooks.py', 'ipapi.py', | ||||
Administrator
|
r1981 | 'iplib.py', 'ipmaker.py', 'ipstruct.py', 'Itpl.py', | ||
Brian Granger
|
r1972 | 'Logger.py', 'macro.py', 'Magic.py', 'OInspect.py', | ||
'OutputTrap.py', 'platutils.py', 'prefilter.py', 'Prompts.py', | ||||
'PyColorize.py', 'Release.py', 'rlineimpl.py', 'shadowns.py', | ||||
Fernando Perez
|
r2133 | 'shellglobals.py', 'strdispatch.py', | ||
Brian Granger
|
r1972 | 'ultraTB.py', 'upgrade_dir.py', 'usage.py', 'wildcard.py', | ||
# See note above for why this is skipped | ||||
# 'Shell.py', | ||||
'winconsole.py'] | ||||
Fernando Perez
|
r2133 | if have_twisted: | ||
top_mod.append('twshell.py') | ||||
Administrator
|
r1981 | if have_pexpect: | ||
top_mod.append('irunner.py') | ||||
Brian Granger
|
r1972 | |||
Administrator
|
r1986 | if sys.platform == 'win32': | ||
top_mod.append('platutils_win32.py') | ||||
elif os.name == 'posix': | ||||
top_mod.append('platutils_posix.py') | ||||
else: | ||||
top_mod.append('platutils_dummy.py') | ||||
Brian Granger
|
r1973 | # These are tested by nose, so skip IPython.kernel | ||
Administrator
|
r1981 | top_pack = ['config','Extensions','frontend', | ||
Brian Granger
|
r1972 | 'testing','tests','tools','UserConfig'] | ||
Administrator
|
r1981 | if have_wx: | ||
top_pack.append('gui') | ||||
Brian Granger
|
r1972 | modules = ['IPython.%s' % m[:-3] for m in top_mod ] | ||
packages = ['IPython.%s' % m for m in top_pack ] | ||||
# Make runners | ||||
runners = dict(zip(top_pack, [IPTester(params=v) for v in packages])) | ||||
Brian Granger
|
r1974 | |||
Brian Granger
|
r1973 | # Test IPython.kernel using trial if twisted is installed | ||
Administrator
|
r1981 | if have_zi and have_twisted and have_foolscap: | ||
Brian Granger
|
r1972 | runners['trial'] = IPTester('trial',['IPython']) | ||
Brian Granger
|
r1974 | runners['modules'] = IPTester(params=modules) | ||
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. | ||||
""" | ||||
runners = make_runners() | ||||
# Run all test runners, tracking execution time | ||||
failed = {} | ||||
t_start = time.time() | ||||
for name,runner in runners.iteritems(): | ||||
print '*'*77 | ||||
Fernando Perez
|
r2091 | print 'IPython test group:',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 | ||||
Fernando Perez
|
r2091 | print 'Ran %s test groups in %.3fs' % (nrunners, t_tests) | ||
Brian Granger
|
r1972 | |||
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 | ||||
Fernando Perez
|
r2091 | print 'ERROR - %s out of %s test groups failed.' % (nfail, nrunners) | ||
Brian Granger
|
r1972 | 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__': | ||||
Fernando Perez
|
r2091 | main() | ||