summaryrefslogtreecommitdiff
path: root/Tools/Scripts/webkitpy/test
diff options
context:
space:
mode:
authorSimon Hausmann <simon.hausmann@nokia.com>2012-07-16 14:51:15 +0200
committerSimon Hausmann <simon.hausmann@nokia.com>2012-07-16 14:51:15 +0200
commit4e6b3a206fa4ad8bb0b664f7674c9a70376d6e26 (patch)
tree7bb9ad7e31c24d1cf1707e03e6f1a80f6d033951 /Tools/Scripts/webkitpy/test
parent3977e3d2f72f7fe2c887c1ec0e0c342e1d169f42 (diff)
downloadqtwebkit-4e6b3a206fa4ad8bb0b664f7674c9a70376d6e26.tar.gz
Imported WebKit commit 953baa67aa07087b6ecd4199351ec554c724e27d (http://svn.webkit.org/repository/webkit/trunk@122676)
Diffstat (limited to 'Tools/Scripts/webkitpy/test')
-rw-r--r--Tools/Scripts/webkitpy/test/finder.py (renamed from Tools/Scripts/webkitpy/test/test_finder.py)7
-rw-r--r--Tools/Scripts/webkitpy/test/finder_unittest.py (renamed from Tools/Scripts/webkitpy/test/test_finder_unittest.py)8
-rw-r--r--Tools/Scripts/webkitpy/test/main.py124
-rw-r--r--Tools/Scripts/webkitpy/test/main_unittest.py2
-rw-r--r--Tools/Scripts/webkitpy/test/printer.py182
-rw-r--r--Tools/Scripts/webkitpy/test/runner.py71
-rw-r--r--Tools/Scripts/webkitpy/test/runner_unittest.py9
7 files changed, 217 insertions, 186 deletions
diff --git a/Tools/Scripts/webkitpy/test/test_finder.py b/Tools/Scripts/webkitpy/test/finder.py
index 3a90197e9..132072d82 100644
--- a/Tools/Scripts/webkitpy/test/test_finder.py
+++ b/Tools/Scripts/webkitpy/test/finder.py
@@ -31,7 +31,7 @@ import sys
_log = logging.getLogger(__name__)
-class TestDirectoryTree(object):
+class _DirectoryTree(object):
def __init__(self, filesystem, top_directory, starting_subdirectory):
self.filesystem = filesystem
self.top_directory = filesystem.realpath(top_directory)
@@ -63,7 +63,6 @@ class TestDirectoryTree(object):
return realpath.replace(self.top_directory + self.filesystem.sep, '')
return None
-
def clean(self):
"""Delete all .pyc files in the tree that have no matching .py file."""
_log.debug("Cleaning orphaned *.pyc files from: %s" % self.search_directory)
@@ -74,13 +73,13 @@ class TestDirectoryTree(object):
self.filesystem.remove(filename)
-class TestFinder(object):
+class Finder(object):
def __init__(self, filesystem):
self.filesystem = filesystem
self.trees = []
def add_tree(self, top_directory, starting_subdirectory=None):
- self.trees.append(TestDirectoryTree(self.filesystem, top_directory, starting_subdirectory))
+ self.trees.append(_DirectoryTree(self.filesystem, top_directory, starting_subdirectory))
def additional_paths(self, paths):
return [tree.top_directory for tree in self.trees if tree.top_directory not in paths]
diff --git a/Tools/Scripts/webkitpy/test/test_finder_unittest.py b/Tools/Scripts/webkitpy/test/finder_unittest.py
index 5b6b3b030..09048b159 100644
--- a/Tools/Scripts/webkitpy/test/test_finder_unittest.py
+++ b/Tools/Scripts/webkitpy/test/finder_unittest.py
@@ -25,10 +25,10 @@ import unittest
from webkitpy.common.system.filesystem_mock import MockFileSystem
from webkitpy.common.system.outputcapture import OutputCapture
-from webkitpy.test.test_finder import TestFinder
+from webkitpy.test.finder import Finder
-class TestFinderTest(unittest.TestCase):
+class FinderTest(unittest.TestCase):
def setUp(self):
files = {
'/foo/bar/baz.py': '',
@@ -40,7 +40,7 @@ class TestFinderTest(unittest.TestCase):
'/tmp/another_unittest.py': '',
}
self.fs = MockFileSystem(files)
- self.finder = TestFinder(self.fs)
+ self.finder = Finder(self.fs)
self.finder.add_tree('/foo', 'bar')
self.finder.add_tree('/foo2')
@@ -49,7 +49,7 @@ class TestFinderTest(unittest.TestCase):
self.root_logger = logging.getLogger()
self.log_handler = None
for h in self.root_logger.handlers:
- if getattr(h, 'name', None) == 'webkitpy.test.main':
+ if getattr(h, 'name', None) == 'webkitpy.test.printer':
self.log_handler = h
break
if self.log_handler:
diff --git a/Tools/Scripts/webkitpy/test/main.py b/Tools/Scripts/webkitpy/test/main.py
index c5dc39433..2048d9e59 100644
--- a/Tools/Scripts/webkitpy/test/main.py
+++ b/Tools/Scripts/webkitpy/test/main.py
@@ -25,24 +25,24 @@
import logging
import optparse
-import os
import StringIO
import sys
import traceback
import unittest
from webkitpy.common.system.filesystem import FileSystem
-from webkitpy.common.system import outputcapture
-from webkitpy.test.test_finder import TestFinder
-from webkitpy.test.runner import TestRunner
+from webkitpy.test.finder import Finder
+from webkitpy.test.printer import Printer
+from webkitpy.test.runner import Runner
_log = logging.getLogger(__name__)
class Tester(object):
def __init__(self, filesystem=None):
- self.finder = TestFinder(filesystem or FileSystem())
- self.stream = sys.stderr
+ self.finder = Finder(filesystem or FileSystem())
+ self.printer = Printer(sys.stderr)
+ self._options = None
def add_tree(self, top_directory, starting_subdirectory=None):
self.finder.add_tree(top_directory, starting_subdirectory)
@@ -50,13 +50,13 @@ class Tester(object):
def _parse_args(self):
parser = optparse.OptionParser(usage='usage: %prog [options] [args...]')
parser.add_option('-a', '--all', action='store_true', default=False,
- help='run all the tests'),
+ help='run all the tests')
parser.add_option('-c', '--coverage', action='store_true', default=False,
- help='generate code coverage info (requires http://pypi.python.org/pypi/coverage)'),
+ help='generate code coverage info (requires http://pypi.python.org/pypi/coverage)')
parser.add_option('-q', '--quiet', action='store_true', default=False,
- help='run quietly (errors, warnings, and progress only)'),
+ help='run quietly (errors, warnings, and progress only)')
parser.add_option('-t', '--timing', action='store_true', default=False,
- help='display per-test execution time (implies --verbose)'),
+ help='display per-test execution time (implies --verbose)')
parser.add_option('-v', '--verbose', action='count', default=0,
help='verbose output (specify once for individual test results, twice for debug messages)')
parser.add_option('--skip-integrationtests', action='store_true', default=False,
@@ -69,72 +69,9 @@ class Tester(object):
return parser.parse_args()
- def _configure(self, options):
- self._options = options
-
- if options.timing:
- # --timing implies --verbose
- options.verbose = max(options.verbose, 1)
-
- log_level = logging.INFO
- if options.quiet:
- log_level = logging.WARNING
- elif options.verbose == 2:
- log_level = logging.DEBUG
- self._configure_logging(log_level)
-
- def _configure_logging(self, log_level):
- """Configure the root logger.
-
- Configure the root logger not to log any messages from webkitpy --
- except for messages from the autoinstall module. Also set the
- logging level as described below.
- """
- handler = logging.StreamHandler(self.stream)
- # We constrain the level on the handler rather than on the root
- # logger itself. This is probably better because the handler is
- # configured and known only to this module, whereas the root logger
- # is an object shared (and potentially modified) by many modules.
- # Modifying the handler, then, is less intrusive and less likely to
- # interfere with modifications made by other modules (e.g. in unit
- # tests).
- handler.name = __name__
- handler.setLevel(log_level)
- formatter = logging.Formatter("%(message)s")
- handler.setFormatter(formatter)
-
- logger = logging.getLogger()
- logger.addHandler(handler)
- logger.setLevel(logging.NOTSET)
-
- # Filter out most webkitpy messages.
- #
- # Messages can be selectively re-enabled for this script by updating
- # this method accordingly.
- def filter(record):
- """Filter out autoinstall and non-third-party webkitpy messages."""
- # FIXME: Figure out a way not to use strings here, for example by
- # using syntax like webkitpy.test.__name__. We want to be
- # sure not to import any non-Python 2.4 code, though, until
- # after the version-checking code has executed.
- if (record.name.startswith("webkitpy.common.system.autoinstall") or
- record.name.startswith("webkitpy.test")):
- return True
- if record.name.startswith("webkitpy"):
- return False
- return True
-
- testing_filter = logging.Filter()
- testing_filter.filter = filter
-
- # Display a message so developers are not mystified as to why
- # logging does not work in the unit tests.
- _log.info("Suppressing most webkitpy logging while running unit tests.")
- handler.addFilter(testing_filter)
-
def run(self):
- options, args = self._parse_args()
- self._configure(options)
+ self._options, args = self._parse_args()
+ self.printer.configure(self._options)
self.finder.clean_trees()
@@ -149,7 +86,7 @@ class Tester(object):
if self._options.coverage:
try:
import webkitpy.thirdparty.autoinstalled.coverage as coverage
- except ImportError, e:
+ except ImportError:
_log.error("Failed to import 'coverage'; can't generate coverage numbers.")
return False
cov = coverage.coverage()
@@ -169,7 +106,7 @@ class Tester(object):
# produces lousy error messages for bad modules.
try:
__import__(name)
- except ImportError, e:
+ except ImportError:
_log.fatal('Failed to import %s:' % name)
self._log_exception()
return False
@@ -177,11 +114,9 @@ class Tester(object):
suites.append(loader.loadTestsFromName(name, None))
test_suite = unittest.TestSuite(suites)
- test_runner = TestRunner(self.stream, self._options, loader)
+ test_runner = Runner(self.printer, self._options, loader)
_log.debug("Running the tests.")
- if self._options.pass_through:
- outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughStream
result = test_runner.run(test_suite)
if self._options.coverage:
cov.stop()
@@ -194,32 +129,3 @@ class Tester(object):
traceback.print_exc(file=s)
for l in s.buflist:
_log.error(' ' + l.rstrip())
-
-
-class _CaptureAndPassThroughStream(object):
- def __init__(self, stream):
- self._buffer = StringIO.StringIO()
- self._stream = stream
-
- def write(self, msg):
- self._stream.write(msg)
-
- # Note that we don't want to capture any output generated by the debugger
- # because that could cause the results of capture_output() to be invalid.
- if not self._message_is_from_pdb():
- self._buffer.write(msg)
-
- def _message_is_from_pdb(self):
- # We will assume that if the pdb module is in the stack then the output
- # is being generated by the python debugger (or the user calling something
- # from inside the debugger).
- import inspect
- import pdb
- stack = inspect.stack()
- return any(frame[1] == pdb.__file__.replace('.pyc', '.py') for frame in stack)
-
- def flush(self):
- self._stream.flush()
-
- def getvalue(self):
- return self._buffer.getvalue()
diff --git a/Tools/Scripts/webkitpy/test/main_unittest.py b/Tools/Scripts/webkitpy/test/main_unittest.py
index 1a60beef3..2cf6df4a2 100644
--- a/Tools/Scripts/webkitpy/test/main_unittest.py
+++ b/Tools/Scripts/webkitpy/test/main_unittest.py
@@ -40,7 +40,7 @@ class TesterTest(unittest.TestCase):
root_handlers = root_logger.handlers
root_logger.handlers = []
- tester.stream = errors
+ tester.printer.stream = errors
tester.finder.find_names = lambda args, skip_integration, run_all: []
oc = OutputCapture()
try:
diff --git a/Tools/Scripts/webkitpy/test/printer.py b/Tools/Scripts/webkitpy/test/printer.py
new file mode 100644
index 000000000..77e28b8d1
--- /dev/null
+++ b/Tools/Scripts/webkitpy/test/printer.py
@@ -0,0 +1,182 @@
+# Copyright (C) 2012 Google, Inc.
+# Copyright (C) 2010 Chris Jerdonek (cjerdonek@webkit.org)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import logging
+import re
+import StringIO
+
+from webkitpy.common.system import outputcapture
+
+_log = logging.getLogger(__name__)
+
+
+class Printer(object):
+ def __init__(self, stream, options=None):
+ self.stream = stream
+ self.options = options
+ self.test_description = re.compile("(\w+) \(([\w.]+)\)")
+
+ def test_name(self, test):
+ m = self.test_description.match(str(test))
+ return "%s.%s" % (m.group(2), m.group(1))
+
+ def configure(self, options):
+ self.options = options
+
+ if options.timing:
+ # --timing implies --verbose
+ options.verbose = max(options.verbose, 1)
+
+ log_level = logging.INFO
+ if options.quiet:
+ log_level = logging.WARNING
+ elif options.verbose == 2:
+ log_level = logging.DEBUG
+
+ handler = logging.StreamHandler(self.stream)
+ # We constrain the level on the handler rather than on the root
+ # logger itself. This is probably better because the handler is
+ # configured and known only to this module, whereas the root logger
+ # is an object shared (and potentially modified) by many modules.
+ # Modifying the handler, then, is less intrusive and less likely to
+ # interfere with modifications made by other modules (e.g. in unit
+ # tests).
+ handler.name = __name__
+ handler.setLevel(log_level)
+ formatter = logging.Formatter("%(message)s")
+ handler.setFormatter(formatter)
+
+ logger = logging.getLogger()
+ logger.addHandler(handler)
+ logger.setLevel(logging.NOTSET)
+
+ # Filter out most webkitpy messages.
+ #
+ # Messages can be selectively re-enabled for this script by updating
+ # this method accordingly.
+ def filter_records(record):
+ """Filter out autoinstall and non-third-party webkitpy messages."""
+ # FIXME: Figure out a way not to use strings here, for example by
+ # using syntax like webkitpy.test.__name__. We want to be
+ # sure not to import any non-Python 2.4 code, though, until
+ # after the version-checking code has executed.
+ if (record.name.startswith("webkitpy.common.system.autoinstall") or
+ record.name.startswith("webkitpy.test")):
+ return True
+ if record.name.startswith("webkitpy"):
+ return False
+ return True
+
+ testing_filter = logging.Filter()
+ testing_filter.filter = filter_records
+
+ # Display a message so developers are not mystified as to why
+ # logging does not work in the unit tests.
+ _log.info("Suppressing most webkitpy logging while running unit tests.")
+ handler.addFilter(testing_filter)
+
+ if self.options.pass_through:
+ outputcapture.OutputCapture.stream_wrapper = _CaptureAndPassThroughStream
+
+ def print_started_test(self, test_name):
+ if self.options.verbose:
+ self.stream.write(test_name)
+
+ def print_finished_test(self, result, test_name, test_time, failure, err):
+ timing = ''
+ if self.options.timing:
+ timing = ' %.4fs' % test_time
+ if self.options.verbose:
+ if failure:
+ msg = ' failed'
+ elif err:
+ msg = ' erred'
+ else:
+ msg = ' passed'
+ self.stream.write(msg + timing + '\n')
+ else:
+ if failure:
+ msg = 'F'
+ elif err:
+ msg = 'E'
+ else:
+ msg = '.'
+ self.stream.write(msg)
+
+ def print_result(self, result, run_time):
+ self.stream.write('\n')
+
+ for (test, err) in result.errors:
+ self.stream.write("=" * 80 + '\n')
+ self.stream.write("ERROR: " + self.test_name(test) + '\n')
+ self.stream.write("-" * 80 + '\n')
+ for line in err.splitlines():
+ self.stream.write(line + '\n')
+ self.stream.write('\n')
+
+ for (test, failure) in result.failures:
+ self.stream.write("=" * 80 + '\n')
+ self.stream.write("FAILURE: " + self.test_name(test) + '\n')
+ self.stream.write("-" * 80 + '\n')
+ for line in failure.splitlines():
+ self.stream.write(line + '\n')
+ self.stream.write('\n')
+
+ self.stream.write('-' * 80 + '\n')
+ self.stream.write('Ran %d test%s in %.3fs\n' %
+ (result.testsRun, result.testsRun != 1 and "s" or "", run_time))
+
+ if result.wasSuccessful():
+ self.stream.write('\nOK\n')
+ else:
+ self.stream.write('FAILED (failures=%d, errors=%d)\n' %
+ (len(result.failures), len(result.errors)))
+
+
+class _CaptureAndPassThroughStream(object):
+ def __init__(self, stream):
+ self._buffer = StringIO.StringIO()
+ self._stream = stream
+
+ def write(self, msg):
+ self._stream.write(msg)
+
+ # Note that we don't want to capture any output generated by the debugger
+ # because that could cause the results of capture_output() to be invalid.
+ if not self._message_is_from_pdb():
+ self._buffer.write(msg)
+
+ def _message_is_from_pdb(self):
+ # We will assume that if the pdb module is in the stack then the output
+ # is being generated by the python debugger (or the user calling something
+ # from inside the debugger).
+ import inspect
+ import pdb
+ stack = inspect.stack()
+ return any(frame[1] == pdb.__file__.replace('.pyc', '.py') for frame in stack)
+
+ def flush(self):
+ self._stream.flush()
+
+ def getvalue(self):
+ return self._buffer.getvalue()
diff --git a/Tools/Scripts/webkitpy/test/runner.py b/Tools/Scripts/webkitpy/test/runner.py
index e190f2cd4..9c952075e 100644
--- a/Tools/Scripts/webkitpy/test/runner.py
+++ b/Tools/Scripts/webkitpy/test/runner.py
@@ -23,7 +23,6 @@
"""code to actually run a list of python tests."""
import logging
-import re
import time
import unittest
@@ -31,16 +30,11 @@ import unittest
_log = logging.getLogger(__name__)
-class TestRunner(object):
- def __init__(self, stream, options, loader):
+class Runner(object):
+ def __init__(self, printer, options, loader):
self.options = options
- self.stream = stream
+ self.printer = printer
self.loader = loader
- self.test_description = re.compile("(\w+) \(([\w.]+)\)")
-
- def test_name(self, test):
- m = self.test_description.match(str(test))
- return "%s.%s" % (m.group(2), m.group(1))
def all_test_names(self, suite):
names = []
@@ -48,7 +42,7 @@ class TestRunner(object):
for t in suite._tests:
names.extend(self.all_test_names(t))
else:
- names.append(self.test_name(suite))
+ names.append(self.printer.test_name(suite))
return names
def run(self, suite):
@@ -57,8 +51,7 @@ class TestRunner(object):
result = unittest.TestResult()
stop = run_start_time
for test_name in all_test_names:
- if self.options.verbose:
- self.stream.write(test_name)
+ self.printer.print_started_test(test_name)
num_failures = len(result.failures)
num_errors = len(result.errors)
@@ -75,58 +68,8 @@ class TestRunner(object):
failure = result.failures[num_failures][1]
elif len(result.errors) > num_errors:
err = result.errors[num_errors][1]
- self.write_result(result, test_name, stop - start, failure, err)
+ self.printer.print_finished_test(result, test_name, stop - start, failure, err)
- self.write_summary(result, stop - run_start_time)
+ self.printer.print_result(result, stop - run_start_time)
return result
-
- def write_result(self, result, test_name, test_time, failure=None, err=None):
- timing = ''
- if self.options.timing:
- timing = ' %.4fs' % test_time
- if self.options.verbose:
- if failure:
- msg = ' failed'
- elif err:
- msg = ' erred'
- else:
- msg = ' passed'
- self.stream.write(msg + timing + '\n')
- else:
- if failure:
- msg = 'F'
- elif err:
- msg = 'E'
- else:
- msg = '.'
- self.stream.write(msg)
-
- def write_summary(self, result, run_time):
- self.stream.write('\n')
-
- for (test, err) in result.errors:
- self.stream.write("=" * 80 + '\n')
- self.stream.write("ERROR: " + self.test_name(test) + '\n')
- self.stream.write("-" * 80 + '\n')
- for line in err.splitlines():
- self.stream.write(line + '\n')
- self.stream.write('\n')
-
- for (test, failure) in result.failures:
- self.stream.write("=" * 80 + '\n')
- self.stream.write("FAILURE: " + self.test_name(test) + '\n')
- self.stream.write("-" * 80 + '\n')
- for line in failure.splitlines():
- self.stream.write(line + '\n')
- self.stream.write('\n')
-
- self.stream.write('-' * 80 + '\n')
- self.stream.write('Ran %d test%s in %.3fs\n' %
- (result.testsRun, result.testsRun != 1 and "s" or "", run_time))
-
- if result.wasSuccessful():
- self.stream.write('\nOK\n')
- else:
- self.stream.write('FAILED (failures=%d, errors=%d)\n' %
- (len(result.failures), len(result.errors)))
diff --git a/Tools/Scripts/webkitpy/test/runner_unittest.py b/Tools/Scripts/webkitpy/test/runner_unittest.py
index e2ea31aa1..1cf0146fb 100644
--- a/Tools/Scripts/webkitpy/test/runner_unittest.py
+++ b/Tools/Scripts/webkitpy/test/runner_unittest.py
@@ -25,7 +25,8 @@ import StringIO
import unittest
from webkitpy.tool.mocktool import MockOptions
-from webkitpy.test.runner import TestRunner
+from webkitpy.test.printer import Printer
+from webkitpy.test.runner import Runner
class FakeModuleSuite(object):
@@ -74,7 +75,7 @@ class RunnerTest(unittest.TestCase):
loader = FakeLoader(('test1 (Foo)', '.', ''),
('test2 (Foo)', 'F', 'test2\nfailed'),
('test3 (Foo)', 'E', 'test3\nerred'))
- result = TestRunner(stream, options, loader).run(loader.top_suite())
+ result = Runner(Printer(stream, options), options, loader).run(loader.top_suite())
self.assertFalse(result.wasSuccessful())
self.assertEquals(result.testsRun, 3)
self.assertEquals(len(result.failures), 1)
@@ -87,7 +88,7 @@ class RunnerTest(unittest.TestCase):
loader = FakeLoader(('test1 (Foo)', '.', ''),
('test2 (Foo)', 'F', 'test2\nfailed'),
('test3 (Foo)', 'E', 'test3\nerred'))
- result = TestRunner(stream, options, loader).run(loader.top_suite())
+ result = Runner(Printer(stream, options), options, loader).run(loader.top_suite())
self.assertFalse(result.wasSuccessful())
self.assertEquals(result.testsRun, 3)
self.assertEquals(len(result.failures), 1)
@@ -100,7 +101,7 @@ class RunnerTest(unittest.TestCase):
loader = FakeLoader(('test1 (Foo)', '.', ''),
('test2 (Foo)', 'F', 'test2\nfailed'),
('test3 (Foo)', 'E', 'test3\nerred'))
- result = TestRunner(stream, options, loader).run(loader.top_suite())
+ result = Runner(Printer(stream, options), options, loader).run(loader.top_suite())
self.assertFalse(result.wasSuccessful())
self.assertEquals(result.testsRun, 3)
self.assertEquals(len(result.failures), 1)