diff options
-rw-r--r-- | AUTHORS.txt | 1 | ||||
-rw-r--r-- | CHANGES.txt | 4 | ||||
-rw-r--r-- | coverage/cmdline.py | 4 | ||||
-rw-r--r-- | coverage/execfile.py | 68 | ||||
-rw-r--r-- | doc/cmd.rst | 4 | ||||
-rw-r--r-- | test/modules/pkg1/__main__.py | 3 | ||||
-rw-r--r-- | test/modules/pkg1/runmod2.py | 3 | ||||
-rw-r--r-- | test/modules/pkg1/sub/__main__.py | 3 | ||||
-rw-r--r-- | test/modules/pkg1/sub/runmod3.py | 3 | ||||
-rw-r--r-- | test/modules/runmod1.py | 3 | ||||
-rw-r--r-- | test/test_cmdline.py | 30 | ||||
-rw-r--r-- | test/test_execfile.py | 43 |
12 files changed, 136 insertions, 33 deletions
diff --git a/AUTHORS.txt b/AUTHORS.txt index 77bbcbcd..fed3f8ba 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -21,6 +21,7 @@ Patrick Mezard Noel O'Boyle Detlev Offenbach Catherine Proulx +Brandon Rhodes Adi Roiban Greg Rogers George Song diff --git a/CHANGES.txt b/CHANGES.txt index d338e155..e8f463f9 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -11,6 +11,9 @@ Version 3.5 On a file page, ``r``, ``m``, ``x``, and ``p`` toggle the run, missing, excluded, and partial line markings. +- Modules can now be run directly using ``coverage run -m modulename``, to + mirror Python's ``-m`` flag. Closes `issue 95_`, thanks, Brandon Rhodes. + - A little bit of Jython support: `coverage run` can now measure Jython execution by adapting when $py.class files are traced. Thanks, Adi Roiban. @@ -27,6 +30,7 @@ Version 3.5 Brett Cannon. .. _issue 93: http://bitbucket.org/ned/coveragepy/issue/93/copying-a-mock-object-breaks-coverage +.. _issue 95: https://bitbucket.org/ned/coveragepy/issue/95/run-subcommand-should-take-a-module-name .. _issue 104: https://bitbucket.org/ned/coveragepy/issue/104/explicitly-close-files .. _issue 107: https://bitbucket.org/ned/coveragepy/issue/107/codeparser-not-opening-source-files-with diff --git a/coverage/cmdline.py b/coverage/cmdline.py index b82448bf..cdcf3178 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -73,8 +73,8 @@ class Opts(object): ) module = optparse.make_option( '-m', '--module', action='store_true', - help="First argument is a Python module, not a script path," - " which should be run as `python -m` would run it." + help="<pyfile> is an importable Python module, not a script path, " + "to be run as 'python -m' would run it." ) rcfile = optparse.make_option( '', '--rcfile', action='store', diff --git a/coverage/execfile.py b/coverage/execfile.py index 335bf617..a32957c1 100644 --- a/coverage/execfile.py +++ b/coverage/execfile.py @@ -17,35 +17,48 @@ except KeyError: def run_python_module(modulename, args): """Run a python module, as though with ``python -m name args...``. + `modulename` is the name of the module, possibly a dot-separated name. + `args` is the argument array to present as sys.argv, including the first + element naming the module being executed. + """ - # Search for the module - inside its parent package, if any - using - # standard import mechanics. - if '.' in modulename: - packagename, name = modulename.rsplit('.') - package = __import__(packagename, fromlist=['__path__']) - searchpath = package.__path__ - else: - packagename = None - name = modulename - searchpath = None # means "top-level search" to find_module() - openfile, pathname, description = imp.find_module(name, searchpath) - - # Complain if this is a magic non-file module. - if openfile is None and pathname is None: - raise NoSource("module does not live in a file: %r" % modulename) - - # If `modulename` is actually a package, not a mere module, then we - # pretend to be Python 2.7 and try running its __main__.py script. - if openfile is None: - packagename = modulename - name = '__main__' - - package = __import__(packagename, fromlist=['__path__']) - searchpath = package.__path__ - openfile, pathname, description = imp.find_module(name, searchpath) + openfile = None + glo, loc = globals(), locals() + try: + try: + # Search for the module - inside its parent package, if any - using + # standard import mechanics. + if '.' in modulename: + packagename, name = modulename.rsplit('.', 1) + package = __import__(packagename, glo, loc, ['__path__']) + searchpath = package.__path__ + else: + packagename, name = None, modulename + searchpath = None # "top-level search" in imp.find_module() + openfile, pathname, _ = imp.find_module(name, searchpath) + + # Complain if this is a magic non-file module. + if openfile is None and pathname is None: + raise NoSource( + "module does not live in a file: %r" % modulename + ) + + # If `modulename` is actually a package, not a mere module, then we + # pretend to be Python 2.7 and try running its __main__.py script. + if openfile is None: + packagename = modulename + name = '__main__' + package = __import__(packagename, glo, loc, ['__path__']) + searchpath = package.__path__ + openfile, pathname, _ = imp.find_module(name, searchpath) + except ImportError: + _, err, _ = sys.exc_info() + raise NoSource(str(err)) + finally: + if openfile: + openfile.close() # Finally, hand the file off to run_python_file for execution. - openfile.close() run_python_file(pathname, args, package=packagename) @@ -54,7 +67,8 @@ def run_python_file(filename, args, package=None): `filename` is the path to the file to execute, it need not be a .py file. `args` is the argument array to present as sys.argv, including the first - element representing the file being executed. + element naming the file being executed. `package` is the name of the + enclosing package, if any. """ # Create a module to serve as __main__ diff --git a/doc/cmd.rst b/doc/cmd.rst index fa2f0661..ecc24ba1 100644 --- a/doc/cmd.rst +++ b/doc/cmd.rst @@ -66,7 +66,9 @@ command:: Your program runs just as if it had been invoked with the Python command line. Arguments after your file name are passed to your program as usual in -``sys.argv``. +``sys.argv``. Rather than providing a filename, you can use the ``-m`` switch +and specify an importable module name instead, just as you can with the +Python ``-m`` switch. If you want :ref:`branch coverage <branch>` measurement, use the ``--branch`` flag. Otherwise only statement coverage is measured. diff --git a/test/modules/pkg1/__main__.py b/test/modules/pkg1/__main__.py new file mode 100644 index 00000000..66ce5956 --- /dev/null +++ b/test/modules/pkg1/__main__.py @@ -0,0 +1,3 @@ +# Used in the tests for run_python_module +import sys +print("pkg1.__main__: passed %s" % sys.argv[1]) diff --git a/test/modules/pkg1/runmod2.py b/test/modules/pkg1/runmod2.py new file mode 100644 index 00000000..b52964cb --- /dev/null +++ b/test/modules/pkg1/runmod2.py @@ -0,0 +1,3 @@ +# Used in the tests for run_python_module +import sys +print("runmod2: passed %s" % sys.argv[1]) diff --git a/test/modules/pkg1/sub/__main__.py b/test/modules/pkg1/sub/__main__.py new file mode 100644 index 00000000..b5be9f1c --- /dev/null +++ b/test/modules/pkg1/sub/__main__.py @@ -0,0 +1,3 @@ +# Used in the tests for run_python_module +import sys +print("pkg1.sub.__main__: passed %s" % sys.argv[1]) diff --git a/test/modules/pkg1/sub/runmod3.py b/test/modules/pkg1/sub/runmod3.py new file mode 100644 index 00000000..3a1ad155 --- /dev/null +++ b/test/modules/pkg1/sub/runmod3.py @@ -0,0 +1,3 @@ +# Used in the tests for run_python_module +import sys +print("runmod3: passed %s" % sys.argv[1]) diff --git a/test/modules/runmod1.py b/test/modules/runmod1.py new file mode 100644 index 00000000..671d81ef --- /dev/null +++ b/test/modules/runmod1.py @@ -0,0 +1,3 @@ +# Used in the tests for run_python_module +import sys +print("runmod1: passed %s" % sys.argv[1]) diff --git a/test/test_cmdline.py b/test/test_cmdline.py index a9858d51..d4cc763d 100644 --- a/test/test_cmdline.py +++ b/test/test_cmdline.py @@ -33,7 +33,8 @@ class CmdLineTest(CoverageTest): """ m = self.model_object() ret = coverage.CoverageScript( - _covpkg=m, _run_python_file=m.run_python_file, _help_fn=m.help_fn + _covpkg=m, _run_python_file=m.run_python_file, + _run_python_module=m.run_python_module, _help_fn=m.help_fn ).command_line(shlex.split(args)) return m, ret @@ -521,6 +522,33 @@ class NewCmdLineTest(CmdLineTest): .save() """) + def test_run_module(self): + self.cmd_executes("run -m mymodule", """\ + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .erase() + .start() + .run_python_module('mymodule', ['mymodule']) + .stop() + .save() + """) + self.cmd_executes("run -m mymodule -qq arg1 arg2", """\ + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None) + .erase() + .start() + .run_python_module('mymodule', ['mymodule', '-qq', 'arg1', 'arg2']) + .stop() + .save() + """) + self.cmd_executes("run --branch -m mymodule", """\ + .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None) + .erase() + .start() + .run_python_module('mymodule', ['mymodule']) + .stop() + .save() + """) + self.cmd_executes_same("run -m mymodule", "run --module mymodule") + def test_xml(self): # coverage xml [-i] [--omit DIR,...] [FILE1 FILE2 ...] self.cmd_executes("xml", self.INIT_LOAD + """\ diff --git a/test/test_execfile.py b/test/test_execfile.py index f6e4dd7f..1c5b8024 100644 --- a/test/test_execfile.py +++ b/test/test_execfile.py @@ -2,7 +2,7 @@ import os, sys -from coverage.execfile import run_python_file +from coverage.execfile import run_python_file, run_python_module from coverage.misc import NoSource sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k @@ -10,7 +10,7 @@ from coveragetest import CoverageTest here = os.path.dirname(__file__) -class RunTest(CoverageTest): +class RunFileTest(CoverageTest): """Test cases for `run_python_file`.""" def test_run_python_file(self): @@ -76,3 +76,42 @@ class RunTest(CoverageTest): def test_no_such_file(self): self.assertRaises(NoSource, run_python_file, "xyzzy.py", []) + + +class RunModuleTest(CoverageTest): + """Test run_python_module.""" + + run_in_temp_dir = False + + def setUp(self): + super(RunModuleTest, self).setUp() + # Parent class saves and restores sys.path, we can just modify it. + sys.path.append(self.nice_file(os.path.dirname(__file__), 'modules')) + + def test_runmod1(self): + run_python_module("runmod1", ["runmod1", "hello"]) + self.assertEqual(self.stdout(), "runmod1: passed hello\n") + + def test_runmod2(self): + run_python_module("pkg1.runmod2", ["runmod2", "hello"]) + self.assertEqual(self.stdout(), "runmod2: passed hello\n") + + def test_runmod3(self): + run_python_module("pkg1.sub.runmod3", ["runmod3", "hello"]) + self.assertEqual(self.stdout(), "runmod3: passed hello\n") + + def test_pkg1_main(self): + run_python_module("pkg1", ["pkg1", "hello"]) + self.assertEqual(self.stdout(), "pkg1.__main__: passed hello\n") + + def test_pkg1_sub_main(self): + run_python_module("pkg1.sub", ["pkg1.sub", "hello"]) + self.assertEqual(self.stdout(), "pkg1.sub.__main__: passed hello\n") + + def test_no_such_module(self): + self.assertRaises(NoSource, run_python_module, "i_dont_exist", []) + self.assertRaises(NoSource, run_python_module, "i.dont_exist", []) + self.assertRaises(NoSource, run_python_module, "i.dont.exist", []) + + def test_no_main(self): + self.assertRaises(NoSource, run_python_module, "pkg2", ["pkg2", "hi"]) |