diff options
-rw-r--r-- | CHANGES.rst | 4 | ||||
-rw-r--r-- | coverage/cmdline.py | 8 | ||||
-rw-r--r-- | coverage/config.py | 2 | ||||
-rw-r--r-- | doc/config.rst | 7 | ||||
-rw-r--r-- | tests/test_cmdline.py | 47 |
5 files changed, 64 insertions, 4 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 8845d7e5..68128ecd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -17,6 +17,9 @@ Change history for Coverage.py Unreleased ---------- +- You can specify the command line to run your program with the ``[run] + command_line`` configuration setting. `issue 695`_. + - Coverage commands no longer clobber the first entry in sys.path, fixing `issue 715`_. @@ -26,6 +29,7 @@ Unreleased - Combining data files now goes much faster. +.. _issue 695: https://github.com/nedbat/coveragepy/issues/695 .. _issue 715: https://github.com/nedbat/coveragepy/issues/715 .. _issue 716: https://github.com/nedbat/coveragepy/issues/716 diff --git a/coverage/cmdline.py b/coverage/cmdline.py index e6ea6e23..b45547ba 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -8,6 +8,7 @@ from __future__ import print_function import glob import optparse import os.path +import shlex import sys import textwrap import traceback @@ -602,6 +603,13 @@ class CoverageScript(object): """Implementation of 'coverage run'.""" if not args: + command_line = self.coverage.get_option("run:command_line") + if command_line is not None: + args = shlex.split(command_line) + if args and args[0] == "-m": + options.module = True + args = args[1:] + if not args: self.help_fn("Nothing to do.") return ERR diff --git a/coverage/config.py b/coverage/config.py index 2a281875..f61d6951 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -174,6 +174,7 @@ class CoverageConfig(object): # Defaults for [run] self.branch = False + self.command_line = None self.concurrency = None self.context = None self.cover_pylib = False @@ -319,6 +320,7 @@ class CoverageConfig(object): # [run] ('branch', 'run:branch', 'boolean'), + ('command_line', 'run:command_line'), ('concurrency', 'run:concurrency', 'list'), ('context', 'run:context'), ('cover_pylib', 'run:cover_pylib', 'boolean'), diff --git a/doc/config.rst b/doc/config.rst index 8b534637..0b668351 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -115,6 +115,13 @@ to more than one command. ``branch`` (boolean, default False): whether to measure :ref:`branch coverage <branch>` in addition to statement coverage. +``command_line`` (string): the command-line to run your program. This will be +used if you run ``coverage run`` with no further arguments. Coverage.py +options cannot be specified here, other than ``-m`` to indicate the module to +run. + +.. versionadded:: 5.0 + ``concurrency`` (multi-string, default "thread"): the name concurrency libraries in use by the product code. If your program uses `multiprocessing`_, `gevent`_, `greenlet`_, or `eventlet`_, you must name that library in this diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index d1b38b98..e7d3fafa 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -59,9 +59,10 @@ class BaseCmdLineTest(CoverageTest): # same object as the resulting coverage object. mk.Coverage.return_value = mk - # The mock needs to get options, but shouldn't need to set them. + # The mock needs options. config = CoverageConfig() mk.get_option = config.get_option + mk.set_option = config.set_option # Get the type right for the result of reporting. mk.report.return_value = 50.0 @@ -70,14 +71,19 @@ class BaseCmdLineTest(CoverageTest): return mk - def mock_command_line(self, args): + def mock_command_line(self, args, options=None): """Run `args` through the command line, with a Mock. + `options` is a dict of names and values to pass to `set_option`. + Returns the Mock it used and the status code returned. """ m = self.model_object() + for name, value in (options or {}).items(): + m.set_option(name, value) + ret = command_line( args, _covpkg=m, _run_python_file=m.run_python_file, @@ -86,9 +92,9 @@ class BaseCmdLineTest(CoverageTest): return m, ret - def cmd_executes(self, args, code, ret=OK): + def cmd_executes(self, args, code, ret=OK, options=None): """Assert that the `args` end up executing the sequence in `code`.""" - m1, r1 = self.mock_command_line(args) + m1, r1 = self.mock_command_line(args, options=options) self.assertEqual(r1, ret, "Wrong status: got %r, wanted %r" % (r1, ret)) # Remove all indentation, and change ".foo()" to "m2.foo()". @@ -521,6 +527,39 @@ class CmdLineTest(BaseCmdLineTest): self.command_line("run", ret=ERR) self.assertIn("Nothing to do", self.stderr()) + def test_run_from_config(self): + options = {"run:command_line": "myprog.py a 123 'a quoted thing' xyz"} + self.cmd_executes("run", """\ + .Coverage() + .start() + .run_python_file('myprog.py', ['myprog.py', 'a', '123', 'a quoted thing', 'xyz']) + .stop() + .save() + """, + options=options, + ) + + def test_run_module_from_config(self): + options = {"run:command_line": "-m mymodule thing1 thing2"} + self.cmd_executes("run", """\ + .Coverage() + .start() + .run_python_module('mymodule', ['mymodule', 'thing1', 'thing2']) + .stop() + .save() + """, + options=options, + ) + + def test_run_from_config_but_empty(self): + self.cmd_executes("run", """\ + .Coverage() + .help_fn('Nothing to do.') + """, + ret=1, + options={"run:command_line": ""}, + ) + def test_cant_append_parallel(self): self.command_line("run --append --parallel-mode foo.py", ret=ERR) self.assertIn("Can't append to data files in parallel mode.", self.stderr()) |