OLD | NEW |
(Empty) | |
| 1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| 2 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| 3 |
| 4 """Tests for XML reports from coverage.py.""" |
| 5 |
| 6 import os |
| 7 import re |
| 8 |
| 9 import coverage |
| 10 |
| 11 from tests.coveragetest import CoverageTest |
| 12 from tests.goldtest import CoverageGoldTest |
| 13 from tests.goldtest import change_dir, compare |
| 14 |
| 15 |
| 16 class XmlTestHelpers(CoverageTest): |
| 17 """Methods to use from XML tests.""" |
| 18 |
| 19 def run_mycode(self): |
| 20 """Run mycode.py, so we can report on it.""" |
| 21 self.make_file("mycode.py", "print('hello')\n") |
| 22 self.run_command("coverage run mycode.py") |
| 23 |
| 24 def run_doit(self): |
| 25 """Construct a simple sub-package.""" |
| 26 self.make_file("sub/__init__.py") |
| 27 self.make_file("sub/doit.py", "print('doit!')") |
| 28 self.make_file("main.py", "import sub.doit") |
| 29 cov = coverage.Coverage() |
| 30 self.start_import_stop(cov, "main") |
| 31 return cov |
| 32 |
| 33 def make_tree(self, width, depth, curdir="."): |
| 34 """Make a tree of packages. |
| 35 |
| 36 Makes `width` directories, named d0 .. d{width-1}. Each directory has |
| 37 __init__.py, and `width` files, named f0.py .. f{width-1}.py. Each |
| 38 directory also has `width` sub-directories, in the same fashion, until |
| 39 a depth of `depth` is reached. |
| 40 |
| 41 """ |
| 42 if depth == 0: |
| 43 return |
| 44 |
| 45 def here(p): |
| 46 """A path for `p` in our currently interesting directory.""" |
| 47 return os.path.join(curdir, p) |
| 48 |
| 49 for i in range(width): |
| 50 next_dir = here("d{0}".format(i)) |
| 51 self.make_tree(width, depth-1, next_dir) |
| 52 if curdir != ".": |
| 53 self.make_file(here("__init__.py"), "") |
| 54 for i in range(width): |
| 55 filename = here("f{0}.py".format(i)) |
| 56 self.make_file(filename, "# {0}\n".format(filename)) |
| 57 |
| 58 |
| 59 class XmlReportTest(XmlTestHelpers, CoverageTest): |
| 60 """Tests of the XML reports from coverage.py.""" |
| 61 |
| 62 def test_default_file_placement(self): |
| 63 self.run_mycode() |
| 64 self.run_command("coverage xml") |
| 65 self.assert_exists("coverage.xml") |
| 66 |
| 67 def test_argument_affects_xml_placement(self): |
| 68 self.run_mycode() |
| 69 self.run_command("coverage xml -o put_it_there.xml") |
| 70 self.assert_doesnt_exist("coverage.xml") |
| 71 self.assert_exists("put_it_there.xml") |
| 72 |
| 73 def test_config_file_directory_does_not_exist(self): |
| 74 self.run_mycode() |
| 75 self.run_command("coverage xml -o nonexistent/put_it_there.xml") |
| 76 self.assert_doesnt_exist("coverage.xml") |
| 77 self.assert_doesnt_exist("put_it_there.xml") |
| 78 self.assert_exists("nonexistent/put_it_there.xml") |
| 79 |
| 80 def test_config_affects_xml_placement(self): |
| 81 self.run_mycode() |
| 82 self.make_file(".coveragerc", "[xml]\noutput = xml.out\n") |
| 83 self.run_command("coverage xml") |
| 84 self.assert_doesnt_exist("coverage.xml") |
| 85 self.assert_exists("xml.out") |
| 86 |
| 87 def test_no_data(self): |
| 88 # https://bitbucket.org/ned/coveragepy/issue/210 |
| 89 self.run_command("coverage xml") |
| 90 self.assert_doesnt_exist("coverage.xml") |
| 91 |
| 92 def test_no_source(self): |
| 93 # Written while investigating a bug, might as well keep it. |
| 94 # https://bitbucket.org/ned/coveragepy/issue/208 |
| 95 self.make_file("innocuous.py", "a = 4") |
| 96 cov = coverage.Coverage() |
| 97 self.start_import_stop(cov, "innocuous") |
| 98 os.remove("innocuous.py") |
| 99 cov.xml_report(ignore_errors=True) |
| 100 self.assert_exists("coverage.xml") |
| 101 |
| 102 def test_filename_format_showing_everything(self): |
| 103 cov = self.run_doit() |
| 104 cov.xml_report(outfile="-") |
| 105 xml = self.stdout() |
| 106 doit_line = re_line(xml, "class.*doit") |
| 107 self.assertIn('filename="sub/doit.py"', doit_line) |
| 108 |
| 109 def test_filename_format_including_filename(self): |
| 110 cov = self.run_doit() |
| 111 cov.xml_report(["sub/doit.py"], outfile="-") |
| 112 xml = self.stdout() |
| 113 doit_line = re_line(xml, "class.*doit") |
| 114 self.assertIn('filename="sub/doit.py"', doit_line) |
| 115 |
| 116 def test_filename_format_including_module(self): |
| 117 cov = self.run_doit() |
| 118 import sub.doit # pylint: disable=import-error |
| 119 cov.xml_report([sub.doit], outfile="-") |
| 120 xml = self.stdout() |
| 121 doit_line = re_line(xml, "class.*doit") |
| 122 self.assertIn('filename="sub/doit.py"', doit_line) |
| 123 |
| 124 def test_reporting_on_nothing(self): |
| 125 # Used to raise a zero division error: |
| 126 # https://bitbucket.org/ned/coveragepy/issue/250 |
| 127 self.make_file("empty.py", "") |
| 128 cov = coverage.Coverage() |
| 129 empty = self.start_import_stop(cov, "empty") |
| 130 cov.xml_report([empty], outfile="-") |
| 131 xml = self.stdout() |
| 132 empty_line = re_line(xml, "class.*empty") |
| 133 self.assertIn('filename="empty.py"', empty_line) |
| 134 self.assertIn('line-rate="1"', empty_line) |
| 135 |
| 136 def test_empty_file_is_100_not_0(self): |
| 137 # https://bitbucket.org/ned/coveragepy/issue/345 |
| 138 cov = self.run_doit() |
| 139 cov.xml_report(outfile="-") |
| 140 xml = self.stdout() |
| 141 init_line = re_line(xml, 'filename="sub/__init__.py"') |
| 142 self.assertIn('line-rate="1"', init_line) |
| 143 |
| 144 |
| 145 class XmlPackageStructureTest(XmlTestHelpers, CoverageTest): |
| 146 """Tests about the package structure reported in the coverage.xml file.""" |
| 147 |
| 148 def package_and_class_tags(self, cov): |
| 149 """Run an XML report on `cov`, and get the package and class tags.""" |
| 150 self.captured_stdout.truncate(0) |
| 151 cov.xml_report(outfile="-") |
| 152 packages_and_classes = re_lines(self.stdout(), r"<package |<class ") |
| 153 scrubs = r' branch-rate="0"| complexity="0"| line-rate="[\d.]+"' |
| 154 return clean("".join(packages_and_classes), scrubs) |
| 155 |
| 156 def assert_package_and_class_tags(self, cov, result): |
| 157 """Check the XML package and class tags from `cov` match `result`.""" |
| 158 self.assertMultiLineEqual( |
| 159 self.package_and_class_tags(cov), |
| 160 clean(result) |
| 161 ) |
| 162 |
| 163 def test_package_names(self): |
| 164 self.make_tree(width=1, depth=3) |
| 165 self.make_file("main.py", """\ |
| 166 from d0.d0 import f0 |
| 167 """) |
| 168 cov = coverage.Coverage(source=".") |
| 169 self.start_import_stop(cov, "main") |
| 170 self.assert_package_and_class_tags(cov, """\ |
| 171 <package name="."> |
| 172 <class filename="main.py" name="main.py"> |
| 173 <package name="d0"> |
| 174 <class filename="d0/__init__.py" name="__init__.py"> |
| 175 <class filename="d0/f0.py" name="f0.py"> |
| 176 <package name="d0.d0"> |
| 177 <class filename="d0/d0/__init__.py" name="__init__.py"> |
| 178 <class filename="d0/d0/f0.py" name="f0.py"> |
| 179 """) |
| 180 |
| 181 def test_package_depth(self): |
| 182 self.make_tree(width=1, depth=4) |
| 183 self.make_file("main.py", """\ |
| 184 from d0.d0 import f0 |
| 185 """) |
| 186 cov = coverage.Coverage(source=".") |
| 187 self.start_import_stop(cov, "main") |
| 188 |
| 189 cov.set_option("xml:package_depth", 1) |
| 190 self.assert_package_and_class_tags(cov, """\ |
| 191 <package name="."> |
| 192 <class filename="main.py" name="main.py"> |
| 193 <package name="d0"> |
| 194 <class filename="d0/__init__.py" name="__init__.py"> |
| 195 <class filename="d0/d0/__init__.py" name="d0/__init__.py"> |
| 196 <class filename="d0/d0/d0/__init__.py" name="d0/d0/__init__.py"> |
| 197 <class filename="d0/d0/d0/f0.py" name="d0/d0/f0.py"> |
| 198 <class filename="d0/d0/f0.py" name="d0/f0.py"> |
| 199 <class filename="d0/f0.py" name="f0.py"> |
| 200 """) |
| 201 |
| 202 cov.set_option("xml:package_depth", 2) |
| 203 self.assert_package_and_class_tags(cov, """\ |
| 204 <package name="."> |
| 205 <class filename="main.py" name="main.py"> |
| 206 <package name="d0"> |
| 207 <class filename="d0/__init__.py" name="__init__.py"> |
| 208 <class filename="d0/f0.py" name="f0.py"> |
| 209 <package name="d0.d0"> |
| 210 <class filename="d0/d0/__init__.py" name="__init__.py"> |
| 211 <class filename="d0/d0/d0/__init__.py" name="d0/__init__.py"> |
| 212 <class filename="d0/d0/d0/f0.py" name="d0/f0.py"> |
| 213 <class filename="d0/d0/f0.py" name="f0.py"> |
| 214 """) |
| 215 |
| 216 cov.set_option("xml:package_depth", 3) |
| 217 self.assert_package_and_class_tags(cov, """\ |
| 218 <package name="."> |
| 219 <class filename="main.py" name="main.py"> |
| 220 <package name="d0"> |
| 221 <class filename="d0/__init__.py" name="__init__.py"> |
| 222 <class filename="d0/f0.py" name="f0.py"> |
| 223 <package name="d0.d0"> |
| 224 <class filename="d0/d0/__init__.py" name="__init__.py"> |
| 225 <class filename="d0/d0/f0.py" name="f0.py"> |
| 226 <package name="d0.d0.d0"> |
| 227 <class filename="d0/d0/d0/__init__.py" name="__init__.py"> |
| 228 <class filename="d0/d0/d0/f0.py" name="f0.py"> |
| 229 """) |
| 230 |
| 231 |
| 232 def re_lines(text, pat): |
| 233 """Return a list of lines that match `pat` in the string `text`.""" |
| 234 lines = [l for l in text.splitlines(True) if re.search(pat, l)] |
| 235 return lines |
| 236 |
| 237 |
| 238 def re_line(text, pat): |
| 239 """Return the one line in `text` that matches regex `pat`.""" |
| 240 lines = re_lines(text, pat) |
| 241 assert len(lines) == 1 |
| 242 return lines[0] |
| 243 |
| 244 |
| 245 def clean(text, scrub=None): |
| 246 """Clean text to prepare it for comparison. |
| 247 |
| 248 Remove text matching `scrub`, and leading whitespace. Convert backslashes |
| 249 to forward slashes. |
| 250 |
| 251 """ |
| 252 if scrub: |
| 253 text = re.sub(scrub, "", text) |
| 254 text = re.sub(r"(?m)^\s+", "", text) |
| 255 text = re.sub(r"\\", "/", text) |
| 256 return text |
| 257 |
| 258 |
| 259 class XmlGoldTest(CoverageGoldTest): |
| 260 """Tests of XML reporting that use gold files.""" |
| 261 |
| 262 # TODO: this should move out of html. |
| 263 root_dir = 'tests/farm/html' |
| 264 |
| 265 def test_a_xml_1(self): |
| 266 self.output_dir("out/xml_1") |
| 267 |
| 268 with change_dir("src"): |
| 269 # pylint: disable=import-error |
| 270 cov = coverage.Coverage() |
| 271 cov.start() |
| 272 import a # pragma: nested |
| 273 cov.stop() # pragma: nested |
| 274 cov.xml_report(a, outfile="../out/xml_1/coverage.xml") |
| 275 source_path = coverage.files.relative_directory().rstrip('/') |
| 276 |
| 277 compare("gold_x_xml", "out/xml_1", scrubs=[ |
| 278 (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), |
| 279 (r' version="[-.\w]+"', ' version="VERSION"'), |
| 280 (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path)
, |
| 281 (r'/coverage.readthedocs.org/?[-.\w/]*', '/coverage.readthedocs.org/
VER'), |
| 282 ]) |
| 283 |
| 284 def test_a_xml_2(self): |
| 285 self.output_dir("out/xml_2") |
| 286 |
| 287 with change_dir("src"): |
| 288 # pylint: disable=import-error |
| 289 cov = coverage.Coverage(config_file="run_a_xml_2.ini") |
| 290 cov.start() |
| 291 import a # pragma: nested |
| 292 cov.stop() # pragma: nested |
| 293 cov.xml_report(a) |
| 294 source_path = coverage.files.relative_directory().rstrip('/') |
| 295 |
| 296 compare("gold_x_xml", "out/xml_2", scrubs=[ |
| 297 (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), |
| 298 (r' version="[-.\w]+"', ' version="VERSION"'), |
| 299 (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path)
, |
| 300 (r'/coverage.readthedocs.org/?[-.\w/]*', '/coverage.readthedocs.org/
VER'), |
| 301 ]) |
| 302 |
| 303 def test_y_xml_branch(self): |
| 304 self.output_dir("out/y_xml_branch") |
| 305 |
| 306 with change_dir("src"): |
| 307 # pylint: disable=import-error |
| 308 cov = coverage.Coverage(branch=True) |
| 309 cov.start() |
| 310 import y # pragma: nested |
| 311 cov.stop() # pragma: nested |
| 312 cov.xml_report(y, outfile="../out/y_xml_branch/coverage.xml") |
| 313 source_path = coverage.files.relative_directory().rstrip('/') |
| 314 |
| 315 compare("gold_y_xml_branch", "out/y_xml_branch", scrubs=[ |
| 316 (r' timestamp="\d+"', ' timestamp="TIMESTAMP"'), |
| 317 (r' version="[-.\w]+"', ' version="VERSION"'), |
| 318 (r'<source>\s*.*?\s*</source>', '<source>%s</source>' % source_path)
, |
| 319 (r'/coverage.readthedocs.org/?[-.\w/]*', '/coverage.readthedocs.org/
VER'), |
| 320 ]) |
OLD | NEW |