Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Doc/library/unittest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2255,7 +2255,8 @@ Loading and running tests

The *testRunner* argument can either be a test runner class or an already
created instance of it. By default ``main`` calls :func:`sys.exit` with
an exit code indicating success or failure of the tests run.
an exit code indicating success (0) or failure (1) of the tests run.
An exit code of 5 indicates that no tests were run.

The *testLoader* argument has to be a :class:`TestLoader` instance,
and defaults to :data:`defaultTestLoader`.
Expand Down
57 changes: 37 additions & 20 deletions Lib/test/test_unittest/test_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,22 @@ def testExpectedFailure(self):
def testUnexpectedSuccess(self):
pass

class FooBarLoader(unittest.TestLoader):
"""Test loader that returns a suite containing FooBar."""
class Empty(unittest.TestCase):
pass

class TestLoader(unittest.TestLoader):
"""Test loader that returns a suite containing testsuite."""

def __init__(self, testcase):
self.testcase = testcase

def loadTestsFromModule(self, module):
return self.suiteClass(
[self.loadTestsFromTestCase(Test_TestProgram.FooBar)])
[self.loadTestsFromTestCase(self.testcase)])

def loadTestsFromNames(self, names, module):
return self.suiteClass(
[self.loadTestsFromTestCase(Test_TestProgram.FooBar)])
[self.loadTestsFromTestCase(self.testcase)])

def test_defaultTest_with_string(self):
class FakeRunner(object):
Expand All @@ -92,7 +99,7 @@ def run(self, test):
runner = FakeRunner()
program = unittest.TestProgram(testRunner=runner, exit=False,
defaultTest='test.test_unittest',
testLoader=self.FooBarLoader())
testLoader=self.TestLoader(self.FooBar))
sys.argv = old_argv
self.assertEqual(('test.test_unittest',), program.testNames)

Expand All @@ -108,7 +115,7 @@ def run(self, test):
program = unittest.TestProgram(
testRunner=runner, exit=False,
defaultTest=['test.test_unittest', 'test.test_unittest2'],
testLoader=self.FooBarLoader())
testLoader=self.TestLoader(self.FooBar))
sys.argv = old_argv
self.assertEqual(['test.test_unittest', 'test.test_unittest2'],
program.testNames)
Expand All @@ -118,7 +125,7 @@ def test_NonExit(self):
program = unittest.main(exit=False,
argv=["foobar"],
testRunner=unittest.TextTestRunner(stream=stream),
testLoader=self.FooBarLoader())
testLoader=self.TestLoader(self.FooBar))
self.assertTrue(hasattr(program, 'result'))
out = stream.getvalue()
self.assertIn('\nFAIL: testFail ', out)
Expand All @@ -130,13 +137,13 @@ def test_NonExit(self):

def test_Exit(self):
stream = BufferedWriter()
self.assertRaises(
SystemExit,
unittest.main,
argv=["foobar"],
testRunner=unittest.TextTestRunner(stream=stream),
exit=True,
testLoader=self.FooBarLoader())
with self.assertRaises(SystemExit) as cm:
unittest.main(
argv=["foobar"],
testRunner=unittest.TextTestRunner(stream=stream),
exit=True,
testLoader=self.TestLoader(self.FooBar))
self.assertEqual(cm.exception.code, 1)
out = stream.getvalue()
self.assertIn('\nFAIL: testFail ', out)
self.assertIn('\nERROR: testError ', out)
Expand All @@ -147,12 +154,11 @@ def test_Exit(self):

def test_ExitAsDefault(self):
stream = BufferedWriter()
self.assertRaises(
SystemExit,
unittest.main,
argv=["foobar"],
testRunner=unittest.TextTestRunner(stream=stream),
testLoader=self.FooBarLoader())
with self.assertRaises(SystemExit):
unittest.main(
argv=["foobar"],
testRunner=unittest.TextTestRunner(stream=stream),
testLoader=self.TestLoader(self.FooBar))
out = stream.getvalue()
self.assertIn('\nFAIL: testFail ', out)
self.assertIn('\nERROR: testError ', out)
Expand All @@ -161,6 +167,17 @@ def test_ExitAsDefault(self):
'expected failures=1, unexpected successes=1)\n')
self.assertTrue(out.endswith(expected))

def test_ExitEmptySuite(self):
stream = BufferedWriter()
with self.assertRaises(SystemExit) as cm:
unittest.main(
argv=["empty"],
testRunner=unittest.TextTestRunner(stream=stream),
testLoader=self.TestLoader(self.Empty))
self.assertEqual(cm.exception.code, 5)
out = stream.getvalue()
self.assertIn('\nNO TESTS RUN\n', out)


class InitialisableProgram(unittest.TestProgram):
exit = False
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_unittest/test_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ def testFailFastSetByRunner(self):
stream = BufferedWriter()
runner = unittest.TextTestRunner(stream=stream, failfast=True)
def test(result):
result.testsRun += 1
self.assertTrue(result.failfast)
result = runner.run(test)
stream.flush()
Expand Down
10 changes: 10 additions & 0 deletions Lib/test/test_unittest/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,16 @@ def test(self):
'inner setup', 'inner test', 'inner cleanup',
'end outer test', 'outer cleanup'])

def test_run_empty_suite_error_message(self):
class EmptyTest(unittest.TestCase):
pass

suite = unittest.defaultTestLoader.loadTestsFromTestCase(EmptyTest)
runner = getRunner()
runner.run(suite)

self.assertIn("\nNO TESTS RUN\n", runner.stream.getvalue())


class TestModuleCleanUp(unittest.TestCase):
def test_add_and_do_ModuleCleanup(self):
Expand Down
7 changes: 6 additions & 1 deletion Lib/unittest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ def runTests(self):
testRunner = self.testRunner
self.result = testRunner.run(self.test)
if self.exit:
sys.exit(not self.result.wasSuccessful())
if self.result.testsRun == 0:
sys.exit(5)
elif not self.result.wasSuccessful():
sys.exit(1)
else:
sys.exit(0)

main = TestProgram
2 changes: 2 additions & 0 deletions Lib/unittest/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ def run(self, test):
infos.append("failures=%d" % failed)
if errored:
infos.append("errors=%d" % errored)
elif run == 0:
self.stream.write("NO TESTS RUN")
else:
self.stream.write("OK")
if skipped:
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,7 @@ Vlad Riscutia
Wes Rishel
Daniel Riti
Juan M. Bello Rivas
Stefano Rivera
Llandy Riveron Del Risco
Mohd Sanad Zaki Rizvi
Davide Rizzo
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The :mod:`unittest` runner will now exit with status code 5, if no tests
were run. It is common for test runner misconfiguration to fail to find any
tests, this should be an error.