提交 5bc7aa8a authored 作者: Olivier Delalleau's avatar Olivier Delalleau

Merge pull request #489 from lamblin/better_nosetest_script

Better nosetest script
...@@ -5,3 +5,4 @@ global-include *.sh ...@@ -5,3 +5,4 @@ global-include *.sh
recursive-include docs *.png *.gp recursive-include docs *.png *.gp
include distribute_setup.py include distribute_setup.py
include bin/theano-cache include bin/theano-cache
include bin/theano-nose
#!/usr/bin/env python
__authors__ = "Olivier Delalleau, Pascal Lamblin"
__contact__ = "delallea@iro"
"""
This script should behave the same as the `nosetests` command.
The reason for its existence is that on some systems, it may not be obvious to
find where nosetests is installed in order to run it in a different process.
It is also used to load the KnownFailure plugin, in order to hide
KnownFailureTests error messages. Use --without-knownfailure to
disable that plugin.
If the --batch option is used, it will call `run_tests_in_batch.py`,
in order to run the tests by batches, not all at the same time.
`run_tests_in_batch.py` will in turn call back this script in another
process.
"""
import logging
_logger = logging.getLogger('theano.bin.theano-nose')
_logger.setLevel(logging.WARN)
import nose
import textwrap
import sys
def main():
# Handle --batch[=n] arguments
batch_args = [arg for arg in sys.argv if arg.startswith('--batch')]
for arg in batch_args:
sys.argv.remove(arg)
if len(batch_args):
if len(batch_args) > 1:
_logger.warn(
'Multiple --batch arguments detected, using the last one '
'and ignoring the first ones.')
batch_arg = batch_args[-1]
elems = batch_arg.split('=', 1)
if len(elems) == 2:
batch_size = int(elems[1])
else:
# Use run_tests_in_batch's default
batch_size = None
from theano.tests import run_tests_in_batch
return run_tests_in_batch.main(batch_size=batch_size)
# Non-batch mode.
addplugins = []
# We include KnownFailure plugin by default, unless
# it is disabled by the "--without-knownfailure" arg.
if '--without-knownfailure' not in sys.argv:
try:
from numpy.testing.noseclasses import KnownFailure
addplugins.append(KnownFailure())
except ImportError:
_logger.warn(
'KnownFailure plugin from NumPy could not be imported. '
'Use --without-knownfailure to disable this warning.')
else:
sys.argv.remove('--without-knownfailure')
return nose.main(addplugins=addplugins)
def help():
help_msg = """
This script behaves mostly the same as the `nosetests` command.
The main difference is that it loads automatically the
KnownFailure plugin, in order to hide KnownFailureTests error
messages. It also supports executing tests by batches.
Options:
--batch[=n]: Do not run all the tests in one run, but split
the execution in batches of `n` tests each.
Default n is 100.
--help, -h: Displays this help.
--without-knownfailure: Do not load the KnownFailure plugin.
The other options will be passed to nosetests, see ``nosetests -h``.
"""
print textwrap.dedent(help_msg)
if __name__ == '__main__':
if '--help' in sys.argv or '-h' in sys.argv:
help()
else:
result = main()
sys.exit(result)
...@@ -6,6 +6,9 @@ LICENSE ...@@ -6,6 +6,9 @@ LICENSE
Copyright (c) 2008--2012, Theano Development Team Copyright (c) 2008--2012, Theano Development Team
All rights reserved. All rights reserved.
Contains code from NumPy, Copyright (c) 2005-2011, NumPy Developers.
All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met: modification, are permitted provided that the following conditions are met:
......
...@@ -141,7 +141,7 @@ def do_setup(): ...@@ -141,7 +141,7 @@ def do_setup():
'ChangeLog'], 'ChangeLog'],
'theano.misc': ['*.sh'] 'theano.misc': ['*.sh']
}, },
scripts=['bin/theano-cache'], scripts=['bin/theano-cache', 'bin/theano-nose'],
keywords=' '.join([ keywords=' '.join([
'theano', 'math', 'numerical', 'symbolic', 'blas', 'theano', 'math', 'numerical', 'symbolic', 'blas',
'numpy', 'gpu', 'autodiff', 'differentiation' 'numpy', 'gpu', 'autodiff', 'differentiation'
......
...@@ -77,8 +77,8 @@ from compile import \ ...@@ -77,8 +77,8 @@ from compile import \
from misc.safe_asarray import _asarray from misc.safe_asarray import _asarray
import numpy.testing import theano.tests
test = numpy.testing.Tester().test test = theano.tests.TheanoNoseTester().test
FancyModule = Module FancyModule = Module
......
#!/bin/bash #!/bin/bash
#we set the compiledir to the /Tmp dir to make the test faster by bypassing the nfs network.
date date
START=`date +%s` START=`date +%s`
NOSETESTS=nosetests
ARGS=$@ ARGS=$@
PROFILING="" PROFILING=""
RELEASE="" RELEASE=""
if [ "$1" == "--release" ]; then if [ "$1" == "--release" ]; then
RELEASE="True" RELEASE="True"
shift shift
ARGS=$@ ARGS=$@
fi fi
if [ "$1" == "--buildbot" ]; then if [ "$1" == "--buildbot" ]; then
#we set the compiledir to the /Tmp dir to make the test faster by bypassing the nfs network.
COMPILEDIR=/Tmp/lisa_theano_compile_dir_theano COMPILEDIR=/Tmp/lisa_theano_compile_dir_theano
ROOT_CWD=/Tmp/nightly_build ROOT_CWD=/Tmp/nightly_build
FLAGS=compiledir=$COMPILEDIR FLAGS=compiledir=$COMPILEDIR
...@@ -20,24 +21,30 @@ if [ "$1" == "--buildbot" ]; then ...@@ -20,24 +21,30 @@ if [ "$1" == "--buildbot" ]; then
cd .. cd ..
ARGS="Theano" ARGS="Theano"
PROFILING="--with-coverage --cover-package=theano" PROFILING="--with-coverage --cover-package=theano"
NOSETESTS=${ROOT_CWD}/bin/theano-nose
export PYTHONPATH=${ROOT_CWD}:$PYTHONPATH
else else
COMPILEDIR=`python -c "import theano;print theano.config.compiledir"` COMPILEDIR=`python -c "import theano; print theano.config.compiledir"`
NOSETESTS=`python -c "import theano; print theano.__path__[0]"`/../bin/theano-nose
fi fi
echo "Number of elements in the compiledir:" echo "Number of elements in the compiledir:"
ls ${COMPILEDIR}|wc -l ls ${COMPILEDIR}|wc -l
# We don't want warnings in the buildbot for errors already fixed. # We don't want warnings in the buildbot for errors already fixed.
FLAGS=${THEANO_FLAGS},warn.argmax_pushdown_bug=False,warn.gpusum_01_011_0111_bug=False,warn.sum_sum_bug=False,warn.sum_div_dimshuffle_bug=False,warn.subtensor_merge_bug=False,$FLAGS FLAGS=${THEANO_FLAGS},warn.argmax_pushdown_bug=False,warn.gpusum_01_011_0111_bug=False,warn.sum_sum_bug=False,warn.sum_div_dimshuffle_bug=False,warn.subtensor_merge_bug=False,$FLAGS
# We want to see correctly optimization/shape errors, so make make them raise an # We want to see correctly optimization/shape errors, so make make them raise an
# error. # error.
FLAGS=on_opt_error=raise,$FLAGS FLAGS=on_opt_error=raise,$FLAGS
FLAGS=on_shape_error=raise,$FLAGS FLAGS=on_shape_error=raise,$FLAGS
# Ignore user device and floatX config, because: # Ignore user device and floatX config, because:
# 1. Tests are intended to be run with device=cpu. # 1. Tests are intended to be run with device=cpu.
# 2. We explicitly add 'floatX=float32' in one run of the test suite below, # 2. We explicitly add 'floatX=float32' in one run of the test suite below,
# while we want all other runs to run with 'floatX=float64'. # while we want all other runs to run with 'floatX=float64'.
FLAGS=${FLAGS},device=cpu,floatX=float64 FLAGS=${FLAGS},device=cpu,floatX=float64
export PYTHONPATH=${ROOT_CWD}:$PYTHONPATH
if [ "$RELEASE" ]; then if [ "$RELEASE" ]; then
echo "Executing nosetests with default mode and compute_test_value" echo "Executing nosetests with default mode and compute_test_value"
......
...@@ -66,7 +66,12 @@ def mysend(subject, file): ...@@ -66,7 +66,12 @@ def mysend(subject, file):
elif token.startswith("SKIP="): elif token.startswith("SKIP="):
skip+=int(token[5:]) skip+=int(token[5:])
elif token == "KnownFailureTest:": elif token == "KnownFailureTest:":
# This means that KnownFailure plugin is off,
# so knownfails are also counted as errors
knownfail+=1 knownfail+=1
errors-=1
elif token.startswith("KNOWNFAIL="):
knownfail += int(token.split('=')[1])
elif token.startswith("speed_failure_"): elif token.startswith("speed_failure_"):
speed_failure+=int(token.split('=')[1]) speed_failure+=int(token.split('=')[1])
show_speed_failure=True show_speed_failure=True
...@@ -98,11 +103,10 @@ def mysend(subject, file): ...@@ -98,11 +103,10 @@ def mysend(subject, file):
float64_time = start float64_time = start
start = "" start = ""
s="KnownFailure are removed from Error. \n Resume of the output:\n"+filter_output(open(file))+"Full output:\n"+s s="Resume of the output:\n\n"+filter_output(open(file))+"\n\nFull output:\n\n"+s
img = MIMEText(s) img = MIMEText(s)
fp.close() fp.close()
msg.attach(img) msg.attach(img)
errors-=knownfail
# Send the email via our own SMTP server. # Send the email via our own SMTP server.
if show_speed_failure: if show_speed_failure:
......
from main import main from main import main, TheanoNoseTester
import unittest_tools import unittest_tools
__authors__ = "Olivier Delalleau"
__contact__ = "delallea@iro"
"""
This script should behave the same as the `nosetests` command.
The reason for its existence is that on some systems, it may not be obvious to
find where nosetests is installed in order to run it in a different process.
This script is called from `run_tests_in_batch.py`.
"""
import sys
import nose
if __name__ == '__main__':
sys.exit(nose.main())
import unittest,sys import os, unittest, sys
import nose.plugins.builtin
from numpy.testing.nosetester import import_nose, NoseTester
from numpy.testing.noseclasses import KnownFailure, NumpyTestProgram
# This class contains code adapted from NumPy,
# numpy/testing/nosetester.py,
# Copyright (c) 2005-2011, NumPy Developers
class TheanoNoseTester(NoseTester):
"""
Nose test runner.
This class enables running nose tests from inside Theano,
by calling theano.test().
This version is more adapted to what we want than Numpy's one.
"""
def _test_argv(self, verbose, extra_argv):
"""
Generate argv for nosetest command
:type verbose: int
:param verbose: Verbosity value for test outputs, in the range 1-10.
Default is 1.
:type extra_argv: list
:param extra_argv: List with any extra arguments to pass to nosetests.
"""
#self.package_path = os.path.abspath(self.package_path)
argv = [__file__, self.package_path]
argv += ['--verbosity', str(verbose)]
if extra_argv:
argv += extra_argv
return argv
def _show_system_info(self):
nose = import_nose()
import theano
print "Theano version %s" % theano.__version__
theano_dir = os.path.dirname(theano.__file__)
print "theano is installed in %s" % theano_dir
super(TheanoNoseTester, self)._show_system_info()
def prepare_test_args(self, verbose=1, extra_argv=None, coverage=False,
capture=True, knownfailure=True):
"""
Prepare arguments for the `test` method.
Takes the same arguments as `test`.
"""
# fail with nice error message if nose is not present
nose = import_nose()
# compile argv
argv = self._test_argv(verbose, extra_argv)
# numpy way of doing coverage
if coverage:
argv += ['--cover-package=%s' % self.package_name, '--with-coverage',
'--cover-tests', '--cover-inclusive', '--cover-erase']
# Capture output only if needed
if not capture:
argv += ['-s']
# construct list of plugins
plugins = []
if knownfailure:
plugins.append(KnownFailure())
plugins += [p() for p in nose.plugins.builtin.plugins]
return argv, plugins
def test(self, verbose=1, extra_argv=None, coverage=False, capture=True,
knownfailure=True):
"""
Run tests for module using nose.
:type verbose: int
:param verbose: Verbosity value for test outputs, in the range 1-10.
Default is 1.
:type extra_argv: list
:param extra_argv: List with any extra arguments to pass to nosetests.
:type coverage: bool
:param coverage: If True, report coverage of Theano code. Default is False.
:type capture: bool
:param capture: If True, capture the standard output of the tests, like
nosetests does in command-line. The output of failing
tests will be displayed at the end. Default is True.
:type knownfailure: bool
:param knownfailure: If True, tests raising KnownFailureTest will
not be considered Errors nor Failure, but reported as
"known failures" and treated quite like skipped tests.
Default is True.
:returns: Returns the result of running the tests as a
``nose.result.TextTestResult`` object.
"""
# cap verbosity at 3 because nose becomes *very* verbose beyond that
verbose = min(verbose, 3)
self._show_system_info()
cwd = os.getcwd()
if self.package_path in os.listdir(cwd):
# The tests give weird errors if the package to test is
# in current directory.
raise RuntimeError((
"This function does not run correctly when, at the time "
"theano was imported, the working directory was theano's "
"parent directory. You should exit your Python prompt, change "
"directory, then launch Python again, import theano, then "
"launch theano.test()."))
argv, plugins = self.prepare_test_args(verbose, extra_argv, coverage,
capture, knownfailure)
t = NumpyTestProgram(argv=argv, exit=False, plugins=plugins)
return t.result
def main(modulename): def main(modulename):
debug = False debug = False
......
...@@ -36,7 +36,7 @@ import cPickle, os, subprocess, sys ...@@ -36,7 +36,7 @@ import cPickle, os, subprocess, sys
import theano import theano
def main(stdout=None, stderr=None, argv=None, call_nose=None): def main(stdout=None, stderr=None, argv=None, theano_nose=None, batch_size=None):
""" """
Run tests with optional output redirection. Run tests with optional output redirection.
...@@ -46,8 +46,10 @@ def main(stdout=None, stderr=None, argv=None, call_nose=None): ...@@ -46,8 +46,10 @@ def main(stdout=None, stderr=None, argv=None, call_nose=None):
If argv is None, then we use arguments from sys.argv, otherwise we use the If argv is None, then we use arguments from sys.argv, otherwise we use the
provided arguments instead. provided arguments instead.
If call_nose is None, then we use the call_nose.py script found in If theano_nose is None, then we use the theano-nose script found in
theano/tests to call nosetests. Otherwise we call the provided script. Theano/bin to call nosetests. Otherwise we call the provided script.
If batch_size is None, we use a default value of 100.
""" """
if stdout is None: if stdout is None:
stdout = sys.stdout stdout = sys.stdout
...@@ -55,24 +57,28 @@ def main(stdout=None, stderr=None, argv=None, call_nose=None): ...@@ -55,24 +57,28 @@ def main(stdout=None, stderr=None, argv=None, call_nose=None):
stderr = sys.stderr stderr = sys.stderr
if argv is None: if argv is None:
argv = sys.argv argv = sys.argv
if call_nose is None: if theano_nose is None:
call_nose = os.path.join(theano.__path__[0], 'tests', 'call_nose.py') theano_nose = os.path.join(theano.__path__[0], '..', 'bin', 'theano-nose')
if batch_size is None:
batch_size = 100
stdout_backup = sys.stdout stdout_backup = sys.stdout
stderr_backup = sys.stderr stderr_backup = sys.stderr
try: try:
sys.stdout = stdout sys.stdout = stdout
sys.stderr = stderr sys.stderr = stderr
run(stdout, stderr, argv, call_nose) run(stdout, stderr, argv, theano_nose, batch_size)
finally: finally:
sys.stdout = stdout_backup sys.stdout = stdout_backup
sys.stderr = stderr_backup sys.stderr = stderr_backup
def run(stdout, stderr, argv, call_nose): def run(stdout, stderr, argv, theano_nose, batch_size):
if len(argv) == 1: if len(argv) == 1:
tests_dir = theano.__path__[0] tests_dir = theano.__path__[0]
other_args = []
else: else:
assert len(argv) == 2 # tests_dir should be at the end of argv, there can be other arguments
tests_dir = argv[1] tests_dir = argv[-1]
other_args = argv[1:-1]
assert os.path.isdir(tests_dir) assert os.path.isdir(tests_dir)
os.chdir(tests_dir) os.chdir(tests_dir)
# It seems safer to fully regenerate the list of tests on each call. # It seems safer to fully regenerate the list of tests on each call.
...@@ -86,8 +92,13 @@ def run(stdout, stderr, argv, call_nose): ...@@ -86,8 +92,13 @@ def run(stdout, stderr, argv, call_nose):
stdout.flush() stdout.flush()
stderr.flush() stderr.flush()
dummy_in = open(os.devnull) dummy_in = open(os.devnull)
rval = subprocess.call(['python', call_nose, '--collect-only', '--with-id'], # We need to call 'python' on Windows, because theano-nose is not a
stdin=dummy_in.fileno(), stdout=stdout.fileno(), # native Windows app; and it does not hurt to call it on Unix.
rval = subprocess.call(
(['python', theano_nose, '--collect-only', '--with-id']
+ other_args),
stdin=dummy_in.fileno(),
stdout=stdout.fileno(),
stderr=stderr.fileno()) stderr=stderr.fileno())
stdout.flush() stdout.flush()
stderr.flush() stderr.flush()
...@@ -98,21 +109,23 @@ def run(stdout, stderr, argv, call_nose): ...@@ -98,21 +109,23 @@ def run(stdout, stderr, argv, call_nose):
n_tests = len(ids) n_tests = len(ids)
assert n_tests == max(ids) assert n_tests == max(ids)
# Run tests. # Run tests.
n_batch = 100
failed = set() failed = set()
print """\ print """\
################################### ###################################
# RUNNING TESTS IN BATCHES OF %s # # RUNNING TESTS IN BATCHES OF %s #
###################################""" % n_batch ###################################""" % batch_size
for test_id in xrange(1, n_tests + 1, n_batch): for test_id in xrange(1, n_tests + 1, batch_size):
stdout.flush() stdout.flush()
stderr.flush() stderr.flush()
test_range = range(test_id, min(test_id + n_batch, n_tests + 1)) test_range = range(test_id, min(test_id + batch_size, n_tests + 1))
# We suppress all output because we want the user to focus only on the # We suppress all output because we want the user to focus only on the
# failed tests, which are re-run (with output) below. # failed tests, which are re-run (with output) below.
dummy_out = open(os.devnull, 'w') dummy_out = open(os.devnull, 'w')
rval = subprocess.call(['python', call_nose, '-q', '--with-id'] + rval = subprocess.call(
map(str, test_range), stdout=dummy_out.fileno(), (['python', theano_nose, '-q', '--with-id']
+ map(str, test_range)
+ other_args),
stdout=dummy_out.fileno(),
stderr=dummy_out.fileno(), stderr=dummy_out.fileno(),
stdin=dummy_in.fileno()) stdin=dummy_in.fileno())
# Recover failed test indices from the 'failed' field of the '.noseids' # Recover failed test indices from the 'failed' field of the '.noseids'
...@@ -132,8 +145,12 @@ def run(stdout, stderr, argv, call_nose): ...@@ -132,8 +145,12 @@ def run(stdout, stderr, argv, call_nose):
################################""" ################################"""
stdout.flush() stdout.flush()
stderr.flush() stderr.flush()
subprocess.call(['python', call_nose, '-v', '--with-id'] + failed, subprocess.call(
stdin=dummy_in.fileno(), stdout=stdout.fileno(), (['python', theano_nose, '-v', '--with-id']
+ failed
+ other_args),
stdin=dummy_in.fileno(),
stdout=stdout.fileno(),
stderr=stderr.fileno()) stderr=stderr.fileno())
stdout.flush() stdout.flush()
stderr.flush() stderr.flush()
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论