summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xruntest.py14
-rw-r--r--src/engine/SCons/ActionTests.py5
-rw-r--r--testing/framework/TestUnit/__init__.py5
-rw-r--r--testing/framework/TestUnit/cli.py35
-rw-r--r--testing/framework/TestUnit/taprunner.py120
5 files changed, 174 insertions, 5 deletions
diff --git a/runtest.py b/runtest.py
index 91ff8ba7..267915ae 100755
--- a/runtest.py
+++ b/runtest.py
@@ -157,6 +157,7 @@ Options:
--passed Summarize which tests passed.
-q --quiet Don't print the test being executed.
--quit-on-failure Quit on any test failure
+ --runner CLASS Alternative test runner class for unit tests
-s --short-progress Short progress, prints only the command line
and a percentage value, based on the total and
current number of tests.
@@ -199,6 +200,8 @@ parser.add_option('-a', '--all', action='store_true',
help="Run all tests.")
parser.add_option('-o', '--output',
help="Save the output from a test run to the log file.")
+parser.add_option('--runner', metavar='class',
+ help="Test runner class for unit tests.")
parser.add_option('--xml',
help="Save results to file in SCons XML format.")
(options, args) = parser.parse_args()
@@ -612,14 +615,17 @@ old_pythonpath = os.environ.get('PYTHONPATH')
# FIXME: the following is necessary to pull in half of the testing
# harness from $srcdir/etc. Those modules should be transfered
-# to QMTest/ once we completely cut over to using that as
-# the harness, in which case this manipulation of PYTHONPATH
+# to testing/, in which case this manipulation of PYTHONPATH
# should be able to go away.
pythonpaths = [ pythonpath_dir ]
# Add path of the QMTest folder to PYTHONPATH
+# [ ] move used parts from QMTest to testing/framework/
scriptpath = os.path.dirname(os.path.realpath(__file__))
pythonpaths.append(os.path.join(scriptpath, 'QMTest'))
+# Add path for testing framework to PYTHONPATH
+pythonpaths.append(os.path.join(scriptpath, 'testing', 'framework'))
+
os.environ['PYTHONPATH'] = os.pathsep.join(pythonpaths)
@@ -731,7 +737,6 @@ if list_only:
sys.stdout.write(t.path + "\n")
sys.exit(0)
-#
if not python:
if os.name == 'java':
python = os.path.join(sys.prefix, 'jython')
@@ -766,6 +771,9 @@ def run_test(t, io_lock, async=True):
if debug:
command_args.append(debug)
command_args.append(t.path)
+ if options.runner:
+ # For example --runner TestUnit.TAPTestRunner
+ command_args.append('--runner ' + options.runner)
t.command_args = [python] + command_args
t.command_str = " ".join([escape(python)] + command_args)
if printcommand:
diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py
index 3e900d7b..809e5ceb 100644
--- a/src/engine/SCons/ActionTests.py
+++ b/src/engine/SCons/ActionTests.py
@@ -49,6 +49,7 @@ import SCons.Environment
import SCons.Errors
import TestCmd
+import TestUnit
# Initial setup of the common environment for all tests,
# a temporary working directory containing a
@@ -2108,8 +2109,8 @@ if __name__ == "__main__":
for tclass in tclasses:
names = unittest.getTestCaseNames(tclass, 'test_')
suite.addTests(list(map(tclass, names)))
- if not unittest.TextTestRunner().run(suite).wasSuccessful():
- sys.exit(1)
+
+ TestUnit.run(suite)
# Local Variables:
# tab-width:4
diff --git a/testing/framework/TestUnit/__init__.py b/testing/framework/TestUnit/__init__.py
new file mode 100644
index 00000000..51cf9720
--- /dev/null
+++ b/testing/framework/TestUnit/__init__.py
@@ -0,0 +1,5 @@
+
+__all__ = ['TAPTestRunner', 'TAPTestResult', 'run']
+
+from .taprunner import TAPTestRunner, TAPTestResult
+from .cli import run
diff --git a/testing/framework/TestUnit/cli.py b/testing/framework/TestUnit/cli.py
new file mode 100644
index 00000000..6aec7354
--- /dev/null
+++ b/testing/framework/TestUnit/cli.py
@@ -0,0 +1,35 @@
+"""
+Choose test runner class from --runner command line option
+and execute test cases.
+"""
+
+import unittest
+import optparse
+import sys
+
+
+def get_runner():
+ parser = optparse.OptionParser()
+ parser.add_option('--runner', default='unittest.TextTestRunner',
+ help='name of test runner class to use')
+ opts, args = parser.parse_args()
+
+ fromsplit = opts.runner.rsplit('.', 1)
+ if len(fromsplit) < 2:
+ raise ValueError('Can\'t use module as a runner')
+ else:
+ runnermod = __import__(fromsplit[0])
+ return getattr(runnermod, fromsplit[1])
+
+
+def run(suite=None):
+ runner = get_runner()
+ if suite:
+ if not runner().run(suite).wasSuccessful():
+ sys.exit(1)
+ else:
+ unittest.main(argv=sys.argv[:1], testRunner=runner)
+
+
+if __name__ == '__main__':
+ run()
diff --git a/testing/framework/TestUnit/taprunner.py b/testing/framework/TestUnit/taprunner.py
new file mode 100644
index 00000000..01e0e813
--- /dev/null
+++ b/testing/framework/TestUnit/taprunner.py
@@ -0,0 +1,120 @@
+"""
+Format unittest results in Test Anything Protocol (TAP).
+http://testanything.org/tap-version-13-specification.html
+
+Public domain work by:
+ anatoly techtonik <techtonik@gmail.com>
+
+"""
+
+from unittest import suite
+from unittest.runner import TextTestRunner, TextTestResult
+
+__version__ = "0.1"
+
+class TAPTestResult(TextTestResult):
+
+ def _process(self, test, msg, failtype = None, directive = None):
+ """ increase the counter, format and output TAP info """
+ # counterhack: increase test counter
+ test.suite.tap_counter += 1
+ msg = "%s %d" % (msg, test.suite.tap_counter)
+ if "not" not in msg:
+ msg += " " # justify
+ self.stream.write("%s - " % msg)
+ if failtype:
+ self.stream.write("%s - " % failtype)
+ self.stream.write("%s" % test.__class__.__name__)
+ self.stream.write(".%s" % test._testMethodName)
+ if directive:
+ self.stream.write(directive)
+ self.stream.write("\n")
+ # [ ] write test __doc__ (if exists) in comment
+ self.stream.flush()
+
+ def addSuccess(self, test):
+ super(TextTestResult, self).addSuccess(test)
+ self._process(test, "ok")
+
+ def addFailure(self, test, err):
+ super(TextTestResult, self).addFailure(test, err)
+ self._process(test, "not ok", "FAIL")
+ # [ ] add structured data about assertion
+
+ def addError(self, test, err):
+ super(TextTestResult, self).addError(test, err)
+ self._process(test, "not ok", "ERROR")
+ # [ ] add structured data about exception
+
+ def addSkip(self, test, reason):
+ super(TextTestResult, self).addSkip(test, reason)
+ self._process(test, "ok", directive=(" # SKIP %s" % reason))
+
+ def addExpectedFailure(self, test, err):
+ super(TextTestResult, self).addExpectedFailure(test, err)
+ self._process(test, "not ok", directive=(" # TODO"))
+
+ def addUnexpectedSuccess(self, test):
+ super(TextTestResult, self).addUnexpectedSuccess(test)
+ self._process(test, "not ok", "FAIL (unexpected success)")
+
+ """
+ def printErrors(self):
+ def printErrorList(self, flavour, errors):
+ """
+
+
+class TAPTestRunner(TextTestRunner):
+ resultclass = TAPTestResult
+
+ def run(self, test):
+ self.stream.write("TAP version 13\n")
+ # [ ] add commented block with test suite __doc__
+ # [ ] check call with a single test
+ # if isinstance(test, suite.TestSuite):
+ self.stream.write("1..%s\n" % len(list(test)))
+
+ # counterhack: inject test counter into test suite
+ test.tap_counter = 0
+ # counterhack: inject reference to suite into each test case
+ for case in test:
+ case.suite = test
+
+ return super(TAPTestRunner, self).run(test)
+
+
+if __name__ == "__main__":
+ import sys
+ import unittest
+
+ class Test(unittest.TestCase):
+ def test_ok(self):
+ pass
+ def test_fail(self):
+ self.assertTrue(False)
+ def test_error(self):
+ bad_symbol
+ @unittest.skip("skipin'")
+ def test_skip(self):
+ pass
+ @unittest.expectedFailure
+ def test_not_ready(self):
+ self.fail()
+ @unittest.expectedFailure
+ def test_invalid_fail_mark(self):
+ pass
+ def test_another_ok(self):
+ pass
+
+
+ suite = unittest.TestSuite([
+ Test('test_ok'),
+ Test('test_fail'),
+ Test('test_error'),
+ Test('test_skip'),
+ Test('test_not_ready'),
+ Test('test_invalid_fail_mark'),
+ Test('test_another_ok')
+ ])
+ if not TAPTestRunner().run(suite).wasSuccessful():
+ sys.exit(1)