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 coverage.py's API.""" |
| 5 |
| 6 import fnmatch |
| 7 import os |
| 8 import sys |
| 9 import textwrap |
| 10 |
| 11 import coverage |
| 12 from coverage.backward import StringIO |
| 13 from coverage.misc import CoverageException |
| 14 |
| 15 from tests.coveragetest import CoverageTest |
| 16 |
| 17 |
| 18 class ApiTest(CoverageTest): |
| 19 """Api-oriented tests for coverage.py.""" |
| 20 |
| 21 def clean_files(self, files, pats): |
| 22 """Remove names matching `pats` from `files`, a list of file names.""" |
| 23 good = [] |
| 24 for f in files: |
| 25 for pat in pats: |
| 26 if fnmatch.fnmatch(f, pat): |
| 27 break |
| 28 else: |
| 29 good.append(f) |
| 30 return good |
| 31 |
| 32 def assertFiles(self, files): |
| 33 """Assert that the files here are `files`, ignoring the usual junk.""" |
| 34 here = os.listdir(".") |
| 35 here = self.clean_files(here, ["*.pyc", "__pycache__"]) |
| 36 self.assertCountEqual(here, files) |
| 37 |
| 38 def test_unexecuted_file(self): |
| 39 cov = coverage.Coverage() |
| 40 |
| 41 self.make_file("mycode.py", """\ |
| 42 a = 1 |
| 43 b = 2 |
| 44 if b == 3: |
| 45 c = 4 |
| 46 d = 5 |
| 47 """) |
| 48 |
| 49 self.make_file("not_run.py", """\ |
| 50 fooey = 17 |
| 51 """) |
| 52 |
| 53 # Import the Python file, executing it. |
| 54 self.start_import_stop(cov, "mycode") |
| 55 |
| 56 _, statements, missing, _ = cov.analysis("not_run.py") |
| 57 self.assertEqual(statements, [1]) |
| 58 self.assertEqual(missing, [1]) |
| 59 |
| 60 def test_filenames(self): |
| 61 |
| 62 self.make_file("mymain.py", """\ |
| 63 import mymod |
| 64 a = 1 |
| 65 """) |
| 66 |
| 67 self.make_file("mymod.py", """\ |
| 68 fooey = 17 |
| 69 """) |
| 70 |
| 71 # Import the Python file, executing it. |
| 72 cov = coverage.Coverage() |
| 73 self.start_import_stop(cov, "mymain") |
| 74 |
| 75 filename, _, _, _ = cov.analysis("mymain.py") |
| 76 self.assertEqual(os.path.basename(filename), "mymain.py") |
| 77 filename, _, _, _ = cov.analysis("mymod.py") |
| 78 self.assertEqual(os.path.basename(filename), "mymod.py") |
| 79 |
| 80 filename, _, _, _ = cov.analysis(sys.modules["mymain"]) |
| 81 self.assertEqual(os.path.basename(filename), "mymain.py") |
| 82 filename, _, _, _ = cov.analysis(sys.modules["mymod"]) |
| 83 self.assertEqual(os.path.basename(filename), "mymod.py") |
| 84 |
| 85 # Import the Python file, executing it again, once it's been compiled |
| 86 # already. |
| 87 cov = coverage.Coverage() |
| 88 self.start_import_stop(cov, "mymain") |
| 89 |
| 90 filename, _, _, _ = cov.analysis("mymain.py") |
| 91 self.assertEqual(os.path.basename(filename), "mymain.py") |
| 92 filename, _, _, _ = cov.analysis("mymod.py") |
| 93 self.assertEqual(os.path.basename(filename), "mymod.py") |
| 94 |
| 95 filename, _, _, _ = cov.analysis(sys.modules["mymain"]) |
| 96 self.assertEqual(os.path.basename(filename), "mymain.py") |
| 97 filename, _, _, _ = cov.analysis(sys.modules["mymod"]) |
| 98 self.assertEqual(os.path.basename(filename), "mymod.py") |
| 99 |
| 100 def test_ignore_stdlib(self): |
| 101 self.make_file("mymain.py", """\ |
| 102 import colorsys |
| 103 a = 1 |
| 104 hls = colorsys.rgb_to_hls(1.0, 0.5, 0.0) |
| 105 """) |
| 106 |
| 107 # Measure without the stdlib. |
| 108 cov1 = coverage.Coverage() |
| 109 self.assertEqual(cov1.config.cover_pylib, False) |
| 110 self.start_import_stop(cov1, "mymain") |
| 111 |
| 112 # some statements were marked executed in mymain.py |
| 113 _, statements, missing, _ = cov1.analysis("mymain.py") |
| 114 self.assertNotEqual(statements, missing) |
| 115 # but none were in colorsys.py |
| 116 _, statements, missing, _ = cov1.analysis("colorsys.py") |
| 117 self.assertEqual(statements, missing) |
| 118 |
| 119 # Measure with the stdlib. |
| 120 cov2 = coverage.Coverage(cover_pylib=True) |
| 121 self.start_import_stop(cov2, "mymain") |
| 122 |
| 123 # some statements were marked executed in mymain.py |
| 124 _, statements, missing, _ = cov2.analysis("mymain.py") |
| 125 self.assertNotEqual(statements, missing) |
| 126 # and some were marked executed in colorsys.py |
| 127 _, statements, missing, _ = cov2.analysis("colorsys.py") |
| 128 self.assertNotEqual(statements, missing) |
| 129 |
| 130 def test_include_can_measure_stdlib(self): |
| 131 self.make_file("mymain.py", """\ |
| 132 import colorsys, random |
| 133 a = 1 |
| 134 r, g, b = [random.random() for _ in range(3)] |
| 135 hls = colorsys.rgb_to_hls(r, g, b) |
| 136 """) |
| 137 |
| 138 # Measure without the stdlib, but include colorsys. |
| 139 cov1 = coverage.Coverage(cover_pylib=False, include=["*/colorsys.py"]) |
| 140 self.start_import_stop(cov1, "mymain") |
| 141 |
| 142 # some statements were marked executed in colorsys.py |
| 143 _, statements, missing, _ = cov1.analysis("colorsys.py") |
| 144 self.assertNotEqual(statements, missing) |
| 145 # but none were in random.py |
| 146 _, statements, missing, _ = cov1.analysis("random.py") |
| 147 self.assertEqual(statements, missing) |
| 148 |
| 149 def test_exclude_list(self): |
| 150 cov = coverage.Coverage() |
| 151 cov.clear_exclude() |
| 152 self.assertEqual(cov.get_exclude_list(), []) |
| 153 cov.exclude("foo") |
| 154 self.assertEqual(cov.get_exclude_list(), ["foo"]) |
| 155 cov.exclude("bar") |
| 156 self.assertEqual(cov.get_exclude_list(), ["foo", "bar"]) |
| 157 self.assertEqual(cov._exclude_regex('exclude'), "(?:foo)|(?:bar)") |
| 158 cov.clear_exclude() |
| 159 self.assertEqual(cov.get_exclude_list(), []) |
| 160 |
| 161 def test_exclude_partial_list(self): |
| 162 cov = coverage.Coverage() |
| 163 cov.clear_exclude(which='partial') |
| 164 self.assertEqual(cov.get_exclude_list(which='partial'), []) |
| 165 cov.exclude("foo", which='partial') |
| 166 self.assertEqual(cov.get_exclude_list(which='partial'), ["foo"]) |
| 167 cov.exclude("bar", which='partial') |
| 168 self.assertEqual(cov.get_exclude_list(which='partial'), ["foo", "bar"]) |
| 169 self.assertEqual( |
| 170 cov._exclude_regex(which='partial'), "(?:foo)|(?:bar)" |
| 171 ) |
| 172 cov.clear_exclude(which='partial') |
| 173 self.assertEqual(cov.get_exclude_list(which='partial'), []) |
| 174 |
| 175 def test_exclude_and_partial_are_separate_lists(self): |
| 176 cov = coverage.Coverage() |
| 177 cov.clear_exclude(which='partial') |
| 178 cov.clear_exclude(which='exclude') |
| 179 cov.exclude("foo", which='partial') |
| 180 self.assertEqual(cov.get_exclude_list(which='partial'), ['foo']) |
| 181 self.assertEqual(cov.get_exclude_list(which='exclude'), []) |
| 182 cov.exclude("bar", which='exclude') |
| 183 self.assertEqual(cov.get_exclude_list(which='partial'), ['foo']) |
| 184 self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar']) |
| 185 cov.exclude("p2", which='partial') |
| 186 cov.exclude("e2", which='exclude') |
| 187 self.assertEqual(cov.get_exclude_list(which='partial'), ['foo', 'p2']) |
| 188 self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar', 'e2']) |
| 189 cov.clear_exclude(which='partial') |
| 190 self.assertEqual(cov.get_exclude_list(which='partial'), []) |
| 191 self.assertEqual(cov.get_exclude_list(which='exclude'), ['bar', 'e2']) |
| 192 cov.clear_exclude(which='exclude') |
| 193 self.assertEqual(cov.get_exclude_list(which='partial'), []) |
| 194 self.assertEqual(cov.get_exclude_list(which='exclude'), []) |
| 195 |
| 196 def test_datafile_default(self): |
| 197 # Default data file behavior: it's .coverage |
| 198 self.make_file("datatest1.py", """\ |
| 199 fooey = 17 |
| 200 """) |
| 201 |
| 202 self.assertFiles(["datatest1.py"]) |
| 203 cov = coverage.Coverage() |
| 204 self.start_import_stop(cov, "datatest1") |
| 205 cov.save() |
| 206 self.assertFiles(["datatest1.py", ".coverage"]) |
| 207 |
| 208 def test_datafile_specified(self): |
| 209 # You can specify the data file name. |
| 210 self.make_file("datatest2.py", """\ |
| 211 fooey = 17 |
| 212 """) |
| 213 |
| 214 self.assertFiles(["datatest2.py"]) |
| 215 cov = coverage.Coverage(data_file="cov.data") |
| 216 self.start_import_stop(cov, "datatest2") |
| 217 cov.save() |
| 218 self.assertFiles(["datatest2.py", "cov.data"]) |
| 219 |
| 220 def test_datafile_and_suffix_specified(self): |
| 221 # You can specify the data file name and suffix. |
| 222 self.make_file("datatest3.py", """\ |
| 223 fooey = 17 |
| 224 """) |
| 225 |
| 226 self.assertFiles(["datatest3.py"]) |
| 227 cov = coverage.Coverage(data_file="cov.data", data_suffix="14") |
| 228 self.start_import_stop(cov, "datatest3") |
| 229 cov.save() |
| 230 self.assertFiles(["datatest3.py", "cov.data.14"]) |
| 231 |
| 232 def test_datafile_from_rcfile(self): |
| 233 # You can specify the data file name in the .coveragerc file |
| 234 self.make_file("datatest4.py", """\ |
| 235 fooey = 17 |
| 236 """) |
| 237 self.make_file(".coveragerc", """\ |
| 238 [run] |
| 239 data_file = mydata.dat |
| 240 """) |
| 241 |
| 242 self.assertFiles(["datatest4.py", ".coveragerc"]) |
| 243 cov = coverage.Coverage() |
| 244 self.start_import_stop(cov, "datatest4") |
| 245 cov.save() |
| 246 self.assertFiles(["datatest4.py", ".coveragerc", "mydata.dat"]) |
| 247 |
| 248 def test_empty_reporting(self): |
| 249 # empty summary reports raise exception, just like the xml report |
| 250 cov = coverage.Coverage() |
| 251 cov.erase() |
| 252 self.assertRaises(CoverageException, cov.report) |
| 253 |
| 254 def test_start_stop_start_stop(self): |
| 255 self.make_file("code1.py", """\ |
| 256 code1 = 1 |
| 257 """) |
| 258 self.make_file("code2.py", """\ |
| 259 code2 = 1 |
| 260 code2 = 2 |
| 261 """) |
| 262 cov = coverage.Coverage() |
| 263 self.start_import_stop(cov, "code1") |
| 264 cov.save() |
| 265 self.start_import_stop(cov, "code2") |
| 266 _, statements, missing, _ = cov.analysis("code1.py") |
| 267 self.assertEqual(statements, [1]) |
| 268 self.assertEqual(missing, []) |
| 269 _, statements, missing, _ = cov.analysis("code2.py") |
| 270 self.assertEqual(statements, [1, 2]) |
| 271 self.assertEqual(missing, []) |
| 272 |
| 273 if 0: # expected failure |
| 274 # for https://bitbucket.org/ned/coveragepy/issue/79 |
| 275 def test_start_save_stop(self): |
| 276 self.make_file("code1.py", """\ |
| 277 code1 = 1 |
| 278 """) |
| 279 self.make_file("code2.py", """\ |
| 280 code2 = 1 |
| 281 code2 = 2 |
| 282 """) |
| 283 cov = coverage.Coverage() |
| 284 cov.start() |
| 285 self.import_local_file("code1") |
| 286 cov.save() |
| 287 self.import_local_file("code2") |
| 288 cov.stop() |
| 289 |
| 290 _, statements, missing, _ = cov.analysis("code1.py") |
| 291 self.assertEqual(statements, [1]) |
| 292 self.assertEqual(missing, []) |
| 293 _, statements, missing, _ = cov.analysis("code2.py") |
| 294 self.assertEqual(statements, [1, 2]) |
| 295 self.assertEqual(missing, []) |
| 296 |
| 297 |
| 298 |
| 299 class UsingModulesMixin(object): |
| 300 """A mixin for importing modules from test/modules and test/moremodules.""" |
| 301 |
| 302 run_in_temp_dir = False |
| 303 |
| 304 def setUp(self): |
| 305 super(UsingModulesMixin, self).setUp() |
| 306 |
| 307 old_dir = os.getcwd() |
| 308 os.chdir(self.nice_file(os.path.dirname(__file__), 'modules')) |
| 309 self.addCleanup(os.chdir, old_dir) |
| 310 |
| 311 # Parent class saves and restores sys.path, we can just modify it. |
| 312 sys.path.append(".") |
| 313 sys.path.append("../moremodules") |
| 314 |
| 315 |
| 316 class OmitIncludeTestsMixin(UsingModulesMixin): |
| 317 """Test methods for coverage methods taking include and omit.""" |
| 318 |
| 319 def filenames_in(self, summary, filenames): |
| 320 """Assert the `filenames` are in the keys of `summary`.""" |
| 321 for filename in filenames.split(): |
| 322 self.assertIn(filename, summary) |
| 323 |
| 324 def filenames_not_in(self, summary, filenames): |
| 325 """Assert the `filenames` are not in the keys of `summary`.""" |
| 326 for filename in filenames.split(): |
| 327 self.assertNotIn(filename, summary) |
| 328 |
| 329 def test_nothing_specified(self): |
| 330 result = self.coverage_usepkgs() |
| 331 self.filenames_in(result, "p1a p1b p2a p2b othera otherb osa osb") |
| 332 self.filenames_not_in(result, "p1c") |
| 333 # Because there was no source= specified, we don't search for |
| 334 # unexecuted files. |
| 335 |
| 336 def test_include(self): |
| 337 result = self.coverage_usepkgs(include=["*/p1a.py"]) |
| 338 self.filenames_in(result, "p1a") |
| 339 self.filenames_not_in(result, "p1b p1c p2a p2b othera otherb osa osb") |
| 340 |
| 341 def test_include_2(self): |
| 342 result = self.coverage_usepkgs(include=["*a.py"]) |
| 343 self.filenames_in(result, "p1a p2a othera osa") |
| 344 self.filenames_not_in(result, "p1b p1c p2b otherb osb") |
| 345 |
| 346 def test_include_as_string(self): |
| 347 result = self.coverage_usepkgs(include="*a.py") |
| 348 self.filenames_in(result, "p1a p2a othera osa") |
| 349 self.filenames_not_in(result, "p1b p1c p2b otherb osb") |
| 350 |
| 351 def test_omit(self): |
| 352 result = self.coverage_usepkgs(omit=["*/p1a.py"]) |
| 353 self.filenames_in(result, "p1b p2a p2b") |
| 354 self.filenames_not_in(result, "p1a p1c") |
| 355 |
| 356 def test_omit_2(self): |
| 357 result = self.coverage_usepkgs(omit=["*a.py"]) |
| 358 self.filenames_in(result, "p1b p2b otherb osb") |
| 359 self.filenames_not_in(result, "p1a p1c p2a othera osa") |
| 360 |
| 361 def test_omit_as_string(self): |
| 362 result = self.coverage_usepkgs(omit="*a.py") |
| 363 self.filenames_in(result, "p1b p2b otherb osb") |
| 364 self.filenames_not_in(result, "p1a p1c p2a othera osa") |
| 365 |
| 366 def test_omit_and_include(self): |
| 367 result = self.coverage_usepkgs(include=["*/p1*"], omit=["*/p1a.py"]) |
| 368 self.filenames_in(result, "p1b") |
| 369 self.filenames_not_in(result, "p1a p1c p2a p2b") |
| 370 |
| 371 |
| 372 class SourceOmitIncludeTest(OmitIncludeTestsMixin, CoverageTest): |
| 373 """Test using `source`, `omit` and `include` when measuring code.""" |
| 374 |
| 375 def coverage_usepkgs(self, **kwargs): |
| 376 """Run coverage on usepkgs and return the line summary. |
| 377 |
| 378 Arguments are passed to the `coverage.Coverage` constructor. |
| 379 |
| 380 """ |
| 381 cov = coverage.Coverage(**kwargs) |
| 382 cov.start() |
| 383 import usepkgs # pragma: nested # pylint: disable=import-error,unused
-variable |
| 384 cov.stop() # pragma: nested |
| 385 data = cov.get_data() |
| 386 summary = data.line_counts() |
| 387 for k, v in list(summary.items()): |
| 388 assert k.endswith(".py") |
| 389 summary[k[:-3]] = v |
| 390 return summary |
| 391 |
| 392 def test_source_package(self): |
| 393 lines = self.coverage_usepkgs(source=["pkg1"]) |
| 394 self.filenames_in(lines, "p1a p1b") |
| 395 self.filenames_not_in(lines, "p2a p2b othera otherb osa osb") |
| 396 # Because source= was specified, we do search for unexecuted files. |
| 397 self.assertEqual(lines['p1c'], 0) |
| 398 |
| 399 def test_source_package_dotted(self): |
| 400 lines = self.coverage_usepkgs(source=["pkg1.p1b"]) |
| 401 self.filenames_in(lines, "p1b") |
| 402 self.filenames_not_in(lines, "p1a p1c p2a p2b othera otherb osa osb") |
| 403 |
| 404 def test_source_package_part_omitted(self): |
| 405 # https://bitbucket.org/ned/coveragepy/issue/218 |
| 406 # Used to be if you omitted something executed and inside the source, |
| 407 # then after it was executed but not recorded, it would be found in |
| 408 # the search for unexecuted files, and given a score of 0%. |
| 409 lines = self.coverage_usepkgs(source=["pkg1"], omit=["pkg1/p1b.py"]) |
| 410 self.filenames_in(lines, "p1a") |
| 411 self.filenames_not_in(lines, "p1b") |
| 412 self.assertEqual(lines['p1c'], 0) |
| 413 |
| 414 |
| 415 class ReportIncludeOmitTest(OmitIncludeTestsMixin, CoverageTest): |
| 416 """Tests of the report include/omit functionality.""" |
| 417 |
| 418 def coverage_usepkgs(self, **kwargs): |
| 419 """Try coverage.report().""" |
| 420 cov = coverage.Coverage() |
| 421 cov.start() |
| 422 import usepkgs # pragma: nested # pylint: disable=import-error,unused
-variable |
| 423 cov.stop() # pragma: nested |
| 424 report = StringIO() |
| 425 cov.report(file=report, **kwargs) |
| 426 return report.getvalue() |
| 427 |
| 428 |
| 429 class XmlIncludeOmitTest(OmitIncludeTestsMixin, CoverageTest): |
| 430 """Tests of the XML include/omit functionality. |
| 431 |
| 432 This also takes care of the HTML and annotate include/omit, by virtue |
| 433 of the structure of the code. |
| 434 |
| 435 """ |
| 436 |
| 437 def coverage_usepkgs(self, **kwargs): |
| 438 """Try coverage.xml_report().""" |
| 439 cov = coverage.Coverage() |
| 440 cov.start() |
| 441 import usepkgs # pragma: nested # pylint: disable=import-error,unused
-variable |
| 442 cov.stop() # pragma: nested |
| 443 cov.xml_report(outfile="-", **kwargs) |
| 444 return self.stdout() |
| 445 |
| 446 |
| 447 class AnalysisTest(CoverageTest): |
| 448 """Test the numerical analysis of results.""" |
| 449 def test_many_missing_branches(self): |
| 450 cov = coverage.Coverage(branch=True) |
| 451 |
| 452 self.make_file("missing.py", """\ |
| 453 def fun1(x): |
| 454 if x == 1: |
| 455 print("one") |
| 456 else: |
| 457 print("not one") |
| 458 print("done") # pragma: nocover |
| 459 |
| 460 def fun2(x): |
| 461 print("x") |
| 462 |
| 463 fun2(3) |
| 464 """) |
| 465 |
| 466 # Import the Python file, executing it. |
| 467 self.start_import_stop(cov, "missing") |
| 468 |
| 469 nums = cov._analyze("missing.py").numbers |
| 470 self.assertEqual(nums.n_files, 1) |
| 471 self.assertEqual(nums.n_statements, 7) |
| 472 self.assertEqual(nums.n_excluded, 1) |
| 473 self.assertEqual(nums.n_missing, 3) |
| 474 self.assertEqual(nums.n_branches, 2) |
| 475 self.assertEqual(nums.n_partial_branches, 0) |
| 476 self.assertEqual(nums.n_missing_branches, 2) |
| 477 |
| 478 |
| 479 class PluginTest(CoverageTest): |
| 480 """Test that the API works properly the way the plugins call it. |
| 481 |
| 482 We don't actually use the plugins, but these tests call the API the same |
| 483 way they do. |
| 484 |
| 485 """ |
| 486 def pretend_to_be_nose_with_cover(self, erase): |
| 487 """This is what the nose --with-cover plugin does.""" |
| 488 cov = coverage.Coverage() |
| 489 |
| 490 self.make_file("no_biggie.py", """\ |
| 491 a = 1 |
| 492 b = 2 |
| 493 if b == 1: |
| 494 c = 4 |
| 495 """) |
| 496 |
| 497 if erase: |
| 498 cov.combine() |
| 499 cov.erase() |
| 500 cov.load() |
| 501 self.start_import_stop(cov, "no_biggie") |
| 502 cov.combine() |
| 503 cov.save() |
| 504 cov.report(["no_biggie.py"]) |
| 505 self.assertEqual(self.stdout(), textwrap.dedent("""\ |
| 506 Name Stmts Miss Cover Missing |
| 507 -------------------------------------------- |
| 508 no_biggie.py 4 1 75% 4 |
| 509 """)) |
| 510 |
| 511 def test_nose_plugin(self): |
| 512 self.pretend_to_be_nose_with_cover(erase=False) |
| 513 |
| 514 def test_nose_plugin_with_erase(self): |
| 515 self.pretend_to_be_nose_with_cover(erase=True) |
OLD | NEW |