summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgarar <none@none>2015-08-14 12:47:52 +0000
committergarar <none@none>2015-08-14 12:47:52 +0000
commit3e91ce7723dd163cca6f0caa1721de0a6bd5a3e4 (patch)
tree1cfea96190973f2b4c4b4d06d9703d2683d8de52
parentc62707914e5a90a0a843b7ed72fae0153ce97cd6 (diff)
parent9616b5e2032028bfa38115c10ea09ca95d3311e1 (diff)
downloadpython-coveragepy-git-3e91ce7723dd163cca6f0caa1721de0a6bd5a3e4.tar.gz
Merge default
-rw-r--r--coverage/summary.py5
-rw-r--r--doc/howitworks.rst98
-rw-r--r--doc/index.rst1
-rw-r--r--tests/test_summary.py14
4 files changed, 106 insertions, 12 deletions
diff --git a/coverage/summary.py b/coverage/summary.py
index 9b8b2338..99e73974 100644
--- a/coverage/summary.py
+++ b/coverage/summary.py
@@ -65,10 +65,7 @@ class SummaryReporter(Reporter):
if self.config.skip_covered:
# Don't report on 100% files.
no_missing_lines = (nums.n_missing == 0)
- if self.branches:
- no_missing_branches = (nums.n_partial_branches == 0)
- else:
- no_missing_branches = True
+ no_missing_branches = (nums.n_partial_branches == 0)
if no_missing_lines and no_missing_branches:
skipped_count += 1
continue
diff --git a/doc/howitworks.rst b/doc/howitworks.rst
new file mode 100644
index 00000000..08b19cba
--- /dev/null
+++ b/doc/howitworks.rst
@@ -0,0 +1,98 @@
+.. Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+.. For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
+
+.. _howitworks:
+
+=====================
+How Coverage.py works
+=====================
+
+.. :history: 20150812T071000, new page.
+
+For advanced use of coverage.py, or just because you are curious, it helps to
+understand what's happening behind the scenes. Coverage.py works in three
+phases:
+
+* **Execution**: your code is run, and monitored to see what lines were executed.
+
+* **Analysis**: your code is examined to determine what lines could have run.
+
+* **Reporting**: the results of execution and analysis are combined to produce
+ a coverage number and an indication of missing execution.
+
+The execution phase is handled by the ``coverage run`` command. The analysis
+and reporting phases are handled by the reporting commands like ``coverage
+report`` or ``coverage html``.
+
+Let's look at each phase in more detail.
+
+
+Execution
+---------
+
+At the heart of the execution phase is a Python trace function. This is a
+function that Python will invoke for each line executed in a program.
+Coverage.py implements a trace function that records each file and line number
+as it is executed.
+
+Executing a function for every line in your program can make execution very
+slow. Coverage.py's trace function is implemented in C to reduce that
+slowdown, and also takes care to not trace code that you aren't interested in.
+
+When measuring branch coverage, the same trace function is used, but instead of
+recording line numbers, coverage.py records pairs of line numbers. Each
+invocation of the trace function remembers the line number, then the next
+invocation records the pair `(prev, this)` to indicate that execution
+transitioned from the previous line to this line. Internally, these are called
+arcs.
+
+For more details of trace functions, see the Python docs for `sys.settrace`_,
+or if you are really brave, `How C trace functions really work`_.
+
+At the end of execution, coverage.py writes the data it collected to a data
+file, usually named ``.coverage``. This is a JSON-based file containing all of
+the recorded file names and line numbers executed.
+
+.. _sys.settrace: https://docs.python.org/3/library/sys.html#sys.settrace
+.. _How C trace functions really work: http://nedbatchelder.com/text/trace-function.html
+
+
+Analysis
+--------
+
+After your program has been executed and the line numbers recorded, coverage.py
+needs to determine what lines could have been executed. Luckily, compiled
+Python files (.pyc files) have a table of line numbers in them. Coverage.py
+reads this table to get the set of executable lines.
+
+The table isn't used directly, because it records line numbers for docstrings,
+for example, and we don't want to consider them executable. A few tweaks are
+made for considerations like this, and we have a set of lines that could have
+been executed.
+
+The data file is read to get the set of lines that were executed. The
+difference between those two sets are the lines that were not executed.
+
+The same principle applies for branch measurement, though the process for
+determining possible branches is more involved. Coverage.py reads the bytecode
+of the compiled Python file, and decides on a set of possible branches.
+Unfortunately, this process is inexact, and there are some `well-known cases`__
+that aren't correct.
+
+.. __: https://bitbucket.org/ned/coveragepy/issues?status=new&status=open&component=branch
+
+
+Reporting
+---------
+
+Once we have the set of executed lines and missing lines, reporting is just a
+matter of formatting that information in a useful way. Each reporting method
+(text, html, annotated source, xml) has a different output format, but the
+process is the same: write out the information in the particular format,
+possibly including the source code itself.
+
+
+Plugins
+-------
+
+Plugins interact with these phases.
diff --git a/doc/index.rst b/doc/index.rst
index fef9af7f..f5e134cd 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -172,6 +172,7 @@ More information
branch
subprocess
api
+ howitworks
plugins
contributing
trouble
diff --git a/tests/test_summary.py b/tests/test_summary.py
index 15e948ab..b765f313 100644
--- a/tests/test_summary.py
+++ b/tests/test_summary.py
@@ -142,8 +142,7 @@ class SummaryTest(CoverageTest):
self.assertEqual(self.line_count(report), 3)
self.assertIn("mybranch.py ", report)
- self.assertEqual(self.last_line_squeezed(report),
- "mybranch.py 5 0 2 1 86%")
+ self.assertEqual(self.last_line_squeezed(report), "mybranch.py 5 0 2 1 86%")
def test_report_show_missing(self):
self.make_file("mymissing.py", """\
@@ -367,7 +366,7 @@ class SummaryTest(CoverageTest):
# pylint: disable=line-too-long
# Name Stmts Miss Cover
# ----------------------------
- # mycode NotPython: Couldn't parse '/tmp/test_cover/63354509363/mycode.py' as Python source: 'invalid syntax' at line 1
+ # mycode NotPython: Couldn't parse '...' as Python source: 'invalid syntax' at line 1
# No data to report.
last = self.squeezed_lines(report)[-2]
@@ -375,11 +374,10 @@ class SummaryTest(CoverageTest):
last = re.sub(r"parse '.*mycode.py", "parse 'mycode.py", last)
# The actual error message varies version to version
last = re.sub(r": '.*' at", ": 'error' at", last)
- self.assertEqual(last,
- "mycode.py NotPython: "
- "Couldn't parse 'mycode.py' as Python source: "
- "'error' at line 1"
- )
+ self.assertEqual(
+ last,
+ "mycode.py NotPython: Couldn't parse 'mycode.py' as Python source: 'error' at line 1"
+ )
def test_dotpy_not_python_ignored(self):
# We run a .py file, and when reporting, we can't parse it as Python,