Skip to content

Commit

Permalink
Coverage(data_file=None) means no data file at all. #871
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Dec 3, 2019
1 parent ae25eef commit b75575f
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 7 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Expand Up @@ -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.
Expand All @@ -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:
Expand Down
27 changes: 21 additions & 6 deletions coverage/control.py
Expand Up @@ -25,7 +25,7 @@
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -91,16 +93,21 @@ def current(cls):
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
Expand Down Expand Up @@ -166,6 +173,12 @@ def __init__(
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,
Expand Down Expand Up @@ -460,6 +473,7 @@ def _init_data(self, suffix):
suffix=suffix,
warn=self._warn,
debug=self._debug,
no_disk=self._no_disk,
)

def start(self):
Expand Down Expand Up @@ -526,6 +540,7 @@ def erase(self):
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.
Expand Down
14 changes: 14 additions & 0 deletions coverage/misc.py
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew5x.rst
Expand Up @@ -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.
Expand Down
27 changes: 26 additions & 1 deletion tests/test_api.py
Expand Up @@ -16,7 +16,7 @@

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
Expand Down Expand Up @@ -263,6 +263,31 @@ def test_deep_datafile(self):
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()
Expand Down

0 comments on commit b75575f

Please sign in to comment.