summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/unittest.rst10
-rw-r--r--Lib/unittest/main.py19
-rw-r--r--Lib/unittest/test/test_program.py79
-rw-r--r--Misc/NEWS11
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()
diff --git a/Misc/NEWS b/Misc/NEWS
index a19025a..59946bd 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.