summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2018-10-18 15:10:58 -0400
committerNed Batchelder <ned@nedbatchelder.com>2018-10-18 15:12:17 -0400
commite57549076220764ace6f2b67da3600ff5ae33f02 (patch)
treef58432568c9acdd2d6707f7f55e9155ec98d0b04
parentadf376f699e9ce977dcc47329f1cef7a7a8ce9c7 (diff)
downloadpython-coveragepy-git-e57549076220764ace6f2b67da3600ff5ae33f02.tar.gz
`[run] command_line` is the command line to use for 'coverage run'. #695
-rw-r--r--CHANGES.rst4
-rw-r--r--coverage/cmdline.py8
-rw-r--r--coverage/config.py2
-rw-r--r--doc/config.rst7
-rw-r--r--tests/test_cmdline.py47
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())