diff options
-rw-r--r-- | CHANGES.rst | 6 | ||||
-rw-r--r-- | coverage/control.py | 27 | ||||
-rw-r--r-- | coverage/misc.py | 14 | ||||
-rw-r--r-- | doc/whatsnew5x.rst | 5 | ||||
-rw-r--r-- | tests/test_api.py | 27 |
5 files changed, 72 insertions, 7 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index e58ca9e6..3a27c69f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,11 @@ Unreleased one (or many) environments, and then report in another. It has not had much real-world testing, so it may change in incompatible ways in the future. +- When constructing a :class:`coverage.Coverage` object, `data_file` can be + specified as None to prevent writing any data file at all. In previous + versions, an explicit `data_file=None` argument would use the default of + ".coverage". Fixes `issue 871`_. + - Python files run with ``-m`` now have ``__spec__`` defined properly. This fixes `issue 745`_ (about not being able to run unittest tests that spawn subprocesses), and `issue 838`_, which described the problem directly. @@ -46,6 +51,7 @@ Unreleased .. _issue 649: https://github.com/nedbat/coveragepy/issues/649 .. _issue 745: https://github.com/nedbat/coveragepy/issues/745 .. _issue 838: https://github.com/nedbat/coveragepy/issues/838 +.. _issue 871: https://github.com/nedbat/coveragepy/issues/871 .. _changes_50b1: diff --git a/coverage/control.py b/coverage/control.py index 3a19695a..53942242 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -25,7 +25,7 @@ from coverage.html import HtmlReporter from coverage.inorout import InOrOut from coverage.jsonreport import JsonReporter from coverage.misc import CoverageException, bool_or_none, join_regex -from coverage.misc import ensure_dir_for_file, isolate_module +from coverage.misc import DefaultValue, ensure_dir_for_file, isolate_module from coverage.plugin import FileReporter from coverage.plugin_support import Plugins from coverage.python import PythonFileReporter @@ -58,6 +58,8 @@ def override_config(cov, **kwargs): cov.config = original_config +_DEFAULT_DATAFILE = DefaultValue("MISSING") + class Coverage(object): """Programmatic access to coverage.py. @@ -91,16 +93,21 @@ class Coverage(object): return None def __init__( - self, data_file=None, data_suffix=None, cover_pylib=None, + self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None, auto_data=False, timid=None, branch=None, config_file=True, source=None, omit=None, include=None, debug=None, concurrency=None, check_preimported=False, context=None, ): """ - `data_file` is the base name of the data file to use, defaulting to - ".coverage". `data_suffix` is appended (with a dot) to `data_file` to - create the final file name. If `data_suffix` is simply True, then a - suffix is created with the machine and process identity included. + Many of these arguments duplicate and override values that can be + provided in a configuration file. Parameters that are missing here + will use values from the config file. + + `data_file` is the base name of the data file to use. The config value + defaults to ".coverage". None can be provied to prevent writing a data + file. `data_suffix` is appended (with a dot) to `data_file` to create + the final file name. If `data_suffix` is simply True, then a suffix is + created with the machine and process identity included. `cover_pylib` is a boolean determining whether Python code installed with the Python interpreter is measured. This includes the Python @@ -166,6 +173,12 @@ class Coverage(object): The `check_preimported` and `context` parameters. """ + # data_file=None means no disk file at all. data_file missing means + # use the value from the config file. + self._no_disk = data_file is None + if data_file is _DEFAULT_DATAFILE: + data_file = None + # Build our configuration from a number of sources. self.config = read_coverage_config( config_file=config_file, @@ -460,6 +473,7 @@ class Coverage(object): suffix=suffix, warn=self._warn, debug=self._debug, + no_disk=self._no_disk, ) def start(self): @@ -526,6 +540,7 @@ class Coverage(object): self._init_data(suffix=None) self._data.erase(parallel=self.config.parallel) self._data = None + self._inited_for_start = False def switch_context(self, new_context): """Switch to a new dynamic context. diff --git a/coverage/misc.py b/coverage/misc.py index 2f3bcac6..6f8c368a 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -254,6 +254,20 @@ def _needs_to_implement(that, func_name): ) +class DefaultValue(object): + """A sentinel object to use for unusual default-value needs. + + Construct with a string that will be used as the repr, for display in help + and Sphinx output. + + """ + def __init__(self, display_as): + self.display_as = display_as + + def __repr__(self): + return self.display_as + + def substitute_variables(text, variables): """Substitute ``${VAR}`` variables in `text` with their values. diff --git a/doc/whatsnew5x.rst b/doc/whatsnew5x.rst index c805b8d7..4724dee7 100644 --- a/doc/whatsnew5x.rst +++ b/doc/whatsnew5x.rst @@ -33,6 +33,11 @@ Backward Incompatibilities circumstances, you may need to use ``parallel=true`` to avoid multiple processes overwriting each others' data. +- When constructing a :class:`coverage.Coverage` object, `data_file` can be + specified as None to prevent writing any data file at all. In previous + versions, an explicit `data_file=None` argument would use the default of + ".coverage". Fixes :github:`871`. + - The ``[run] note`` setting has been deprecated. Using it will result in a warning, and the note will not be written to the data file. The corresponding :class:`.CoverageData` methods have been removed. diff --git a/tests/test_api.py b/tests/test_api.py index 228f0147..93b6dc9a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -16,7 +16,7 @@ from unittest_mixins import change_dir import coverage from coverage import env -from coverage.backward import StringIO, import_local_file +from coverage.backward import code_object, import_local_file, StringIO from coverage.data import line_counts from coverage.files import abs_file from coverage.misc import CoverageException @@ -263,6 +263,31 @@ class ApiTest(CoverageTest): self.assertFiles(["datatest5.py", "deep"]) self.assert_exists("deep/sub/cov.data") + def test_datafile_none(self): + cov = coverage.Coverage(data_file=None) + + def f1(): + a = 1 # pylint: disable=unused-variable + + one_line_number = code_object(f1).co_firstlineno + 1 + lines = [] + + def run_one_function(f): + cov.erase() + cov.start() + f() + cov.stop() + + fs = cov.get_data().measured_files() + lines.append(cov.get_data().lines(list(fs)[0])) + + run_one_function(f1) + run_one_function(f1) + run_one_function(f1) + assert lines == [[one_line_number]] * 3 + self.assert_doesnt_exist(".coverage") + assert os.listdir(".") == [] + def test_empty_reporting(self): # empty summary reports raise exception, just like the xml report cov = coverage.Coverage() |