summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-11-13 19:23:24 -0500
committerNed Batchelder <ned@nedbatchelder.com>2021-11-14 08:12:49 -0500
commitb82e9fd8766a77c2a275bde7b574f3e8cb529f8f (patch)
treef310ea38a19f2b4e8c93ee3e3a5b8a99aa43351a
parent342e7da2941ae5291f1a94b6ad66ce489f6985fe (diff)
downloadpython-coveragepy-git-nedbat/exceptions.tar.gz
refactor: specialize exceptionsnedbat/exceptions
CoverageException is fine as a base class, but not good to use for raising (and catching sometimes). Introduce specialized exceptions that allow third-party tools to integrate better.
-rw-r--r--coverage/collector.py10
-rw-r--r--coverage/config.py18
-rw-r--r--coverage/control.py8
-rw-r--r--coverage/data.py8
-rw-r--r--coverage/exceptions.py19
-rw-r--r--coverage/files.py4
-rw-r--r--coverage/html.py4
-rw-r--r--coverage/inorout.py4
-rw-r--r--coverage/plugin_support.py4
-rw-r--r--coverage/report.py4
-rw-r--r--coverage/results.py4
-rw-r--r--coverage/sqldata.py32
-rw-r--r--coverage/summary.py6
-rw-r--r--coverage/tomlconfig.py6
-rw-r--r--tests/test_api.py10
-rw-r--r--tests/test_config.py36
-rw-r--r--tests/test_coverage.py8
-rw-r--r--tests/test_data.py42
-rw-r--r--tests/test_files.py4
-rw-r--r--tests/test_plugins.py6
-rw-r--r--tests/test_results.py4
-rw-r--r--tests/test_summary.py4
22 files changed, 132 insertions, 113 deletions
diff --git a/coverage/collector.py b/coverage/collector.py
index 6f9aa2c1..89ba66ba 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -9,7 +9,7 @@ import sys
from coverage import env
from coverage.debug import short_stack
from coverage.disposition import FileDisposition
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.misc import human_sorted, isolate_module
from coverage.pytracer import PyTracer
@@ -116,7 +116,7 @@ class Collector:
# We can handle a few concurrency options here, but only one at a time.
these_concurrencies = self.SUPPORTED_CONCURRENCIES.intersection(concurrency)
if len(these_concurrencies) > 1:
- raise CoverageException(f"Conflicting concurrency settings: {concurrency}")
+ raise ConfigError(f"Conflicting concurrency settings: {concurrency}")
self.concurrency = these_concurrencies.pop() if these_concurrencies else ''
try:
@@ -136,9 +136,9 @@ class Collector:
import threading
self.threading = threading
else:
- raise CoverageException(f"Don't understand concurrency={concurrency}")
+ raise ConfigError(f"Don't understand concurrency={concurrency}")
except ImportError as ex:
- raise CoverageException(
+ raise ConfigError(
"Couldn't trace with concurrency={}, the module isn't installed.".format(
self.concurrency,
)
@@ -245,7 +245,7 @@ class Collector:
if hasattr(tracer, 'concur_id_func'):
tracer.concur_id_func = self.concur_id_func
elif self.concur_id_func:
- raise CoverageException(
+ raise ConfigError(
"Can't support concurrency={} with {}, only threads are supported".format(
self.concurrency, self.tracer_name(),
)
diff --git a/coverage/config.py b/coverage/config.py
index 3b873579..d73f69eb 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -10,7 +10,7 @@ import os
import os.path
import re
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.misc import contract, isolate_module, substitute_variables
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
@@ -59,7 +59,7 @@ class HandyConfigParser(configparser.RawConfigParser):
real_section = section_prefix + section
if configparser.RawConfigParser.has_section(self, real_section):
return configparser.RawConfigParser.options(self, real_section)
- raise configparser.NoSectionError(section)
+ raise ConfigError(f"No section: {section!r}")
def get_section(self, section):
"""Get the contents of a section, as a dictionary."""
@@ -83,7 +83,7 @@ class HandyConfigParser(configparser.RawConfigParser):
if configparser.RawConfigParser.has_option(self, real_section, option):
break
else:
- raise configparser.NoOptionError(option, section)
+ raise ConfigError(f"No option {option!r} in section: {section!r}")
v = configparser.RawConfigParser.get(self, real_section, option, *args, **kwargs)
v = substitute_variables(v, os.environ)
@@ -123,7 +123,7 @@ class HandyConfigParser(configparser.RawConfigParser):
try:
re.compile(value)
except re.error as e:
- raise CoverageException(
+ raise ConfigError(
f"Invalid [{section}].{option} value {value!r}: {e}"
) from e
if value:
@@ -272,7 +272,7 @@ class CoverageConfig:
try:
files_read = cp.read(filename)
except (configparser.Error, TomlDecodeError) as err:
- raise CoverageException(f"Couldn't read config file {filename}: {err}") from err
+ raise ConfigError(f"Couldn't read config file {filename}: {err}") from err
if not files_read:
return False
@@ -285,7 +285,7 @@ class CoverageConfig:
if was_set:
any_set = True
except ValueError as err:
- raise CoverageException(f"Couldn't read config file {filename}: {err}") from err
+ raise ConfigError(f"Couldn't read config file {filename}: {err}") from err
# Check that there are no unrecognized options.
all_options = collections.defaultdict(set)
@@ -443,7 +443,7 @@ class CoverageConfig:
return
# If we get here, we didn't find the option.
- raise CoverageException(f"No such option: {option_name!r}")
+ raise ConfigError(f"No such option: {option_name!r}")
def get_option(self, option_name):
"""Get an option from the configuration.
@@ -471,7 +471,7 @@ class CoverageConfig:
return self.plugin_options.get(plugin_name, {}).get(key)
# If we get here, we didn't find the option.
- raise CoverageException(f"No such option: {option_name!r}")
+ raise ConfigError(f"No such option: {option_name!r}")
def post_process_file(self, path):
"""Make final adjustments to a file path to make it usable."""
@@ -546,7 +546,7 @@ def read_coverage_config(config_file, warn, **kwargs):
if config_read:
break
if specified_file:
- raise CoverageException(f"Couldn't read {fname!r} as a config file")
+ raise ConfigError(f"Couldn't read {fname!r} as a config file")
# $set_env.py: COVERAGE_DEBUG - Options for --debug.
# 3) from environment variables:
diff --git a/coverage/control.py b/coverage/control.py
index 47e741aa..c80a74f5 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -21,7 +21,7 @@ from coverage.context import should_start_context_test_function, combine_context
from coverage.data import CoverageData, combine_parallel_data
from coverage.debug import DebugControl, short_stack, write_formatted_info
from coverage.disposition import disposition_debug_msg
-from coverage.exceptions import CoverageException, CoverageWarning
+from coverage.exceptions import ConfigError, CoverageException, CoverageWarning, PluginError
from coverage.files import PathAliases, abs_file, relative_filename, set_relative_directory
from coverage.html import HtmlReporter
from coverage.inorout import InOrOut
@@ -449,7 +449,7 @@ class Coverage:
concurrency = self.config.concurrency or ()
if "multiprocessing" in concurrency:
if not patch_multiprocessing:
- raise CoverageException( # pragma: only jython
+ raise ConfigError( # pragma: only jython
"multiprocessing is not supported on this Python"
)
patch_multiprocessing(rcfile=self.config.config_file)
@@ -460,7 +460,7 @@ class Coverage:
elif dycon == "test_function":
context_switchers = [should_start_context_test_function]
else:
- raise CoverageException(f"Don't understand dynamic_context setting: {dycon!r}")
+ raise ConfigError(f"Don't understand dynamic_context setting: {dycon!r}")
context_switchers.extend(
plugin.dynamic_context for plugin in self._plugins.context_switchers
@@ -835,7 +835,7 @@ class Coverage:
if plugin:
file_reporter = plugin.file_reporter(mapped_morf)
if file_reporter is None:
- raise CoverageException(
+ raise PluginError(
"Plugin {!r} did not provide a file reporter for {!r}.".format(
plugin._coverage_plugin_name, morf
)
diff --git a/coverage/data.py b/coverage/data.py
index e7c94b4f..c70993ad 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -13,7 +13,7 @@ imports working.
import glob
import os.path
-from coverage.exceptions import CoverageException
+from coverage.exceptions import CoverageException, NoDataError
from coverage.misc import file_be_gone
from coverage.sqldata import CoverageData
@@ -72,7 +72,7 @@ def combinable_files(data_file, data_paths=None):
pattern = os.path.join(os.path.abspath(p), f"{local}.*")
files_to_combine.extend(glob.glob(pattern))
else:
- raise CoverageException(f"Couldn't combine from non-existent path '{p}'")
+ raise NoDataError(f"Couldn't combine from non-existent path '{p}'")
return files_to_combine
@@ -107,7 +107,7 @@ def combine_parallel_data(
files_to_combine = combinable_files(data.base_filename(), data_paths)
if strict and not files_to_combine:
- raise CoverageException("No data to combine")
+ raise NoDataError("No data to combine")
files_combined = 0
for f in files_to_combine:
@@ -138,4 +138,4 @@ def combine_parallel_data(
file_be_gone(f)
if strict and not files_combined:
- raise CoverageException("No usable data files")
+ raise NoDataError("No usable data files")
diff --git a/coverage/exceptions.py b/coverage/exceptions.py
index de2257a1..7352e9fc 100644
--- a/coverage/exceptions.py
+++ b/coverage/exceptions.py
@@ -14,6 +14,20 @@ class CoverageException(_BaseCoverageException):
pass
+class ConfigError(_BaseCoverageException):
+ """A problem with a config file, or a value in one."""
+ pass
+
+
+class DataError(CoverageException):
+ """An error in using a data file."""
+ pass
+
+class NoDataError(CoverageException):
+ """We didn't have data to work with."""
+ pass
+
+
class NoSource(CoverageException):
"""We couldn't find the source for a module."""
pass
@@ -29,6 +43,11 @@ class NotPython(CoverageException):
pass
+class PluginError(CoverageException):
+ """A plugin misbehaved."""
+ pass
+
+
class _ExceptionDuringRun(CoverageException):
"""An exception happened while running customer code.
diff --git a/coverage/files.py b/coverage/files.py
index a721e533..afb0dbf5 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -13,7 +13,7 @@ import re
import sys
from coverage import env
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.misc import contract, human_sorted, isolate_module, join_regex
@@ -356,7 +356,7 @@ class PathAliases:
# The pattern can't end with a wildcard component.
if pattern.endswith("*"):
- raise CoverageException("Pattern must not end with wildcards.")
+ raise ConfigError("Pattern must not end with wildcards.")
# The pattern is meant to match a filepath. Let's make it absolute
# unless it already is, or is meant to match any prefix.
diff --git a/coverage/html.py b/coverage/html.py
index 2acc8aba..ae1d041f 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -12,7 +12,7 @@ import types
import coverage
from coverage.data import add_data_to_hash
-from coverage.exceptions import CoverageException
+from coverage.exceptions import NoDataError
from coverage.files import flat_rootname
from coverage.misc import ensure_dir, file_be_gone, Hasher, isolate_module, format_local_datetime
from coverage.misc import human_sorted, plural
@@ -208,7 +208,7 @@ class HtmlReporter:
self.html_file(fr, analysis)
if not self.all_files_nums:
- raise CoverageException("No data to report.")
+ raise NoDataError("No data to report.")
self.totals = sum(self.all_files_nums)
diff --git a/coverage/inorout.py b/coverage/inorout.py
index 87afdaaf..69084b26 100644
--- a/coverage/inorout.py
+++ b/coverage/inorout.py
@@ -15,7 +15,7 @@ import traceback
from coverage import env
from coverage.disposition import FileDisposition, disposition_init
-from coverage.exceptions import CoverageException
+from coverage.exceptions import CoverageException, PluginError
from coverage.files import TreeMatcher, FnmatchMatcher, ModuleMatcher
from coverage.files import prep_patterns, find_python_files, canonical_filename
from coverage.misc import sys_modules_saved
@@ -392,7 +392,7 @@ class InOrOut:
if not disp.has_dynamic_filename:
if not disp.source_filename:
- raise CoverageException(
+ raise PluginError(
f"Plugin {plugin!r} didn't set source_filename for '{disp.original_filename}'"
)
reason = self.check_include_omit_etc(disp.source_filename, frame)
diff --git a/coverage/plugin_support.py b/coverage/plugin_support.py
index dfe5a4ec..0b892391 100644
--- a/coverage/plugin_support.py
+++ b/coverage/plugin_support.py
@@ -7,7 +7,7 @@ import os
import os.path
import sys
-from coverage.exceptions import CoverageException
+from coverage.exceptions import PluginError
from coverage.misc import isolate_module
from coverage.plugin import CoveragePlugin, FileTracer, FileReporter
@@ -44,7 +44,7 @@ class Plugins:
coverage_init = getattr(mod, "coverage_init", None)
if not coverage_init:
- raise CoverageException(
+ raise PluginError(
f"Plugin module {module!r} didn't define a coverage_init function"
)
diff --git a/coverage/report.py b/coverage/report.py
index c1aaa45b..6382eb51 100644
--- a/coverage/report.py
+++ b/coverage/report.py
@@ -5,7 +5,7 @@
import sys
-from coverage.exceptions import CoverageException, NotPython
+from coverage.exceptions import CoverageException, NoDataError, NotPython
from coverage.files import prep_patterns, FnmatchMatcher
from coverage.misc import ensure_dir_for_file, file_be_gone
@@ -65,7 +65,7 @@ def get_analysis_to_report(coverage, morfs):
file_reporters = [fr for fr in file_reporters if not matcher.match(fr.filename)]
if not file_reporters:
- raise CoverageException("No data to report.")
+ raise NoDataError("No data to report.")
for fr in sorted(file_reporters):
try:
diff --git a/coverage/results.py b/coverage/results.py
index 64e5130c..7bb4781c 100644
--- a/coverage/results.py
+++ b/coverage/results.py
@@ -6,7 +6,7 @@
import collections
from coverage.debug import SimpleReprMixin
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.misc import contract, nice_pair
@@ -337,7 +337,7 @@ def should_fail_under(total, fail_under, precision):
# We can never achieve higher than 100% coverage, or less than zero.
if not (0 <= fail_under <= 100.0):
msg = f"fail_under={fail_under} is invalid. Must be between 0 and 100."
- raise CoverageException(msg)
+ raise ConfigError(msg)
# Special case for fail_under=100, it must really be 100.
if fail_under == 100.0 and total != 100.0:
diff --git a/coverage/sqldata.py b/coverage/sqldata.py
index 7fca3248..e5fee8ce 100644
--- a/coverage/sqldata.py
+++ b/coverage/sqldata.py
@@ -19,7 +19,7 @@ import threading
import zlib
from coverage.debug import NoDebugging, SimpleReprMixin, clipped_repr
-from coverage.exceptions import CoverageException
+from coverage.exceptions import CoverageException, DataError
from coverage.files import PathAliases
from coverage.misc import contract, file_be_gone, filename_suffix, isolate_module
from coverage.numbits import numbits_to_nums, numbits_union, nums_to_numbits
@@ -287,14 +287,14 @@ class CoverageData(SimpleReprMixin):
try:
schema_version, = db.execute_one("select version from coverage_schema")
except Exception as exc:
- raise CoverageException(
+ raise DataError(
"Data file {!r} doesn't seem to be a coverage data file: {}".format(
self._filename, exc
)
) from exc
else:
if schema_version != SCHEMA_VERSION:
- raise CoverageException(
+ raise DataError(
"Couldn't use data file {!r}: wrong schema: {} instead of {}".format(
self._filename, schema_version, SCHEMA_VERSION
)
@@ -368,9 +368,9 @@ class CoverageData(SimpleReprMixin):
if self._debug.should("dataio"):
self._debug.write(f"Loading data into data file {self._filename!r}")
if data[:1] != b"z":
- raise CoverageException(
+ raise DataError(
f"Unrecognized serialization: {data[:40]!r} (head of {len(data)} bytes)"
- )
+ )
script = zlib.decompress(data[1:]).decode("utf-8")
self._dbs[threading.get_ident()] = db = SqliteDb(self._filename, self._debug)
with db:
@@ -511,9 +511,9 @@ class CoverageData(SimpleReprMixin):
assert lines or arcs
assert not (lines and arcs)
if lines and self._has_arcs:
- raise CoverageException("Can't add line measurements to existing branch data")
+ raise DataError("Can't add line measurements to existing branch data")
if arcs and self._has_lines:
- raise CoverageException("Can't add branch measurements to existing line data")
+ raise DataError("Can't add branch measurements to existing line data")
if not self._has_arcs and not self._has_lines:
self._has_lines = lines
self._has_arcs = arcs
@@ -539,14 +539,14 @@ class CoverageData(SimpleReprMixin):
for filename, plugin_name in file_tracers.items():
file_id = self._file_id(filename)
if file_id is None:
- raise CoverageException(
+ raise DataError(
f"Can't add file tracer data for unmeasured file '{filename}'"
)
existing_plugin = self.file_tracer(filename)
if existing_plugin:
if existing_plugin != plugin_name:
- raise CoverageException(
+ raise DataError(
"Conflicting file tracer name for '{}': {!r} vs {!r}".format(
filename, existing_plugin, plugin_name,
)
@@ -576,7 +576,7 @@ class CoverageData(SimpleReprMixin):
self._start_using()
with self._connect(): # Use this to get one transaction.
if not self._has_arcs and not self._has_lines:
- raise CoverageException("Can't touch files in an empty CoverageData")
+ raise DataError("Can't touch files in an empty CoverageData")
for filename in filenames:
self._file_id(filename, add=True)
@@ -595,9 +595,9 @@ class CoverageData(SimpleReprMixin):
getattr(other_data, "_filename", "???"),
))
if self._has_lines and other_data._has_arcs:
- raise CoverageException("Can't combine arc data with line data")
+ raise DataError("Can't combine arc data with line data")
if self._has_arcs and other_data._has_lines:
- raise CoverageException("Can't combine line data with arc data")
+ raise DataError("Can't combine line data with arc data")
aliases = aliases or PathAliases()
@@ -690,7 +690,7 @@ class CoverageData(SimpleReprMixin):
other_tracer = tracers.get(path, "")
# If there is no tracer, there is always the None tracer.
if this_tracer is not None and this_tracer != other_tracer:
- raise CoverageException(
+ raise DataError(
"Conflicting file tracer name for '{}': {!r} vs {!r}".format(
path, this_tracer, other_tracer
)
@@ -1033,7 +1033,7 @@ class SqliteDb(SimpleReprMixin):
try:
self.con = sqlite3.connect(self.filename, check_same_thread=False)
except sqlite3.Error as exc:
- raise CoverageException(f"Couldn't use data file {self.filename!r}: {exc}") from exc
+ raise DataError(f"Couldn't use data file {self.filename!r}: {exc}") from exc
self.con.create_function("REGEXP", 2, _regexp)
@@ -1066,7 +1066,7 @@ class SqliteDb(SimpleReprMixin):
except Exception as exc:
if self.debug:
self.debug.write(f"EXCEPTION from __exit__: {exc}")
- raise CoverageException(f"Couldn't end data file {self.filename!r}: {exc}") from exc
+ raise DataError(f"Couldn't end data file {self.filename!r}: {exc}") from exc
def execute(self, sql, parameters=()):
"""Same as :meth:`python:sqlite3.Connection.execute`."""
@@ -1097,7 +1097,7 @@ class SqliteDb(SimpleReprMixin):
pass
if self.debug:
self.debug.write(f"EXCEPTION from execute: {msg}")
- raise CoverageException(f"Couldn't use data file {self.filename!r}: {msg}") from exc
+ raise DataError(f"Couldn't use data file {self.filename!r}: {msg}") from exc
def execute_one(self, sql, parameters=()):
"""Execute a statement and return the one row that results.
diff --git a/coverage/summary.py b/coverage/summary.py
index 0b54a05b..467c601d 100644
--- a/coverage/summary.py
+++ b/coverage/summary.py
@@ -5,7 +5,7 @@
import sys
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError, NoDataError
from coverage.misc import human_sorted_items
from coverage.report import get_analysis_to_report
from coverage.results import Numbers
@@ -103,7 +103,7 @@ class SummaryReporter:
else:
position = column_order.get(sort_option)
if position is None:
- raise CoverageException(f"Invalid sorting option: {self.config.sort!r}")
+ raise ConfigError(f"Invalid sorting option: {self.config.sort!r}")
lines.sort(key=lambda l: (l[1][position], l[0]), reverse=reverse)
for line in lines:
@@ -122,7 +122,7 @@ class SummaryReporter:
# Write other final lines.
if not self.total.n_files and not self.skipped_count:
- raise CoverageException("No data to report.")
+ raise NoDataError("No data to report.")
if self.config.skip_covered and self.skipped_count:
self.writeout(
diff --git a/coverage/tomlconfig.py b/coverage/tomlconfig.py
index 4a1e322c..a06da65f 100644
--- a/coverage/tomlconfig.py
+++ b/coverage/tomlconfig.py
@@ -7,7 +7,7 @@ import configparser
import os
import re
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.misc import import_third_party, substitute_variables
# TOML support is an install-time extra option. (Import typing is here because
@@ -57,7 +57,7 @@ class TomlConfigParser:
if self.our_file or has_toml:
# Looks like they meant to read TOML, but we can't read it.
msg = "Can't read {!r} without TOML support. Install with [toml] extra"
- raise CoverageException(msg.format(filename))
+ raise ConfigError(msg.format(filename))
return []
def _get_section(self, section):
@@ -148,7 +148,7 @@ class TomlConfigParser:
try:
re.compile(value)
except re.error as e:
- raise CoverageException(f"Invalid [{name}].{option} value {value!r}: {e}") from e
+ raise ConfigError(f"Invalid [{name}].{option} value {value!r}: {e}") from e
return values
def getint(self, section, option):
diff --git a/tests/test_api.py b/tests/test_api.py
index 9d56b23e..6b065709 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -18,7 +18,7 @@ import pytest
import coverage
from coverage import env
from coverage.data import line_counts
-from coverage.exceptions import CoverageException
+from coverage.exceptions import CoverageException, DataError, NoDataError, NoSource
from coverage.files import abs_file, relative_filename
from coverage.misc import import_local_file
@@ -293,7 +293,7 @@ class ApiTest(CoverageTest):
# empty summary reports raise exception, just like the xml report
cov = coverage.Coverage()
cov.erase()
- with pytest.raises(CoverageException, match="No data to report."):
+ with pytest.raises(NoDataError, match="No data to report."):
cov.report()
def test_completely_zero_reporting(self):
@@ -322,7 +322,7 @@ class ApiTest(CoverageTest):
)
self.make_file(".coverage", cov4_data)
cov = coverage.Coverage()
- with pytest.raises(CoverageException, match="Looks like a coverage 4.x data file"):
+ with pytest.raises(DataError, match="Looks like a coverage 4.x data file"):
cov.load()
cov.erase()
@@ -445,7 +445,7 @@ class ApiTest(CoverageTest):
self.assert_exists(".coverage")
cov2 = coverage.Coverage()
- with pytest.raises(CoverageException, match=r"No data to combine"):
+ with pytest.raises(NoDataError, match=r"No data to combine"):
cov2.combine(strict=True, keep=False)
cov3 = coverage.Coverage()
@@ -1126,7 +1126,7 @@ class RelativePathTest(CoverageTest):
with change_dir("new"):
cov = coverage.Coverage()
cov.load()
- with pytest.raises(CoverageException, match=expected):
+ with pytest.raises(NoSource, match=expected):
cov.report()
def test_moving_stuff_with_relative(self):
diff --git a/tests/test_config.py b/tests/test_config.py
index 411a456f..9a64d5b1 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -10,7 +10,7 @@ import pytest
import coverage
from coverage.config import HandyConfigParser
-from coverage.exceptions import CoverageException, CoverageWarning
+from coverage.exceptions import ConfigError, CoverageWarning
from tests.coveragetest import CoverageTest, UsingModulesMixin
from tests.helpers import without_module
@@ -159,7 +159,7 @@ class ConfigTest(CoverageTest):
def test_missing_rcfile_from_environment(self):
self.set_environ("COVERAGE_RCFILE", "nowhere.ini")
msg = "Couldn't read 'nowhere.ini' as a config file"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
coverage.Coverage()
@pytest.mark.parametrize("bad_config, msg", [
@@ -178,9 +178,9 @@ class ConfigTest(CoverageTest):
r"multiple repeat"),
])
def test_parse_errors(self, bad_config, msg):
- # Im-parsable values raise CoverageException, with details.
+ # Im-parsable values raise ConfigError, with details.
self.make_file(".coveragerc", bad_config)
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
coverage.Coverage()
@pytest.mark.parametrize("bad_config, msg", [
@@ -201,9 +201,9 @@ class ConfigTest(CoverageTest):
('[tool.coverage.report]\nfail_under="s"', "not a float"),
])
def test_toml_parse_errors(self, bad_config, msg):
- # Im-parsable values raise CoverageException, with details.
+ # Im-parsable values raise ConfigError, with details.
self.make_file("pyproject.toml", bad_config)
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
coverage.Coverage()
def test_environment_vars_in_config(self):
@@ -360,13 +360,13 @@ class ConfigTest(CoverageTest):
def test_tweak_error_checking(self):
# Trying to set an unknown config value raises an error.
cov = coverage.Coverage()
- with pytest.raises(CoverageException, match="No such option: 'run:xyzzy'"):
+ with pytest.raises(ConfigError, match="No such option: 'run:xyzzy'"):
cov.set_option("run:xyzzy", 12)
- with pytest.raises(CoverageException, match="No such option: 'xyzzy:foo'"):
+ with pytest.raises(ConfigError, match="No such option: 'xyzzy:foo'"):
cov.set_option("xyzzy:foo", 12)
- with pytest.raises(CoverageException, match="No such option: 'run:xyzzy'"):
+ with pytest.raises(ConfigError, match="No such option: 'run:xyzzy'"):
_ = cov.get_option("run:xyzzy")
- with pytest.raises(CoverageException, match="No such option: 'xyzzy:foo'"):
+ with pytest.raises(ConfigError, match="No such option: 'xyzzy:foo'"):
_ = cov.get_option("xyzzy:foo")
def test_tweak_plugin_options(self):
@@ -375,12 +375,12 @@ class ConfigTest(CoverageTest):
cov.set_option("run:plugins", ["fooey.plugin", "xyzzy.coverage.plugin"])
cov.set_option("fooey.plugin:xyzzy", 17)
cov.set_option("xyzzy.coverage.plugin:plugh", ["a", "b"])
- with pytest.raises(CoverageException, match="No such option: 'no_such.plugin:foo'"):
+ with pytest.raises(ConfigError, match="No such option: 'no_such.plugin:foo'"):
cov.set_option("no_such.plugin:foo", 23)
assert cov.get_option("fooey.plugin:xyzzy") == 17
assert cov.get_option("xyzzy.coverage.plugin:plugh") == ["a", "b"]
- with pytest.raises(CoverageException, match="No such option: 'no_such.plugin:foo'"):
+ with pytest.raises(ConfigError, match="No such option: 'no_such.plugin:foo'"):
_ = cov.get_option("no_such.plugin:foo")
def test_unknown_option(self):
@@ -425,9 +425,9 @@ class ConfigTest(CoverageTest):
branch = True
""")
config = HandyConfigParser("config.ini")
- with pytest.raises(Exception, match="No section: 'xyzzy'"):
+ with pytest.raises(ConfigError, match="No section: 'xyzzy'"):
config.options("xyzzy")
- with pytest.raises(Exception, match="No option 'foo' in section: 'xyzzy'"):
+ with pytest.raises(ConfigError, match="No option 'foo' in section: 'xyzzy'"):
config.get("xyzzy", "foo")
@@ -683,7 +683,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
# If a config file is explicitly specified, then it is an error for it
# to not be readable.
msg = f"Couldn't read {bad_file!r} as a config file"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
coverage.Coverage(config_file=bad_file)
def test_nocoveragerc_file_when_specified(self):
@@ -707,7 +707,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
# Can't read a toml file that doesn't exist.
with without_module(coverage.tomlconfig, 'tomli'):
msg = "Couldn't read 'cov.toml' as a config file"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
coverage.Coverage(config_file="cov.toml")
def test_no_toml_installed_explicit_toml(self):
@@ -715,7 +715,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
self.make_file("cov.toml", "# A toml file!")
with without_module(coverage.tomlconfig, 'tomli'):
msg = "Can't read 'cov.toml' without TOML support"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
coverage.Coverage(config_file="cov.toml")
def test_no_toml_installed_pyproject_toml(self):
@@ -727,7 +727,7 @@ class ConfigFileTest(UsingModulesMixin, CoverageTest):
""")
with without_module(coverage.tomlconfig, 'tomli'):
msg = "Can't read 'pyproject.toml' without TOML support"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
coverage.Coverage()
def test_no_toml_installed_pyproject_no_coverage(self):
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
index d69a0418..9fc7a001 100644
--- a/tests/test_coverage.py
+++ b/tests/test_coverage.py
@@ -7,7 +7,7 @@ import pytest
import coverage
from coverage import env
-from coverage.exceptions import CoverageException
+from coverage.exceptions import NoDataError
from tests.coveragetest import CoverageTest
@@ -1850,19 +1850,19 @@ class ReportingTest(CoverageTest):
def test_no_data_to_report_on_annotate(self):
# Reporting with no data produces a nice message and no output
# directory.
- with pytest.raises(CoverageException, match="No data to report."):
+ with pytest.raises(NoDataError, match="No data to report."):
self.command_line("annotate -d ann")
self.assert_doesnt_exist("ann")
def test_no_data_to_report_on_html(self):
# Reporting with no data produces a nice message and no output
# directory.
- with pytest.raises(CoverageException, match="No data to report."):
+ with pytest.raises(NoDataError, match="No data to report."):
self.command_line("html -d htmlcov")
self.assert_doesnt_exist("htmlcov")
def test_no_data_to_report_on_xml(self):
# Reporting with no data produces a nice message.
- with pytest.raises(CoverageException, match="No data to report."):
+ with pytest.raises(NoDataError, match="No data to report."):
self.command_line("xml")
self.assert_doesnt_exist("coverage.xml")
diff --git a/tests/test_data.py b/tests/test_data.py
index 2e20618b..bc06eb23 100644
--- a/tests/test_data.py
+++ b/tests/test_data.py
@@ -16,7 +16,7 @@ import pytest
from coverage.data import CoverageData, combine_parallel_data
from coverage.data import add_data_to_hash, line_counts
from coverage.debug import DebugControlString
-from coverage.exceptions import CoverageException
+from coverage.exceptions import DataError, NoDataError
from coverage.files import PathAliases, canonical_filename
from tests.coveragetest import CoverageTest
@@ -164,14 +164,14 @@ class CoverageDataTest(CoverageTest):
covdata = DebugCoverageData()
covdata.add_lines(LINES_1)
msg = "Can't add branch measurements to existing line data"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata.add_arcs(ARCS_3)
def test_cant_add_lines_with_arcs(self):
covdata = DebugCoverageData()
covdata.add_arcs(ARCS_3)
msg = "Can't add line measurements to existing branch data"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata.add_lines(LINES_1)
def test_touch_file_with_lines(self):
@@ -324,11 +324,11 @@ class CoverageDataTest(CoverageTest):
def test_cant_file_tracer_unmeasured_files(self):
covdata = DebugCoverageData()
msg = "Can't add file tracer data for unmeasured file 'p1.foo'"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata.add_file_tracers({"p1.foo": "p1.plugin"})
covdata.add_lines({"p2.html": [10, 11, 12]})
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata.add_file_tracers({"p1.foo": "p1.plugin"})
def test_cant_change_file_tracer_name(self):
@@ -337,7 +337,7 @@ class CoverageDataTest(CoverageTest):
covdata.add_file_tracers({"p1.foo": "p1.plugin"})
msg = "Conflicting file tracer name for 'p1.foo': 'p1.plugin' vs 'p1.plugin.foo'"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata.add_file_tracers({"p1.foo": "p1.plugin.foo"})
def test_update_lines(self):
@@ -375,10 +375,10 @@ class CoverageDataTest(CoverageTest):
covdata2 = DebugCoverageData(suffix='2')
covdata2.add_arcs(ARCS_3)
- with pytest.raises(CoverageException, match="Can't combine arc data with line data"):
+ with pytest.raises(DataError, match="Can't combine arc data with line data"):
covdata1.update(covdata2)
- with pytest.raises(CoverageException, match="Can't combine line data with arc data"):
+ with pytest.raises(DataError, match="Can't combine line data with arc data"):
covdata2.update(covdata1)
def test_update_file_tracers(self):
@@ -424,11 +424,11 @@ class CoverageDataTest(CoverageTest):
covdata2.add_file_tracers({"p1.html": "html.other_plugin"})
msg = "Conflicting file tracer name for 'p1.html': 'html.plugin' vs 'html.other_plugin'"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata1.update(covdata2)
msg = "Conflicting file tracer name for 'p1.html': 'html.other_plugin' vs 'html.plugin'"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata2.update(covdata1)
def test_update_file_tracer_vs_no_file_tracer(self):
@@ -440,11 +440,11 @@ class CoverageDataTest(CoverageTest):
covdata2.add_lines({"p1.html": [1, 2, 3]})
msg = "Conflicting file tracer name for 'p1.html': 'html.plugin' vs ''"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata1.update(covdata2)
msg = "Conflicting file tracer name for 'p1.html': '' vs 'html.plugin'"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata2.update(covdata1)
def test_update_lines_empty(self):
@@ -529,7 +529,7 @@ class CoverageDataTest(CoverageTest):
def test_cant_touch_in_empty_data(self):
covdata = DebugCoverageData()
msg = "Can't touch files in an empty CoverageData"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata.touch_file("abc.py")
def test_read_and_write_are_opposites(self):
@@ -587,13 +587,13 @@ class CoverageDataInTempDirTest(CoverageTest):
msg = r"Couldn't .* '.*[/\\]{}': \S+"
self.make_file("xyzzy.dat", "xyzzy")
- with pytest.raises(CoverageException, match=msg.format("xyzzy.dat")):
+ with pytest.raises(DataError, match=msg.format("xyzzy.dat")):
covdata = DebugCoverageData("xyzzy.dat")
covdata.read()
assert not covdata
self.make_file("empty.dat", "")
- with pytest.raises(CoverageException, match=msg.format("empty.dat")):
+ with pytest.raises(DataError, match=msg.format("empty.dat")):
covdata = DebugCoverageData("empty.dat")
covdata.read()
assert not covdata
@@ -601,14 +601,14 @@ class CoverageDataInTempDirTest(CoverageTest):
def test_hard_read_error(self):
self.make_file("noperms.dat", "go away")
os.chmod("noperms.dat", 0)
- with pytest.raises(CoverageException, match=r"Couldn't .* '.*[/\\]noperms.dat': "):
+ with pytest.raises(DataError, match=r"Couldn't .* '.*[/\\]noperms.dat': "):
covdata = DebugCoverageData("noperms.dat")
covdata.read()
@pytest.mark.parametrize("klass", [CoverageData, DebugCoverageData])
def test_error_when_closing(self, klass):
msg = r"Couldn't .* '.*[/\\]flaked.dat': \S+"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata = klass("flaked.dat")
covdata.add_lines(LINES_1)
# I don't know how to make a real error, so let's fake one.
@@ -621,7 +621,7 @@ class CoverageDataInTempDirTest(CoverageTest):
con.execute("create table coverage_schema (version integer)")
con.execute("insert into coverage_schema (version) values (99)")
msg = r"Couldn't .* '.*[/\\]wrong_schema.db': wrong schema: 99 instead of \d+"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata = DebugCoverageData("wrong_schema.db")
covdata.read()
assert not covdata
@@ -629,7 +629,7 @@ class CoverageDataInTempDirTest(CoverageTest):
with sqlite3.connect("no_schema.db") as con:
con.execute("create table foobar (baz text)")
msg = r"Couldn't .* '.*[/\\]no_schema.db': \S+"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata = DebugCoverageData("no_schema.db")
covdata.read()
assert not covdata
@@ -854,7 +854,7 @@ class CoverageDataFilesTest(CoverageTest):
def test_combining_from_nonexistent_directories(self):
covdata = DebugCoverageData()
msg = "Couldn't combine from non-existent path 'xyzzy'"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(NoDataError, match=msg):
combine_parallel_data(covdata, data_paths=['xyzzy'])
def test_interleaved_erasing_bug716(self):
@@ -894,5 +894,5 @@ class DumpsLoadsTest(CoverageTest):
re.escape(repr(bad_data[:40])),
len(bad_data),
)
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(DataError, match=msg):
covdata.loads(bad_data)
diff --git a/tests/test_files.py b/tests/test_files.py
index de0dbbd5..0780fdb3 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -10,7 +10,7 @@ import pytest
from coverage import env
from coverage import files
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.files import (
TreeMatcher, FnmatchMatcher, ModuleMatcher, PathAliases,
find_python_files, abs_file, actual_path, flat_rootname, fnmatches_to_regex,
@@ -291,7 +291,7 @@ class PathAliasesTest(CoverageTest):
def test_cant_have_wildcard_at_end(self, badpat):
aliases = PathAliases()
msg = "Pattern must not end with wildcards."
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
aliases.add(badpat, "fooey")
def test_no_accidental_munging(self):
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index 5d3b5322..2140f00c 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -14,7 +14,7 @@ import coverage
from coverage import env
from coverage.control import Plugins
from coverage.data import line_counts
-from coverage.exceptions import CoverageException, CoverageWarning
+from coverage.exceptions import CoverageWarning, NoSource, PluginError
from coverage.misc import import_local_file
import coverage.plugin
@@ -133,7 +133,7 @@ class LoadPluginsTest(CoverageTest):
Nothing = 0
""")
msg_pat = "Plugin module 'no_plugin' didn't define a coverage_init function"
- with pytest.raises(CoverageException, match=msg_pat):
+ with pytest.raises(PluginError, match=msg_pat):
list(Plugins.load_plugins(["no_plugin"], None))
@@ -576,7 +576,7 @@ class GoodFileTracerTest(FileTracerTest):
# But completely new filenames are not in the results.
assert len(cov.get_data().measured_files()) == 3
- with pytest.raises(CoverageException):
+ with pytest.raises(NoSource):
cov.analysis("fictional.py")
diff --git a/tests/test_results.py b/tests/test_results.py
index 83968956..8b2a737b 100644
--- a/tests/test_results.py
+++ b/tests/test_results.py
@@ -5,7 +5,7 @@
import pytest
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.results import format_lines, Numbers, should_fail_under
from tests.coveragetest import CoverageTest
@@ -114,7 +114,7 @@ def test_should_fail_under(total, fail_under, precision, result):
def test_should_fail_under_invalid_value():
- with pytest.raises(CoverageException, match=r"fail_under=101"):
+ with pytest.raises(ConfigError, match=r"fail_under=101"):
should_fail_under(100.0, 101, 0)
diff --git a/tests/test_summary.py b/tests/test_summary.py
index 6fbb034d..769c6ec7 100644
--- a/tests/test_summary.py
+++ b/tests/test_summary.py
@@ -16,7 +16,7 @@ import coverage
from coverage import env
from coverage.control import Coverage
from coverage.data import CoverageData
-from coverage.exceptions import CoverageException
+from coverage.exceptions import ConfigError
from coverage.summary import SummaryReporter
from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin
@@ -921,5 +921,5 @@ class SummaryReporterConfigurationTest(CoverageTest):
def test_sort_report_by_invalid_option(self):
# Sort the text report by a nonsense column.
msg = "Invalid sorting option: 'Xyzzy'"
- with pytest.raises(CoverageException, match=msg):
+ with pytest.raises(ConfigError, match=msg):
self.get_summary_text(('report:sort', 'Xyzzy'))