Skip to content

Commit e0ae05f

Browse files
committed
docs: warnings now link to docs about the warning
1 parent a9983a6 commit e0ae05f

File tree

5 files changed

+76
-9
lines changed

5 files changed

+76
-9
lines changed

coverage/control.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@ def _warn(self, msg: str, slug: str | None = None, once: bool = False) -> None:
470470
slug.)
471471
472472
"""
473+
import coverage as covmod
474+
473475
if not self._no_warn_slugs:
474476
self._no_warn_slugs = set(self.config.disable_warnings)
475477

@@ -479,7 +481,11 @@ def _warn(self, msg: str, slug: str | None = None, once: bool = False) -> None:
479481

480482
self._warnings.append(msg)
481483
if slug:
482-
msg = f"{msg} ({slug})"
484+
url = (
485+
f"https://coverage.readthedocs.io/en/{covmod.__version__}"
486+
+ f"/messages.html#warning-{slug}"
487+
)
488+
msg = f"{msg} ({slug}); see {url}"
483489
if self._debug.should("pid"):
484490
msg = f"[{os.getpid()}] {msg}"
485491
warnings.warn(msg, category=CoverageWarning, stacklevel=2)

doc/messages.rst

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,32 +24,42 @@ measurement or reporting.
2424
.. _warnings:
2525

2626
Warnings
27-
........
27+
--------
2828

2929
Warnings are issued for possible problems but don't stop the measurement or
3030
reporting. See below for the details of each warning, and how to suppress
3131
warnings you don't need to see.
3232

33+
.. _warning_couldnt_parse:
34+
3335
Couldn't parse Python file XXX (couldnt-parse)
3436
During reporting, a file was thought to be Python, but it couldn't be parsed
3537
as Python.
3638

39+
.. _warning_trace_changed:
40+
3741
Trace function changed, data is likely wrong: XXX (trace-changed)
3842
Coverage measurement depends on a Python setting called the trace function.
3943
Other Python code in your product might change that function, which will
4044
disrupt coverage.py's measurement. This warning indicates that has happened.
4145
The XXX in the message is the new trace function value, which might provide
4246
a clue to the cause.
4347

48+
.. _warning_module_not_python:
49+
4450
Module XXX has no Python source (module-not-python)
4551
You asked coverage.py to measure module XXX, but once it was imported, it
4652
turned out not to have a corresponding .py file. Without a .py file,
4753
coverage.py can't report on missing lines.
4854

55+
.. _warning_module_not_imported:
56+
4957
Module XXX was never imported (module-not-imported)
5058
You asked coverage.py to measure module XXX, but it was never imported by
5159
your program.
5260

61+
.. _warning_no_data_collected:
62+
5363
No data was collected (no-data-collected)
5464
Coverage.py ran your program, but didn't measure any lines as executed.
5565
This could be because you asked to measure only modules that never ran,
@@ -58,33 +68,45 @@ No data was collected (no-data-collected)
5868
To debug this problem, try using ``run --debug=trace`` to see the tracing
5969
decision made for each file.
6070

71+
.. _warning_module_not_measured:
72+
6173
Module XXX was previously imported, but not measured (module-not-measured)
6274
You asked coverage.py to measure module XXX, but it had already been imported
6375
when coverage started. This meant coverage.py couldn't monitor its
6476
execution.
6577

78+
.. _warning_already_imported:
79+
6680
Already imported a file that will be measured: XXX (already-imported)
6781
File XXX had already been imported when coverage.py started measurement. Your
6882
setting for ``--source`` or ``--include`` indicates that you wanted to
6983
measure that file. Lines will be missing from the coverage report since the
7084
execution during import hadn't been measured.
7185

86+
.. _warning_include_ignored:
87+
7288
\-\-include is ignored because \-\-source is set (include-ignored)
7389
Both ``--include`` and ``--source`` were specified while running code. Both
7490
are meant to focus measurement on a particular part of your source code, so
7591
``--include`` is ignored in favor of ``--source``.
7692

93+
.. _warning_dynamic_conflict:
94+
7795
Conflicting dynamic contexts (dynamic-conflict)
7896
The ``[run] dynamic_context`` option is set in the configuration file, but
7997
something (probably a test runner plugin) is also calling the
8098
:meth:`.Coverage.switch_context` function to change the context. Only one of
8199
these mechanisms should be in use at a time.
82100

101+
.. _warning_no_ctracer:
102+
83103
Couldn't import C tracer (no-ctracer)
84104
The core tracer implemented in C should have been used, but couldn't be
85105
imported. The reason is included in the warning message. The Python tracer
86106
will be used instead.
87107

108+
.. _warning_no_sysmon:
109+
88110
sys.monitoring isn't available in this version, using default core (no-sysmon)
89111
You requested to use the sys.monitoring measurement core, but are running on
90112
Python 3.11 or lower where it isn't available. A default core will be used
@@ -100,6 +122,10 @@ sys.monitoring doesn't yet support dynamic contexts, using default core (no-sysm
100122
This isn't supported by coverage.py yet. A default core will be used
101123
instead.
102124

125+
126+
Disabling warnings
127+
------------------
128+
103129
Individual warnings can be disabled with the :ref:`disable_warnings
104130
<config_run_disable_warnings>` configuration setting. It is a list of the
105131
short parenthetical nicknames in the warning messages. For example, to silence

tests/helpers.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from typing import Any, Callable, NoReturn, TypeVar, cast
2424
from collections.abc import Iterable, Iterator
2525

26+
import coverage
2627
from coverage import env
2728
from coverage.debug import DebugControl
2829
from coverage.exceptions import CoverageWarning
@@ -313,6 +314,13 @@ def assert_count_equal(
313314
assert collections.Counter(list(a)) == collections.Counter(list(b))
314315

315316

317+
def get_coverage_warnings(warns: Iterable[warnings.WarningMessage]) -> list[str]:
318+
"""Extract the text of CoverageWarnings."""
319+
warns = [w for w in warns if issubclass(w.category, CoverageWarning)]
320+
texts = [cast(Warning, w.message).args[0] for w in warns]
321+
return texts
322+
323+
316324
def assert_coverage_warnings(
317325
warns: Iterable[warnings.WarningMessage],
318326
*msgs: str | re.Pattern[str],
@@ -323,15 +331,15 @@ def assert_coverage_warnings(
323331
Each msg can be a string compared for equality, or a compiled regex used to
324332
search the text.
325333
"""
334+
actuals = get_coverage_warnings(warns)
326335
assert msgs # don't call this without some messages.
327-
warns = [w for w in warns if issubclass(w.category, CoverageWarning)]
328-
actuals = [cast(Warning, w.message).args[0] for w in warns]
329336
assert len(msgs) == len(actuals)
330-
for expected, actual in zip(msgs, actuals):
337+
for actual, expected in zip(actuals, msgs):
331338
if hasattr(expected, "search"):
332339
assert expected.search(actual), f"{actual!r} didn't match {expected!r}"
333340
else:
334-
assert expected == actual
341+
actual = actual.partition("; see ")[0]
342+
assert actual == expected
335343

336344

337345
@contextlib.contextmanager

tests/test_api.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,14 @@
3030

3131
from tests import testenv
3232
from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin
33-
from tests.helpers import assert_count_equal, assert_coverage_warnings
34-
from tests.helpers import change_dir, nice_file, os_sep
33+
from tests.helpers import (
34+
assert_count_equal,
35+
assert_coverage_warnings,
36+
change_dir,
37+
get_coverage_warnings,
38+
nice_file,
39+
os_sep,
40+
)
3541

3642
BAD_SQLITE_REGEX = r"file( is encrypted or)? is not a database"
3743

@@ -619,6 +625,26 @@ def test_warn_once(self) -> None:
619625

620626
assert_coverage_warnings(warns, "Warning, warning 1! (bot)")
621627
# No "Warning, warning 2!" in warns
628+
assert len(warns) == 1
629+
630+
def test_warnings_with_urls(self) -> None:
631+
with pytest.warns(Warning) as warns:
632+
cov = coverage.Coverage()
633+
cov.load()
634+
cov._warn("Warning Will Robinson", slug="will-rob")
635+
cov._warn("Warning, warning 2!", slug="second-one")
636+
warnings = get_coverage_warnings(warns)
637+
638+
def url(slug):
639+
return (
640+
f"https://coverage.readthedocs.io/en/{coverage.__version__}"
641+
+ f"/messages.html#warning-{slug}"
642+
)
643+
644+
assert warnings == [
645+
f"Warning Will Robinson (will-rob); see {url('will-rob')}",
646+
f"Warning, warning 2! (second-one); see {url('second-one')}",
647+
]
622648

623649
def test_source_and_include_dont_conflict(self) -> None:
624650
# A bad fix made this case fail: https://github.com/nedbat/coveragepy/issues/541

tests/test_process.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,13 +762,14 @@ def foo():
762762
# Remove the file location and source line from the warning.
763763
out = re.sub(r"(?m)^[\\/\w.:~_-]+:\d+: CoverageWarning: ", "f:d: CoverageWarning: ", out)
764764
out = re.sub(r"(?m)^\s+self.warn.*$\n", "", out)
765+
out = re.sub(r"; see https://.*$", "", out)
765766
expected = (
766767
"Run 1\n"
767768
+ "Run 2\n"
768769
+ "f:d: CoverageWarning: Module foo was previously imported, but not measured "
769770
+ "(module-not-measured)\n"
770771
)
771-
assert expected == out
772+
assert out == expected
772773

773774
def test_module_name(self) -> None:
774775
# https://github.com/nedbat/coveragepy/issues/478

0 commit comments

Comments
 (0)