summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2015-01-19 18:03:21 -0500
committerNed Batchelder <ned@nedbatchelder.com>2015-01-19 18:03:21 -0500
commite03bfa80f990b0f30eedf4da3aa7cedfcb2ead27 (patch)
tree3aead9fa06c73eed9479e4a15099416a262ba740
parentddf3884d15ac50d4cff3b15742bcb804588e4006 (diff)
downloadpython-coveragepy-git-e03bfa80f990b0f30eedf4da3aa7cedfcb2ead27.tar.gz
Fix bad regression: XML report now reports packages again. #235
-rw-r--r--CHANGES.txt5
-rw-r--r--coverage/xmlreport.py19
-rw-r--r--tests/farm/html/gold_x_xml/coverage.xml2
-rw-r--r--tests/farm/html/gold_y_xml_branch/coverage.xml2
-rw-r--r--tests/test_xml.py71
5 files changed, 90 insertions, 9 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 39650a1f..7bd3bd60 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -10,6 +10,10 @@ Latest
describe a/b/c.py as "a/b/c". Now it is shown as "a/b/c.py". This allows
for better support of non-Python files, and also fixed `issue 69`_.
+- The XML report now reports each directory as a package again. This was a bad
+ regression, I apologize. This was reported in `issue 235`_, which is now
+ fixed.
+
- When looking for the source for a frame, check if the file exists. On
Windows, .pyw files are no longer recorded as .py files. Along the way, this
fixed `issue 290`_.
@@ -21,6 +25,7 @@ Latest
to provide error messages earlier (`issue 349`_).
.. _issue 69: https://bitbucket.org/ned/coveragepy/issue/69/coverage-html-overwrite-files-that-doesnt
+.. _issue 235: https://bitbucket.org/ned/coveragepy/issue/235/package-name-is-missing-in-xml-report
.. _issue 290: https://bitbucket.org/ned/coveragepy/issue/290/running-programmatically-with-pyw-files
.. _issue 345: https://bitbucket.org/ned/coveragepy/issue/345/xml-reports-line-rate-0-for-empty-files
.. _issue 349: https://bitbucket.org/ned/coveragepy/issue/349/bad-regex-in-config-should-get-an-earlier
diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py
index 45a4652e..3510c5b6 100644
--- a/coverage/xmlreport.py
+++ b/coverage/xmlreport.py
@@ -1,11 +1,14 @@
"""XML reporting for coverage.py"""
-import os, sys, time
+import os
+import sys
+import time
import xml.dom.minidom
from coverage import __url__, __version__
from coverage.report import Reporter
+
def rate(hit, num):
"""Return the fraction of `hit`/`num`, as a string."""
if num == 0:
@@ -118,7 +121,10 @@ class XmlReporter(Reporter):
# Create the 'lines' and 'package' XML elements, which
# are populated later. Note that a package == a directory.
- package_name = cu.name.rpartition(".")[0]
+ filename = cu.file_locator.relative_filename(cu.filename)
+ filename = filename.replace("\\", "/")
+ dirname = os.path.dirname(filename) or "."
+ package_name = dirname.replace("/", ".")
className = cu.name
self.source_paths.add(cu.file_locator.relative_dir.rstrip('/'))
@@ -131,9 +137,8 @@ class XmlReporter(Reporter):
xlines = self.xml_out.createElement("lines")
xclass.appendChild(xlines)
- xclass.setAttribute("name", className)
- filename = cu.file_locator.relative_filename(cu.filename)
- xclass.setAttribute("filename", filename.replace("\\", "/"))
+ xclass.setAttribute("name", os.path.relpath(filename, dirname))
+ xclass.setAttribute("filename", filename)
xclass.setAttribute("complexity", "0")
branch_stats = analysis.branch_stats()
@@ -151,7 +156,8 @@ class XmlReporter(Reporter):
if line in branch_stats:
total, taken = branch_stats[line]
xline.setAttribute("branch", "true")
- xline.setAttribute("condition-coverage",
+ xline.setAttribute(
+ "condition-coverage",
"%d%% (%d/%d)" % (100*taken/total, taken, total)
)
xlines.appendChild(xline)
@@ -174,6 +180,7 @@ class XmlReporter(Reporter):
else:
branch_rate = "0"
xclass.setAttribute("branch-rate", branch_rate)
+
package[0][className] = xclass
package[1] += class_hits
package[2] += class_lines
diff --git a/tests/farm/html/gold_x_xml/coverage.xml b/tests/farm/html/gold_x_xml/coverage.xml
index f528e123..108cc528 100644
--- a/tests/farm/html/gold_x_xml/coverage.xml
+++ b/tests/farm/html/gold_x_xml/coverage.xml
@@ -7,7 +7,7 @@
<source></source>
</sources>
<packages>
- <package branch-rate="0" complexity="0" line-rate="0.6667" name="a">
+ <package branch-rate="0" complexity="0" line-rate="0.6667" name=".">
<classes>
<class branch-rate="0" complexity="0" filename="a.py" line-rate="0.6667" name="a.py">
<methods/>
diff --git a/tests/farm/html/gold_y_xml_branch/coverage.xml b/tests/farm/html/gold_y_xml_branch/coverage.xml
index eb0c5323..8ac186b5 100644
--- a/tests/farm/html/gold_y_xml_branch/coverage.xml
+++ b/tests/farm/html/gold_y_xml_branch/coverage.xml
@@ -7,7 +7,7 @@
<source></source>
</sources>
<packages>
- <package branch-rate="0.5" complexity="0" line-rate="0.8" name="y">
+ <package branch-rate="0.5" complexity="0" line-rate="0.8" name=".">
<classes>
<class branch-rate="0.5" complexity="0" filename="y.py" line-rate="0.8" name="y.py">
<methods/>
diff --git a/tests/test_xml.py b/tests/test_xml.py
index 2d805219..5309ebce 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -2,6 +2,7 @@
import os
import re
+
import coverage
from tests.coveragetest import CoverageTest
@@ -24,6 +25,31 @@ class XmlTestHelpers(CoverageTest):
self.start_import_stop(cov, "main")
return cov
+ def make_tree(self, width, depth, curdir="."):
+ """Make a tree of packages.
+
+ Makes `width` directories, named d0 .. d{width-1}. Each directory has
+ __init__.py, and `width` files, named f0.py .. f{width-1}.py. Each
+ directory also has `width` subdirectories, in the same fashion, until
+ a depth of `depth` is reached.
+
+ """
+ if depth == 0:
+ return
+
+ def here(p):
+ """A path for `p` in our currently interesting directory."""
+ return os.path.join(curdir, p)
+
+ for i in range(width):
+ next_dir = here("d{0}".format(i))
+ self.make_tree(width, depth-1, next_dir)
+ if curdir != ".":
+ self.make_file(here("__init__.py"), "")
+ for i in range(width):
+ filename = here("f{0}.py".format(i))
+ self.make_file(filename, "# {0}\n".format(filename))
+
class XmlReportTest(XmlTestHelpers, CoverageTest):
"""Tests of the XML reports from coverage.py."""
@@ -111,8 +137,51 @@ class XmlReportTest(XmlTestHelpers, CoverageTest):
self.assertIn('line-rate="1"', init_line)
+class XmlPackageStructureTest(XmlTestHelpers, CoverageTest):
+ """Tests about the package structure reported in the coverage.xml file."""
+
+ def test_packages(self):
+ self.make_tree(width=1, depth=3)
+ self.make_file("main.py", """\
+ from d0.d0 import f0
+ """)
+ cov = coverage.coverage(source=".")
+ self.start_import_stop(cov, "main")
+ cov.xml_report(outfile="-")
+ xml = self.stdout()
+ packages_and_classes = "".join(re_lines(xml, r"<package |<class "))
+ scrubs = r' branch-rate="0"| complexity="0"| line-rate="[\d.]+"'
+ self.assertMultiLineEqual(
+ clean(packages_and_classes, scrubs),
+ clean("""\
+ <package name=".">
+ <class filename="main.py" name="main.py">
+ <package name="d0">
+ <class filename="d0/__init__.py" name="__init__.py">
+ <class filename="d0/f0.py" name="f0.py">
+ <package name="d0.d0">
+ <class filename="d0/d0/__init__.py" name="__init__.py">
+ <class filename="d0/d0/f0.py" name="f0.py">
+ """)
+ )
+
+
+def re_lines(text, pat):
+ """Return a list of lines that match `pat` in the string `text`."""
+ lines = [l for l in text.splitlines(True) if re.search(pat, l)]
+ return lines
+
+
def re_line(text, pat):
"""Return the one line in `text` that matches regex `pat`."""
- lines = [l for l in text.splitlines() if re.search(pat, l)]
+ lines = re_lines(text, pat)
assert len(lines) == 1
return lines[0]
+
+
+def clean(text, scrub=None):
+ """Remove any text matching `scrub`, and all leading whitespace."""
+ if scrub:
+ text = re.sub(scrub, "", text)
+ text = re.sub(r"(?m)^\s+", "", text)
+ return text