OLD | NEW |
(Empty) | |
| 1 # coding: utf8 |
| 2 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
| 3 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
| 4 |
| 5 """Tests for process behavior of coverage.py.""" |
| 6 |
| 7 import glob |
| 8 import os |
| 9 import os.path |
| 10 import re |
| 11 import sys |
| 12 import textwrap |
| 13 |
| 14 import coverage |
| 15 from coverage import env, CoverageData |
| 16 |
| 17 from tests.coveragetest import CoverageTest |
| 18 |
| 19 HERE = os.path.dirname(__file__) |
| 20 |
| 21 |
| 22 class ProcessTest(CoverageTest): |
| 23 """Tests of the per-process behavior of coverage.py.""" |
| 24 |
| 25 def number_of_data_files(self): |
| 26 """Return the number of coverage data files in this directory.""" |
| 27 num = 0 |
| 28 for f in os.listdir('.'): |
| 29 if f.startswith('.coverage.') or f == '.coverage': |
| 30 num += 1 |
| 31 return num |
| 32 |
| 33 def test_save_on_exit(self): |
| 34 self.make_file("mycode.py", """\ |
| 35 h = "Hello" |
| 36 w = "world" |
| 37 """) |
| 38 |
| 39 self.assert_doesnt_exist(".coverage") |
| 40 self.run_command("coverage run mycode.py") |
| 41 self.assert_exists(".coverage") |
| 42 |
| 43 def test_environment(self): |
| 44 # Checks that we can import modules from the test directory at all! |
| 45 self.make_file("mycode.py", """\ |
| 46 import covmod1 |
| 47 import covmodzip1 |
| 48 a = 1 |
| 49 print('done') |
| 50 """) |
| 51 |
| 52 self.assert_doesnt_exist(".coverage") |
| 53 out = self.run_command("coverage run mycode.py") |
| 54 self.assert_exists(".coverage") |
| 55 self.assertEqual(out, 'done\n') |
| 56 |
| 57 def make_b_or_c_py(self): |
| 58 """Create b_or_c.py, used in a few of these tests.""" |
| 59 self.make_file("b_or_c.py", """\ |
| 60 import sys |
| 61 a = 1 |
| 62 if sys.argv[1] == 'b': |
| 63 b = 1 |
| 64 else: |
| 65 c = 1 |
| 66 d = 1 |
| 67 print('done') |
| 68 """) |
| 69 |
| 70 def test_combine_parallel_data(self): |
| 71 self.make_b_or_c_py() |
| 72 out = self.run_command("coverage run -p b_or_c.py b") |
| 73 self.assertEqual(out, 'done\n') |
| 74 self.assert_doesnt_exist(".coverage") |
| 75 self.assertEqual(self.number_of_data_files(), 1) |
| 76 |
| 77 out = self.run_command("coverage run -p b_or_c.py c") |
| 78 self.assertEqual(out, 'done\n') |
| 79 self.assert_doesnt_exist(".coverage") |
| 80 |
| 81 # After two -p runs, there should be two .coverage.machine.123 files. |
| 82 self.assertEqual(self.number_of_data_files(), 2) |
| 83 |
| 84 # Combine the parallel coverage data files into .coverage . |
| 85 self.run_command("coverage combine") |
| 86 self.assert_exists(".coverage") |
| 87 |
| 88 # After combining, there should be only the .coverage file. |
| 89 self.assertEqual(self.number_of_data_files(), 1) |
| 90 |
| 91 # Read the coverage file and see that b_or_c.py has all 7 lines |
| 92 # executed. |
| 93 data = coverage.CoverageData() |
| 94 data.read_file(".coverage") |
| 95 self.assertEqual(data.line_counts()['b_or_c.py'], 7) |
| 96 |
| 97 def test_combine_parallel_data_in_two_steps(self): |
| 98 self.make_b_or_c_py() |
| 99 |
| 100 out = self.run_command("coverage run -p b_or_c.py b") |
| 101 self.assertEqual(out, 'done\n') |
| 102 self.assert_doesnt_exist(".coverage") |
| 103 self.assertEqual(self.number_of_data_files(), 1) |
| 104 |
| 105 # Combine the (one) parallel coverage data file into .coverage . |
| 106 self.run_command("coverage combine") |
| 107 self.assert_exists(".coverage") |
| 108 self.assertEqual(self.number_of_data_files(), 1) |
| 109 |
| 110 out = self.run_command("coverage run -p b_or_c.py c") |
| 111 self.assertEqual(out, 'done\n') |
| 112 self.assert_exists(".coverage") |
| 113 self.assertEqual(self.number_of_data_files(), 2) |
| 114 |
| 115 # Combine the parallel coverage data files into .coverage . |
| 116 self.run_command("coverage combine") |
| 117 self.assert_exists(".coverage") |
| 118 |
| 119 # After combining, there should be only the .coverage file. |
| 120 self.assertEqual(self.number_of_data_files(), 1) |
| 121 |
| 122 # Read the coverage file and see that b_or_c.py has all 7 lines |
| 123 # executed. |
| 124 data = coverage.CoverageData() |
| 125 data.read_file(".coverage") |
| 126 self.assertEqual(data.line_counts()['b_or_c.py'], 7) |
| 127 |
| 128 def test_append_data(self): |
| 129 self.make_b_or_c_py() |
| 130 |
| 131 out = self.run_command("coverage run b_or_c.py b") |
| 132 self.assertEqual(out, 'done\n') |
| 133 self.assert_exists(".coverage") |
| 134 self.assertEqual(self.number_of_data_files(), 1) |
| 135 |
| 136 out = self.run_command("coverage run --append b_or_c.py c") |
| 137 self.assertEqual(out, 'done\n') |
| 138 self.assert_exists(".coverage") |
| 139 self.assertEqual(self.number_of_data_files(), 1) |
| 140 |
| 141 # Read the coverage file and see that b_or_c.py has all 7 lines |
| 142 # executed. |
| 143 data = coverage.CoverageData() |
| 144 data.read_file(".coverage") |
| 145 self.assertEqual(data.line_counts()['b_or_c.py'], 7) |
| 146 |
| 147 def test_append_data_with_different_file(self): |
| 148 self.make_b_or_c_py() |
| 149 |
| 150 self.make_file(".coveragerc", """\ |
| 151 [run] |
| 152 data_file = .mycovdata |
| 153 """) |
| 154 |
| 155 out = self.run_command("coverage run b_or_c.py b") |
| 156 self.assertEqual(out, 'done\n') |
| 157 self.assert_doesnt_exist(".coverage") |
| 158 self.assert_exists(".mycovdata") |
| 159 |
| 160 out = self.run_command("coverage run --append b_or_c.py c") |
| 161 self.assertEqual(out, 'done\n') |
| 162 self.assert_doesnt_exist(".coverage") |
| 163 self.assert_exists(".mycovdata") |
| 164 |
| 165 # Read the coverage file and see that b_or_c.py has all 7 lines |
| 166 # executed. |
| 167 data = coverage.CoverageData() |
| 168 data.read_file(".mycovdata") |
| 169 self.assertEqual(data.line_counts()['b_or_c.py'], 7) |
| 170 |
| 171 def test_append_can_create_a_data_file(self): |
| 172 self.make_b_or_c_py() |
| 173 |
| 174 out = self.run_command("coverage run --append b_or_c.py b") |
| 175 self.assertEqual(out, 'done\n') |
| 176 self.assert_exists(".coverage") |
| 177 self.assertEqual(self.number_of_data_files(), 1) |
| 178 |
| 179 # Read the coverage file and see that b_or_c.py has only 6 lines |
| 180 # executed. |
| 181 data = coverage.CoverageData() |
| 182 data.read_file(".coverage") |
| 183 self.assertEqual(data.line_counts()['b_or_c.py'], 6) |
| 184 |
| 185 def test_combine_with_rc(self): |
| 186 self.make_b_or_c_py() |
| 187 |
| 188 self.make_file(".coveragerc", """\ |
| 189 [run] |
| 190 parallel = true |
| 191 """) |
| 192 |
| 193 out = self.run_command("coverage run b_or_c.py b") |
| 194 self.assertEqual(out, 'done\n') |
| 195 self.assert_doesnt_exist(".coverage") |
| 196 |
| 197 out = self.run_command("coverage run b_or_c.py c") |
| 198 self.assertEqual(out, 'done\n') |
| 199 self.assert_doesnt_exist(".coverage") |
| 200 |
| 201 # After two runs, there should be two .coverage.machine.123 files. |
| 202 self.assertEqual(self.number_of_data_files(), 2) |
| 203 |
| 204 # Combine the parallel coverage data files into .coverage . |
| 205 self.run_command("coverage combine") |
| 206 self.assert_exists(".coverage") |
| 207 self.assert_exists(".coveragerc") |
| 208 |
| 209 # After combining, there should be only the .coverage file. |
| 210 self.assertEqual(self.number_of_data_files(), 1) |
| 211 |
| 212 # Read the coverage file and see that b_or_c.py has all 7 lines |
| 213 # executed. |
| 214 data = coverage.CoverageData() |
| 215 data.read_file(".coverage") |
| 216 self.assertEqual(data.line_counts()['b_or_c.py'], 7) |
| 217 |
| 218 # Reporting should still work even with the .rc file |
| 219 out = self.run_command("coverage report") |
| 220 self.assertMultiLineEqual(out, textwrap.dedent("""\ |
| 221 Name Stmts Miss Cover |
| 222 ------------------------------- |
| 223 b_or_c.py 7 0 100% |
| 224 """)) |
| 225 |
| 226 def test_combine_with_aliases(self): |
| 227 self.make_file("d1/x.py", """\ |
| 228 a = 1 |
| 229 b = 2 |
| 230 print("%s %s" % (a, b)) |
| 231 """) |
| 232 |
| 233 self.make_file("d2/x.py", """\ |
| 234 # 1 |
| 235 # 2 |
| 236 # 3 |
| 237 c = 4 |
| 238 d = 5 |
| 239 print("%s %s" % (c, d)) |
| 240 """) |
| 241 |
| 242 self.make_file(".coveragerc", """\ |
| 243 [run] |
| 244 parallel = True |
| 245 |
| 246 [paths] |
| 247 source = |
| 248 src |
| 249 */d1 |
| 250 */d2 |
| 251 """) |
| 252 |
| 253 out = self.run_command("coverage run " + os.path.normpath("d1/x.py")) |
| 254 self.assertEqual(out, '1 2\n') |
| 255 out = self.run_command("coverage run " + os.path.normpath("d2/x.py")) |
| 256 self.assertEqual(out, '4 5\n') |
| 257 |
| 258 self.assertEqual(self.number_of_data_files(), 2) |
| 259 |
| 260 self.run_command("coverage combine") |
| 261 self.assert_exists(".coverage") |
| 262 |
| 263 # After combining, there should be only the .coverage file. |
| 264 self.assertEqual(self.number_of_data_files(), 1) |
| 265 |
| 266 # Read the coverage data file and see that the two different x.py |
| 267 # files have been combined together. |
| 268 data = coverage.CoverageData() |
| 269 data.read_file(".coverage") |
| 270 summary = data.line_counts(fullpath=True) |
| 271 self.assertEqual(len(summary), 1) |
| 272 actual = os.path.normcase(os.path.abspath(list(summary.keys())[0])) |
| 273 expected = os.path.normcase(os.path.abspath('src/x.py')) |
| 274 self.assertEqual(actual, expected) |
| 275 self.assertEqual(list(summary.values())[0], 6) |
| 276 |
| 277 def test_erase_parallel(self): |
| 278 self.make_file(".coveragerc", """\ |
| 279 [run] |
| 280 data_file = data.dat |
| 281 parallel = True |
| 282 """) |
| 283 self.make_file("data.dat") |
| 284 self.make_file("data.dat.fooey") |
| 285 self.make_file("data.dat.gooey") |
| 286 self.make_file(".coverage") |
| 287 |
| 288 self.run_command("coverage erase") |
| 289 self.assert_doesnt_exist("data.dat") |
| 290 self.assert_doesnt_exist("data.dat.fooey") |
| 291 self.assert_doesnt_exist("data.dat.gooey") |
| 292 self.assert_exists(".coverage") |
| 293 |
| 294 def test_missing_source_file(self): |
| 295 # Check what happens if the source is missing when reporting happens. |
| 296 self.make_file("fleeting.py", """\ |
| 297 s = 'goodbye, cruel world!' |
| 298 """) |
| 299 |
| 300 self.run_command("coverage run fleeting.py") |
| 301 os.remove("fleeting.py") |
| 302 out = self.run_command("coverage html -d htmlcov") |
| 303 self.assertRegex(out, "No source for code: '.*fleeting.py'") |
| 304 self.assertNotIn("Traceback", out) |
| 305 |
| 306 # It happens that the code paths are different for *.py and other |
| 307 # files, so try again with no extension. |
| 308 self.make_file("fleeting", """\ |
| 309 s = 'goodbye, cruel world!' |
| 310 """) |
| 311 |
| 312 self.run_command("coverage run fleeting") |
| 313 os.remove("fleeting") |
| 314 status, out = self.run_command_status("coverage html -d htmlcov") |
| 315 self.assertRegex(out, "No source for code: '.*fleeting'") |
| 316 self.assertNotIn("Traceback", out) |
| 317 self.assertEqual(status, 1) |
| 318 |
| 319 def test_running_missing_file(self): |
| 320 status, out = self.run_command_status("coverage run xyzzy.py") |
| 321 self.assertRegex(out, "No file to run: .*xyzzy.py") |
| 322 self.assertNotIn("raceback", out) |
| 323 self.assertNotIn("rror", out) |
| 324 self.assertEqual(status, 1) |
| 325 |
| 326 def test_code_throws(self): |
| 327 self.make_file("throw.py", """\ |
| 328 def f1(): |
| 329 raise Exception("hey!") |
| 330 |
| 331 def f2(): |
| 332 f1() |
| 333 |
| 334 f2() |
| 335 """) |
| 336 |
| 337 # The important thing is for "coverage run" and "python" to report the |
| 338 # same traceback. |
| 339 status, out = self.run_command_status("coverage run throw.py") |
| 340 out2 = self.run_command("python throw.py") |
| 341 if env.PYPY: |
| 342 # Pypy has an extra frame in the traceback for some reason |
| 343 lines2 = out2.splitlines() |
| 344 out2 = "".join(l+"\n" for l in lines2 if "toplevel" not in l) |
| 345 self.assertMultiLineEqual(out, out2) |
| 346 |
| 347 # But also make sure that the output is what we expect. |
| 348 self.assertIn('File "throw.py", line 5, in f2', out) |
| 349 self.assertIn('raise Exception("hey!")', out) |
| 350 self.assertNotIn('coverage', out) |
| 351 self.assertEqual(status, 1) |
| 352 |
| 353 def test_code_exits(self): |
| 354 self.make_file("exit.py", """\ |
| 355 import sys |
| 356 def f1(): |
| 357 print("about to exit..") |
| 358 sys.exit(17) |
| 359 |
| 360 def f2(): |
| 361 f1() |
| 362 |
| 363 f2() |
| 364 """) |
| 365 |
| 366 # The important thing is for "coverage run" and "python" to have the |
| 367 # same output. No traceback. |
| 368 status, out = self.run_command_status("coverage run exit.py") |
| 369 status2, out2 = self.run_command_status("python exit.py") |
| 370 self.assertMultiLineEqual(out, out2) |
| 371 self.assertMultiLineEqual(out, "about to exit..\n") |
| 372 self.assertEqual(status, status2) |
| 373 self.assertEqual(status, 17) |
| 374 |
| 375 def test_code_exits_no_arg(self): |
| 376 self.make_file("exit_none.py", """\ |
| 377 import sys |
| 378 def f1(): |
| 379 print("about to exit quietly..") |
| 380 sys.exit() |
| 381 |
| 382 f1() |
| 383 """) |
| 384 status, out = self.run_command_status("coverage run exit_none.py") |
| 385 status2, out2 = self.run_command_status("python exit_none.py") |
| 386 self.assertMultiLineEqual(out, out2) |
| 387 self.assertMultiLineEqual(out, "about to exit quietly..\n") |
| 388 self.assertEqual(status, status2) |
| 389 self.assertEqual(status, 0) |
| 390 |
| 391 def test_coverage_run_is_like_python(self): |
| 392 tryfile = os.path.join(HERE, "try_execfile.py") |
| 393 with open(tryfile) as f: |
| 394 self.make_file("run_me.py", f.read()) |
| 395 out_cov = self.run_command("coverage run run_me.py") |
| 396 out_py = self.run_command("python run_me.py") |
| 397 self.assertMultiLineEqual(out_cov, out_py) |
| 398 |
| 399 def test_coverage_run_dashm_is_like_python_dashm(self): |
| 400 # These -m commands assume the coverage tree is on the path. |
| 401 out_cov = self.run_command("coverage run -m tests.try_execfile") |
| 402 out_py = self.run_command("python -m tests.try_execfile") |
| 403 self.assertMultiLineEqual(out_cov, out_py) |
| 404 |
| 405 def test_coverage_run_dir_is_like_python_dir(self): |
| 406 tryfile = os.path.join(HERE, "try_execfile.py") |
| 407 with open(tryfile) as f: |
| 408 self.make_file("with_main/__main__.py", f.read()) |
| 409 out_cov = self.run_command("coverage run with_main") |
| 410 out_py = self.run_command("python with_main") |
| 411 |
| 412 # The coverage.py results are not identical to the Python results, and |
| 413 # I don't know why. For now, ignore those failures. If someone finds |
| 414 # a real problem with the discrepancies, we can work on it some more. |
| 415 ignored = r"__file__|__loader__|__package__" |
| 416 # PyPy includes the current directory in the path when running a |
| 417 # directory, while CPython and coverage.py do not. Exclude that from |
| 418 # the comparison also... |
| 419 if env.PYPY: |
| 420 ignored += "|"+re.escape(os.getcwd()) |
| 421 out_cov = remove_matching_lines(out_cov, ignored) |
| 422 out_py = remove_matching_lines(out_py, ignored) |
| 423 self.assertMultiLineEqual(out_cov, out_py) |
| 424 |
| 425 def test_coverage_run_dashm_equal_to_doubledashsource(self): |
| 426 """regression test for #328 |
| 427 |
| 428 When imported by -m, a module's __name__ is __main__, but we need the |
| 429 --source machinery to know and respect the original name. |
| 430 """ |
| 431 # These -m commands assume the coverage tree is on the path. |
| 432 out_cov = self.run_command( |
| 433 "coverage run --source tests.try_execfile -m tests.try_execfile" |
| 434 ) |
| 435 out_py = self.run_command("python -m tests.try_execfile") |
| 436 self.assertMultiLineEqual(out_cov, out_py) |
| 437 |
| 438 def test_coverage_run_dashm_superset_of_doubledashsource(self): |
| 439 """Edge case: --source foo -m foo.bar""" |
| 440 # These -m commands assume the coverage tree is on the path. |
| 441 out_cov = self.run_command( |
| 442 "coverage run --source tests -m tests.try_execfile" |
| 443 ) |
| 444 out_py = self.run_command("python -m tests.try_execfile") |
| 445 self.assertMultiLineEqual(out_cov, out_py) |
| 446 |
| 447 st, out = self.run_command_status("coverage report") |
| 448 self.assertEqual(st, 0) |
| 449 self.assertEqual(self.line_count(out), 6, out) |
| 450 |
| 451 def test_coverage_run_script_imports_doubledashsource(self): |
| 452 # This file imports try_execfile, which compiles it to .pyc, so the |
| 453 # first run will have __file__ == "try_execfile.py" and the second will |
| 454 # have __file__ == "try_execfile.pyc", which throws off the comparison. |
| 455 # Setting dont_write_bytecode True stops the compilation to .pyc and |
| 456 # keeps the test working. |
| 457 self.make_file("myscript", """\ |
| 458 import sys; sys.dont_write_bytecode = True |
| 459 import tests.try_execfile |
| 460 """) |
| 461 |
| 462 # These -m commands assume the coverage tree is on the path. |
| 463 out_cov = self.run_command( |
| 464 "coverage run --source tests myscript" |
| 465 ) |
| 466 out_py = self.run_command("python myscript") |
| 467 self.assertMultiLineEqual(out_cov, out_py) |
| 468 |
| 469 st, out = self.run_command_status("coverage report") |
| 470 self.assertEqual(st, 0) |
| 471 self.assertEqual(self.line_count(out), 6, out) |
| 472 |
| 473 def test_coverage_run_dashm_is_like_python_dashm_off_path(self): |
| 474 # https://bitbucket.org/ned/coveragepy/issue/242 |
| 475 tryfile = os.path.join(HERE, "try_execfile.py") |
| 476 self.make_file("sub/__init__.py", "") |
| 477 with open(tryfile) as f: |
| 478 self.make_file("sub/run_me.py", f.read()) |
| 479 out_cov = self.run_command("coverage run -m sub.run_me") |
| 480 out_py = self.run_command("python -m sub.run_me") |
| 481 self.assertMultiLineEqual(out_cov, out_py) |
| 482 |
| 483 def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self): |
| 484 if sys.version_info < (2, 7): |
| 485 # Coverage.py isn't bug-for-bug compatible in the behavior of -m for |
| 486 # Pythons < 2.7 |
| 487 self.skip("-m doesn't work the same < Python 2.7") |
| 488 # https://bitbucket.org/ned/coveragepy/issue/207 |
| 489 self.make_file("package/__init__.py", "print('init')") |
| 490 self.make_file("package/__main__.py", "print('main')") |
| 491 out_cov = self.run_command("coverage run -m package") |
| 492 out_py = self.run_command("python -m package") |
| 493 self.assertMultiLineEqual(out_cov, out_py) |
| 494 |
| 495 def test_fork(self): |
| 496 if not hasattr(os, 'fork'): |
| 497 self.skip("Can't test os.fork since it doesn't exist.") |
| 498 |
| 499 self.make_file("fork.py", """\ |
| 500 import os |
| 501 |
| 502 def child(): |
| 503 print('Child!') |
| 504 |
| 505 def main(): |
| 506 ret = os.fork() |
| 507 |
| 508 if ret == 0: |
| 509 child() |
| 510 else: |
| 511 os.waitpid(ret, 0) |
| 512 |
| 513 main() |
| 514 """) |
| 515 |
| 516 out = self.run_command("coverage run -p fork.py") |
| 517 self.assertEqual(out, 'Child!\n') |
| 518 self.assert_doesnt_exist(".coverage") |
| 519 |
| 520 # After running the forking program, there should be two |
| 521 # .coverage.machine.123 files. |
| 522 self.assertEqual(self.number_of_data_files(), 2) |
| 523 |
| 524 # Combine the parallel coverage data files into .coverage . |
| 525 self.run_command("coverage combine") |
| 526 self.assert_exists(".coverage") |
| 527 |
| 528 # After combining, there should be only the .coverage file. |
| 529 self.assertEqual(self.number_of_data_files(), 1) |
| 530 |
| 531 # Read the coverage file and see that b_or_c.py has all 7 lines |
| 532 # executed. |
| 533 data = coverage.CoverageData() |
| 534 data.read_file(".coverage") |
| 535 self.assertEqual(data.line_counts()['fork.py'], 9) |
| 536 |
| 537 def test_warnings(self): |
| 538 self.make_file("hello.py", """\ |
| 539 import sys, os |
| 540 print("Hello") |
| 541 """) |
| 542 out = self.run_command("coverage run --source=sys,xyzzy,quux hello.py") |
| 543 |
| 544 self.assertIn("Hello\n", out) |
| 545 self.assertIn(textwrap.dedent("""\ |
| 546 Coverage.py warning: Module sys has no Python source. |
| 547 Coverage.py warning: Module xyzzy was never imported. |
| 548 Coverage.py warning: Module quux was never imported. |
| 549 Coverage.py warning: No data was collected. |
| 550 """), out) |
| 551 |
| 552 def test_warnings_during_reporting(self): |
| 553 # While fixing issue #224, the warnings were being printed far too |
| 554 # often. Make sure they're not any more. |
| 555 self.make_file("hello.py", """\ |
| 556 import sys, os, the_other |
| 557 print("Hello") |
| 558 """) |
| 559 self.make_file("the_other.py", """\ |
| 560 print("What?") |
| 561 """) |
| 562 self.make_file(".coveragerc", """\ |
| 563 [run] |
| 564 source = |
| 565 . |
| 566 xyzzy |
| 567 """) |
| 568 |
| 569 self.run_command("coverage run hello.py") |
| 570 out = self.run_command("coverage html") |
| 571 self.assertEqual(out.count("Module xyzzy was never imported."), 0) |
| 572 |
| 573 def test_warnings_if_never_run(self): |
| 574 out = self.run_command("coverage run i_dont_exist.py") |
| 575 self.assertIn("No file to run: 'i_dont_exist.py'", out) |
| 576 self.assertNotIn("warning", out) |
| 577 self.assertNotIn("Exception", out) |
| 578 |
| 579 out = self.run_command("coverage run -m no_such_module") |
| 580 self.assertTrue( |
| 581 ("No module named no_such_module" in out) or |
| 582 ("No module named 'no_such_module'" in out) |
| 583 ) |
| 584 self.assertNotIn("warning", out) |
| 585 self.assertNotIn("Exception", out) |
| 586 |
| 587 def test_warnings_trace_function_changed_with_threads(self): |
| 588 # https://bitbucket.org/ned/coveragepy/issue/164 |
| 589 self.make_file("bug164.py", """\ |
| 590 import threading |
| 591 import time |
| 592 |
| 593 class MyThread (threading.Thread): |
| 594 def run(self): |
| 595 print("Hello") |
| 596 |
| 597 thr = MyThread() |
| 598 thr.start() |
| 599 thr.join() |
| 600 """) |
| 601 out = self.run_command("coverage run --timid bug164.py") |
| 602 |
| 603 self.assertIn("Hello\n", out) |
| 604 self.assertNotIn("warning", out) |
| 605 |
| 606 def test_warning_trace_function_changed(self): |
| 607 self.make_file("settrace.py", """\ |
| 608 import sys |
| 609 print("Hello") |
| 610 sys.settrace(None) |
| 611 print("Goodbye") |
| 612 """) |
| 613 out = self.run_command("coverage run --timid settrace.py") |
| 614 self.assertIn("Hello\n", out) |
| 615 self.assertIn("Goodbye\n", out) |
| 616 |
| 617 self.assertIn("Trace function changed", out) |
| 618 |
| 619 def test_note(self): |
| 620 self.make_file(".coveragerc", """\ |
| 621 [run] |
| 622 data_file = mydata.dat |
| 623 note = These are musical notes: ♫𝅗𝅥♩ |
| 624 """) |
| 625 self.make_file("simple.py", """print('hello')""") |
| 626 self.run_command("coverage run simple.py") |
| 627 |
| 628 data = CoverageData() |
| 629 data.read_file("mydata.dat") |
| 630 infos = data.run_infos() |
| 631 self.assertEqual(len(infos), 1) |
| 632 self.assertEqual(infos[0]['note'], u"These are musical notes: ♫𝅗𝅥♩") |
| 633 |
| 634 def test_fullcoverage(self): # pragma: not covered |
| 635 if env.PY2: # This doesn't work on Python 2. |
| 636 self.skip("fullcoverage doesn't work on Python 2.") |
| 637 # It only works with the C tracer, and if we aren't measuring ourselves. |
| 638 if not env.C_TRACER or env.METACOV: |
| 639 self.skip("fullcoverage only works with the C tracer.") |
| 640 |
| 641 # fullcoverage is a trick to get stdlib modules measured from |
| 642 # the very beginning of the process. Here we import os and |
| 643 # then check how many lines are measured. |
| 644 self.make_file("getenv.py", """\ |
| 645 import os |
| 646 print("FOOEY == %s" % os.getenv("FOOEY")) |
| 647 """) |
| 648 |
| 649 fullcov = os.path.join( |
| 650 os.path.dirname(coverage.__file__), "fullcoverage" |
| 651 ) |
| 652 self.set_environ("FOOEY", "BOO") |
| 653 self.set_environ("PYTHONPATH", fullcov) |
| 654 out = self.run_command("python -m coverage run -L getenv.py") |
| 655 self.assertEqual(out, "FOOEY == BOO\n") |
| 656 data = coverage.CoverageData() |
| 657 data.read_file(".coverage") |
| 658 # The actual number of executed lines in os.py when it's |
| 659 # imported is 120 or so. Just running os.getenv executes |
| 660 # about 5. |
| 661 self.assertGreater(data.line_counts()['os.py'], 50) |
| 662 |
| 663 def test_deprecation_warnings(self): |
| 664 # Test that coverage doesn't trigger deprecation warnings. |
| 665 # https://bitbucket.org/ned/coveragepy/issue/305/pendingdeprecationwarni
ng-the-imp-module |
| 666 self.make_file("allok.py", """\ |
| 667 import warnings |
| 668 warnings.simplefilter('default') |
| 669 import coverage |
| 670 print("No warnings!") |
| 671 """) |
| 672 out = self.run_command("python allok.py") |
| 673 self.assertEqual(out, "No warnings!\n") |
| 674 |
| 675 def test_run_twice(self): |
| 676 # https://bitbucket.org/ned/coveragepy/issue/353/40a3-introduces-an-unex
pected-third-case |
| 677 self.make_file("foo.py", """\ |
| 678 def foo(): |
| 679 pass |
| 680 """) |
| 681 self.make_file("run_twice.py", """\ |
| 682 import coverage |
| 683 |
| 684 for _ in [1, 2]: |
| 685 inst = coverage.Coverage(source=['foo']) |
| 686 inst.load() |
| 687 inst.start() |
| 688 import foo |
| 689 inst.stop() |
| 690 inst.combine() |
| 691 inst.save() |
| 692 """) |
| 693 out = self.run_command("python run_twice.py") |
| 694 self.assertEqual( |
| 695 out, |
| 696 "Coverage.py warning: Module foo was previously imported, but not me
asured.\n" |
| 697 ) |
| 698 |
| 699 |
| 700 class AliasedCommandTest(CoverageTest): |
| 701 """Tests of the version-specific command aliases.""" |
| 702 |
| 703 run_in_temp_dir = False |
| 704 |
| 705 def test_major_version_works(self): |
| 706 # "coverage2" works on py2 |
| 707 cmd = "coverage%d" % sys.version_info[0] |
| 708 out = self.run_command(cmd) |
| 709 self.assertIn("Code coverage for Python", out) |
| 710 |
| 711 def test_wrong_alias_doesnt_work(self): |
| 712 # "coverage3" doesn't work on py2 |
| 713 badcmd = "coverage%d" % (5 - sys.version_info[0]) |
| 714 out = self.run_command(badcmd) |
| 715 self.assertNotIn("Code coverage for Python", out) |
| 716 |
| 717 def test_specific_alias_works(self): |
| 718 # "coverage-2.7" works on py2.7 |
| 719 cmd = "coverage-%d.%d" % sys.version_info[:2] |
| 720 out = self.run_command(cmd) |
| 721 self.assertIn("Code coverage for Python", out) |
| 722 |
| 723 |
| 724 class PydocTest(CoverageTest): |
| 725 """Test that pydoc can get our information.""" |
| 726 |
| 727 run_in_temp_dir = False |
| 728 |
| 729 def assert_pydoc_ok(self, name, thing): |
| 730 """Check that pydoc of `name` finds the docstring from `thing`.""" |
| 731 # Run pydoc. |
| 732 out = self.run_command("python -m pydoc " + name) |
| 733 # It should say "Help on..", and not have a traceback |
| 734 self.assert_starts_with(out, "Help on ") |
| 735 self.assertNotIn("Traceback", out) |
| 736 |
| 737 # All of the lines in the docstring should be there somewhere. |
| 738 for line in thing.__doc__.splitlines(): |
| 739 self.assertIn(line.strip(), out) |
| 740 |
| 741 def test_pydoc_coverage(self): |
| 742 self.assert_pydoc_ok("coverage", coverage) |
| 743 |
| 744 def test_pydoc_coverage_coverage(self): |
| 745 self.assert_pydoc_ok("coverage.Coverage", coverage.Coverage) |
| 746 |
| 747 |
| 748 class FailUnderTest(CoverageTest): |
| 749 """Tests of the --fail-under switch.""" |
| 750 |
| 751 def setUp(self): |
| 752 super(FailUnderTest, self).setUp() |
| 753 self.make_file("forty_two_plus.py", """\ |
| 754 # I have 42.857% (3/7) coverage! |
| 755 a = 1 |
| 756 b = 2 |
| 757 if a > 3: |
| 758 b = 4 |
| 759 c = 5 |
| 760 d = 6 |
| 761 e = 7 |
| 762 """) |
| 763 st, _ = self.run_command_status("coverage run forty_two_plus.py") |
| 764 self.assertEqual(st, 0) |
| 765 st, out = self.run_command_status("coverage report") |
| 766 self.assertEqual(st, 0) |
| 767 self.assertEqual( |
| 768 self.last_line_squeezed(out), |
| 769 "forty_two_plus.py 7 4 43%" |
| 770 ) |
| 771 |
| 772 def test_report(self): |
| 773 st, _ = self.run_command_status("coverage report --fail-under=42") |
| 774 self.assertEqual(st, 0) |
| 775 st, _ = self.run_command_status("coverage report --fail-under=43") |
| 776 self.assertEqual(st, 0) |
| 777 st, _ = self.run_command_status("coverage report --fail-under=44") |
| 778 self.assertEqual(st, 2) |
| 779 |
| 780 def test_html_report(self): |
| 781 st, _ = self.run_command_status("coverage html --fail-under=42") |
| 782 self.assertEqual(st, 0) |
| 783 st, _ = self.run_command_status("coverage html --fail-under=43") |
| 784 self.assertEqual(st, 0) |
| 785 st, _ = self.run_command_status("coverage html --fail-under=44") |
| 786 self.assertEqual(st, 2) |
| 787 |
| 788 def test_xml_report(self): |
| 789 st, _ = self.run_command_status("coverage xml --fail-under=42") |
| 790 self.assertEqual(st, 0) |
| 791 st, _ = self.run_command_status("coverage xml --fail-under=43") |
| 792 self.assertEqual(st, 0) |
| 793 st, _ = self.run_command_status("coverage xml --fail-under=44") |
| 794 self.assertEqual(st, 2) |
| 795 |
| 796 def test_fail_under_in_config(self): |
| 797 self.make_file(".coveragerc", "[report]\nfail_under = 43\n") |
| 798 st, _ = self.run_command_status("coverage report") |
| 799 self.assertEqual(st, 0) |
| 800 |
| 801 self.make_file(".coveragerc", "[report]\nfail_under = 44\n") |
| 802 st, _ = self.run_command_status("coverage report") |
| 803 self.assertEqual(st, 2) |
| 804 |
| 805 |
| 806 class FailUnderNoFilesTest(CoverageTest): |
| 807 """Test that nothing to report results in an error exit status.""" |
| 808 def setUp(self): |
| 809 super(FailUnderNoFilesTest, self).setUp() |
| 810 self.make_file(".coveragerc", "[report]\nfail_under = 99\n") |
| 811 |
| 812 def test_report(self): |
| 813 st, out = self.run_command_status("coverage report") |
| 814 self.assertIn('No data to report.', out) |
| 815 self.assertEqual(st, 1) |
| 816 |
| 817 def test_xml(self): |
| 818 st, out = self.run_command_status("coverage xml") |
| 819 self.assertIn('No data to report.', out) |
| 820 self.assertEqual(st, 1) |
| 821 |
| 822 def test_html(self): |
| 823 st, out = self.run_command_status("coverage html") |
| 824 self.assertIn('No data to report.', out) |
| 825 self.assertEqual(st, 1) |
| 826 |
| 827 |
| 828 class FailUnderEmptyFilesTest(CoverageTest): |
| 829 """Test that empty files produce the proper fail_under exit status.""" |
| 830 def setUp(self): |
| 831 super(FailUnderEmptyFilesTest, self).setUp() |
| 832 |
| 833 self.make_file(".coveragerc", "[report]\nfail_under = 99\n") |
| 834 self.make_file("empty.py", "") |
| 835 st, _ = self.run_command_status("coverage run empty.py") |
| 836 self.assertEqual(st, 0) |
| 837 |
| 838 def test_report(self): |
| 839 st, _ = self.run_command_status("coverage report") |
| 840 self.assertEqual(st, 2) |
| 841 |
| 842 def test_xml(self): |
| 843 st, _ = self.run_command_status("coverage xml") |
| 844 self.assertEqual(st, 2) |
| 845 |
| 846 def test_html(self): |
| 847 st, _ = self.run_command_status("coverage html") |
| 848 self.assertEqual(st, 2) |
| 849 |
| 850 |
| 851 def possible_pth_dirs(): |
| 852 """Produce a sequence of directories for trying to write .pth files.""" |
| 853 # First look through sys.path, and we find a .pth file, then it's a good |
| 854 # place to put ours. |
| 855 for d in sys.path: |
| 856 g = glob.glob(os.path.join(d, "*.pth")) |
| 857 if g: |
| 858 yield d |
| 859 |
| 860 # If we're still looking, then try the Python library directory. |
| 861 # https://bitbucket.org/ned/coveragepy/issue/339/pth-test-malfunctions |
| 862 import distutils.sysconfig # pylint: disable=import-error |
| 863 yield distutils.sysconfig.get_python_lib() |
| 864 |
| 865 |
| 866 class ProcessCoverageMixin(object): |
| 867 """Set up a .pth file to coverage-measure all sub-processes.""" |
| 868 |
| 869 def setUp(self): |
| 870 super(ProcessCoverageMixin, self).setUp() |
| 871 # Find a place to put a .pth file. |
| 872 pth_contents = "import coverage; coverage.process_startup()\n" |
| 873 for pth_dir in possible_pth_dirs(): # pragma: part covered |
| 874 pth_path = os.path.join(pth_dir, "subcover.pth") |
| 875 with open(pth_path, "w") as pth: |
| 876 try: |
| 877 pth.write(pth_contents) |
| 878 self.pth_path = pth_path |
| 879 break |
| 880 except (IOError, OSError): # pragma: not covered |
| 881 pass |
| 882 else: # pragma: not covered |
| 883 raise Exception("Couldn't find a place for the .pth file") |
| 884 |
| 885 self.addCleanup(os.remove, self.pth_path) |
| 886 |
| 887 |
| 888 class ProcessStartupTest(ProcessCoverageMixin, CoverageTest): |
| 889 """Test that we can measure coverage in sub-processes.""" |
| 890 |
| 891 def test_subprocess_with_pth_files(self): # pragma: not covered |
| 892 if env.METACOV: |
| 893 self.skip("Can't test sub-process pth file suppport during metacover
age") |
| 894 |
| 895 # Main will run sub.py |
| 896 self.make_file("main.py", """\ |
| 897 import os, os.path, sys |
| 898 ex = os.path.basename(sys.executable) |
| 899 os.system(ex + " sub.py") |
| 900 """) |
| 901 # sub.py will write a few lines. |
| 902 self.make_file("sub.py", """\ |
| 903 with open("out.txt", "w") as f: |
| 904 f.write("Hello, world!\\n") |
| 905 """) |
| 906 self.make_file("coverage.ini", """\ |
| 907 [run] |
| 908 data_file = .mycovdata |
| 909 """) |
| 910 self.set_environ("COVERAGE_PROCESS_START", "coverage.ini") |
| 911 import main # pylint: disable=import-error,unused-variable |
| 912 |
| 913 with open("out.txt") as f: |
| 914 self.assertEqual(f.read(), "Hello, world!\n") |
| 915 |
| 916 # Read the data from .coverage |
| 917 self.assert_exists(".mycovdata") |
| 918 data = coverage.CoverageData() |
| 919 data.read_file(".mycovdata") |
| 920 self.assertEqual(data.line_counts()['sub.py'], 2) |
| 921 |
| 922 |
| 923 class ProcessStartupWithSourceTest(ProcessCoverageMixin, CoverageTest): |
| 924 """Show that we can configure {[run]source} during process-level coverage. |
| 925 |
| 926 There are three interesting variables, for a total of eight tests: |
| 927 |
| 928 1. -m versus a simple script argument (for example, `python myscript`), |
| 929 |
| 930 2. filtering for the top-level (main.py) or second-level (sub.py) |
| 931 module, and |
| 932 |
| 933 3. whether the files are in a package or not. |
| 934 |
| 935 """ |
| 936 |
| 937 def assert_pth_and_source_work_together( |
| 938 self, dashm, package, source |
| 939 ): # pragma: not covered |
| 940 """Run the test for a particular combination of factors. |
| 941 |
| 942 The arguments are all strings: |
| 943 |
| 944 * `dashm`: Either "" (run the program as a file) or "-m" (run the |
| 945 program as a module). |
| 946 |
| 947 * `package`: Either "" (put the source at the top level) or a |
| 948 package name to use to hold the source. |
| 949 |
| 950 * `source`: Either "main" or "sub", which file to use as the |
| 951 ``--source`` argument. |
| 952 |
| 953 """ |
| 954 if env.METACOV: |
| 955 self.skip("Can't test sub-process pth file suppport during metacover
age") |
| 956 |
| 957 def fullname(modname): |
| 958 """What is the full module name for `modname` for this test?""" |
| 959 if package and dashm: |
| 960 return '.'.join((package, modname)) |
| 961 else: |
| 962 return modname |
| 963 |
| 964 def path(basename): |
| 965 """Where should `basename` be created for this test?""" |
| 966 return os.path.join(package, basename) |
| 967 |
| 968 # Main will run sub.py. |
| 969 self.make_file(path("main.py"), """\ |
| 970 import %s |
| 971 if True: pass |
| 972 """ % fullname('sub')) |
| 973 if package: |
| 974 self.make_file(path("__init__.py"), "") |
| 975 # sub.py will write a few lines. |
| 976 self.make_file(path("sub.py"), """\ |
| 977 with open("out.txt", "w") as f: |
| 978 f.write("Hello, world!") |
| 979 """) |
| 980 self.make_file("coverage.ini", """\ |
| 981 [run] |
| 982 source = %s |
| 983 """ % fullname(source)) |
| 984 |
| 985 self.set_environ("COVERAGE_PROCESS_START", "coverage.ini") |
| 986 |
| 987 if dashm: |
| 988 cmd = "python -m %s" % fullname('main') |
| 989 else: |
| 990 cmd = "python %s" % path('main.py') |
| 991 |
| 992 self.run_command(cmd) |
| 993 |
| 994 with open("out.txt") as f: |
| 995 self.assertEqual(f.read(), "Hello, world!") |
| 996 |
| 997 # Read the data from .coverage |
| 998 self.assert_exists(".coverage") |
| 999 data = coverage.CoverageData() |
| 1000 data.read_file(".coverage") |
| 1001 summary = data.line_counts() |
| 1002 print(summary) |
| 1003 self.assertEqual(summary[source + '.py'], 2) |
| 1004 self.assertEqual(len(summary), 1) |
| 1005 |
| 1006 def test_dashm_main(self): |
| 1007 self.assert_pth_and_source_work_together('-m', '', 'main') |
| 1008 |
| 1009 def test_script_main(self): |
| 1010 self.assert_pth_and_source_work_together('', '', 'main') |
| 1011 |
| 1012 def test_dashm_sub(self): |
| 1013 self.assert_pth_and_source_work_together('-m', '', 'sub') |
| 1014 |
| 1015 def test_script_sub(self): |
| 1016 self.assert_pth_and_source_work_together('', '', 'sub') |
| 1017 |
| 1018 def test_dashm_pkg_main(self): |
| 1019 self.assert_pth_and_source_work_together('-m', 'pkg', 'main') |
| 1020 |
| 1021 def test_script_pkg_main(self): |
| 1022 self.assert_pth_and_source_work_together('', 'pkg', 'main') |
| 1023 |
| 1024 def test_dashm_pkg_sub(self): |
| 1025 self.assert_pth_and_source_work_together('-m', 'pkg', 'sub') |
| 1026 |
| 1027 def test_script_pkg_sub(self): |
| 1028 self.assert_pth_and_source_work_together('', 'pkg', 'sub') |
| 1029 |
| 1030 |
| 1031 def remove_matching_lines(text, pat): |
| 1032 """Return `text` with all lines matching `pat` removed.""" |
| 1033 lines = [l for l in text.splitlines(True) if not re.search(pat, l)] |
| 1034 return "".join(lines) |
OLD | NEW |