diff options
-rw-r--r-- | Doc/library/unittest.rst | 10 | ||||
-rw-r--r-- | Lib/unittest/main.py | 19 | ||||
-rw-r--r-- | Lib/unittest/test/test_program.py | 79 | ||||
-rw-r--r-- | Misc/NEWS | 11 |
4 files changed, 114 insertions, 5 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst index 2b9c2be..63ad705 100644 --- a/Doc/library/unittest.rst +++ b/Doc/library/unittest.rst @@ -204,6 +204,16 @@ modules, classes or even individual test methods:: You can pass in a list with any combination of module names, and fully qualified class or method names. +Test modules can be specified by file path as well:: + + python -m unittest tests/test_something.py + +This allows you to use the shell filename completion to specify the test module. +The file specified must still be importable as a module. The path is converted +to a module name by removing the '.py' and converting path separators into '.'. +If you want to execute a test file that isn't importable as a module you should +execute the file directly instead. + You can run tests with more detail (higher verbosity) by passing in the -v flag:: python -m unittest -v test_module diff --git a/Lib/unittest/main.py b/Lib/unittest/main.py index bd5f2a4..b25d7ac 100644 --- a/Lib/unittest/main.py +++ b/Lib/unittest/main.py @@ -58,7 +58,24 @@ Examples: in MyTestCase """ +def _convert_name(name): + # on Linux / Mac OS X 'foo.PY' is not importable, but on + # Windows it is. Simpler to do a case insensitive match + # a better check would be to check that the name is a + # valid Python module name. + if os.path.isfile(name) and name.lower().endswith('.py'): + if os.path.isabs(name): + rel_path = os.path.relpath(name, os.getcwd()) + if os.path.isabs(rel_path) or rel_path.startswith(os.pardir): + return name + name = rel_path + # on Windows both '\' and '/' are used as path + # separators. Better to replace both than rely on os.path.sep + return name[:-3].replace('\\', '.').replace('/', '.') + return name +def _convert_names(names): + return [_convert_name(name) for name in names] class TestProgram(object): """A command-line program that runs a set of tests; this is primarily @@ -153,7 +170,7 @@ class TestProgram(object): # createTests will load tests from self.module self.testNames = None elif len(args) > 0: - self.testNames = args + self.testNames = _convert_names(args) if __name__ == '__main__': # to support python -m unittest ... self.module = None diff --git a/Lib/unittest/test/test_program.py b/Lib/unittest/test/test_program.py index 2eb0be3..deddd8c 100644 --- a/Lib/unittest/test/test_program.py +++ b/Lib/unittest/test/test_program.py @@ -273,6 +273,85 @@ class TestCommandLineArgs(unittest.TestCase): program.runTests() self.assertTrue(self.installed) + def _patch_isfile(self, names, exists=True): + def isfile(path): + return path in names + original = os.path.isfile + os.path.isfile = isfile + def restore(): + os.path.isfile = original + self.addCleanup(restore) + + + def testParseArgsFileNames(self): + # running tests with filenames instead of module names + program = self.program + argv = ['progname', 'foo.py', 'bar.Py', 'baz.PY', 'wing.txt'] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + # note that 'wing.txt' is not a Python file so the name should + # *not* be converted to a module name + expected = ['foo', 'bar', 'baz', 'wing.txt'] + self.assertEqual(program.testNames, expected) + + + def testParseArgsFilePaths(self): + program = self.program + argv = ['progname', 'foo/bar/baz.py', 'green\\red.py'] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + expected = ['foo.bar.baz', 'green.red'] + self.assertEqual(program.testNames, expected) + + + def testParseArgsNonExistentFiles(self): + program = self.program + argv = ['progname', 'foo/bar/baz.py', 'green\\red.py'] + self._patch_isfile([]) + + program.createTests = lambda: None + program.parseArgs(argv) + + self.assertEqual(program.testNames, argv[1:]) + + def testParseArgsAbsolutePathsThatCanBeConverted(self): + cur_dir = os.getcwd() + program = self.program + def _join(name): + return os.path.join(cur_dir, name) + argv = ['progname', _join('foo/bar/baz.py'), _join('green\\red.py')] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + expected = ['foo.bar.baz', 'green.red'] + self.assertEqual(program.testNames, expected) + + def testParseArgsAbsolutePathsThatCannotBeConverted(self): + program = self.program + # will this test work on Windows? (is '/...' considered absolute?) + argv = ['progname', '/foo/bar/baz.py', '/green/red.py'] + self._patch_isfile(argv) + + program.createTests = lambda: None + program.parseArgs(argv) + + self.assertEqual(program.testNames, argv[1:]) + + # it may be better to use platform specific functions to normalise paths + # rather than accepting '.PY' and '\' as file seprator on Linux / Mac + # it would also be better to check that a filename is a valid module + # identifier (we have a regex for this in loader.py) + # for invalid filenames should we raise a useful error rather than + # leaving the current error message (import of filename fails) in place? + if __name__ == '__main__': unittest.main() @@ -37,6 +37,13 @@ Core and Builtins Library ------- +- Issue 10620: `python -m unittest` can accept file paths instead of module + names for running specific tests. + +- Issue #9424: Deprecate the `unittest.TestCase` methods `assertEquals`, + `assertNotEquals`, `assertAlmostEquals`, `assertNotAlmostEquals` and `assert_` + and replace them with the correct methods in the Python test suite. + - Issue #10272: The ssl module now raises socket.timeout instead of a generic SSLError on socket timeouts. @@ -236,10 +243,6 @@ Tests - `python -m test` can be used to run the test suite as well as `python -m test.regrtest`. -- Issue #9424: Deprecate the `unittest.TestCase` methods `assertEquals`, - `assertNotEquals`, `assertAlmostEquals`, `assertNotAlmostEquals` and `assert_` - and replace them with the correct methods in the Python test suite. - - Do not fail test_socket when the IP address of the local hostname cannot be looked up. |