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 plugins.""" |
| 5 |
| 6 import os.path |
| 7 |
| 8 import coverage |
| 9 from coverage import env |
| 10 from coverage.backward import StringIO |
| 11 from coverage.control import Plugins |
| 12 from coverage.misc import CoverageException |
| 13 |
| 14 import coverage.plugin |
| 15 |
| 16 from tests.coveragetest import CoverageTest |
| 17 from tests.helpers import CheckUniqueFilenames |
| 18 |
| 19 |
| 20 class FakeConfig(object): |
| 21 """A fake config for use in tests.""" |
| 22 |
| 23 def __init__(self, plugin, options): |
| 24 self.plugin = plugin |
| 25 self.options = options |
| 26 self.asked_for = [] |
| 27 |
| 28 def get_plugin_options(self, module): |
| 29 """Just return the options for `module` if this is the right module.""" |
| 30 self.asked_for.append(module) |
| 31 if module == self.plugin: |
| 32 return self.options |
| 33 else: |
| 34 return {} |
| 35 |
| 36 |
| 37 class LoadPluginsTest(CoverageTest): |
| 38 """Test Plugins.load_plugins directly.""" |
| 39 |
| 40 def test_implicit_boolean(self): |
| 41 self.make_file("plugin1.py", """\ |
| 42 from coverage import CoveragePlugin |
| 43 |
| 44 class Plugin(CoveragePlugin): |
| 45 pass |
| 46 |
| 47 def coverage_init(reg, options): |
| 48 reg.add_file_tracer(Plugin()) |
| 49 """) |
| 50 |
| 51 config = FakeConfig("plugin1", {}) |
| 52 plugins = Plugins.load_plugins([], config) |
| 53 self.assertFalse(plugins) |
| 54 |
| 55 plugins = Plugins.load_plugins(["plugin1"], config) |
| 56 self.assertTrue(plugins) |
| 57 |
| 58 def test_importing_and_configuring(self): |
| 59 self.make_file("plugin1.py", """\ |
| 60 from coverage import CoveragePlugin |
| 61 |
| 62 class Plugin(CoveragePlugin): |
| 63 def __init__(self, options): |
| 64 self.options = options |
| 65 self.this_is = "me" |
| 66 |
| 67 def coverage_init(reg, options): |
| 68 reg.add_file_tracer(Plugin(options)) |
| 69 """) |
| 70 |
| 71 config = FakeConfig("plugin1", {'a': 'hello'}) |
| 72 plugins = list(Plugins.load_plugins(["plugin1"], config)) |
| 73 |
| 74 self.assertEqual(len(plugins), 1) |
| 75 self.assertEqual(plugins[0].this_is, "me") |
| 76 self.assertEqual(plugins[0].options, {'a': 'hello'}) |
| 77 self.assertEqual(config.asked_for, ['plugin1']) |
| 78 |
| 79 def test_importing_and_configuring_more_than_one(self): |
| 80 self.make_file("plugin1.py", """\ |
| 81 from coverage import CoveragePlugin |
| 82 |
| 83 class Plugin(CoveragePlugin): |
| 84 def __init__(self, options): |
| 85 self.options = options |
| 86 self.this_is = "me" |
| 87 |
| 88 def coverage_init(reg, options): |
| 89 reg.add_file_tracer(Plugin(options)) |
| 90 """) |
| 91 self.make_file("plugin2.py", """\ |
| 92 from coverage import CoveragePlugin |
| 93 |
| 94 class Plugin(CoveragePlugin): |
| 95 def __init__(self, options): |
| 96 self.options = options |
| 97 |
| 98 def coverage_init(reg, options): |
| 99 reg.add_file_tracer(Plugin(options)) |
| 100 """) |
| 101 |
| 102 config = FakeConfig("plugin1", {'a': 'hello'}) |
| 103 plugins = list(Plugins.load_plugins(["plugin1", "plugin2"], config)) |
| 104 |
| 105 self.assertEqual(len(plugins), 2) |
| 106 self.assertEqual(plugins[0].this_is, "me") |
| 107 self.assertEqual(plugins[0].options, {'a': 'hello'}) |
| 108 self.assertEqual(plugins[1].options, {}) |
| 109 self.assertEqual(config.asked_for, ['plugin1', 'plugin2']) |
| 110 |
| 111 # The order matters... |
| 112 config = FakeConfig("plugin1", {'a': 'second'}) |
| 113 plugins = list(Plugins.load_plugins(["plugin2", "plugin1"], config)) |
| 114 |
| 115 self.assertEqual(len(plugins), 2) |
| 116 self.assertEqual(plugins[0].options, {}) |
| 117 self.assertEqual(plugins[1].this_is, "me") |
| 118 self.assertEqual(plugins[1].options, {'a': 'second'}) |
| 119 |
| 120 def test_cant_import(self): |
| 121 with self.assertRaises(ImportError): |
| 122 _ = Plugins.load_plugins(["plugin_not_there"], None) |
| 123 |
| 124 def test_plugin_must_define_coverage_init(self): |
| 125 self.make_file("no_plugin.py", """\ |
| 126 from coverage import CoveragePlugin |
| 127 Nothing = 0 |
| 128 """) |
| 129 msg_pat = "Plugin module 'no_plugin' didn't define a coverage_init funct
ion" |
| 130 with self.assertRaisesRegex(CoverageException, msg_pat): |
| 131 list(Plugins.load_plugins(["no_plugin"], None)) |
| 132 |
| 133 |
| 134 class PluginTest(CoverageTest): |
| 135 """Test plugins through the Coverage class.""" |
| 136 |
| 137 def test_plugin_imported(self): |
| 138 # Prove that a plugin will be imported. |
| 139 self.make_file("my_plugin.py", """\ |
| 140 from coverage import CoveragePlugin |
| 141 class Plugin(CoveragePlugin): |
| 142 pass |
| 143 def coverage_init(reg, options): |
| 144 reg.add_noop(Plugin()) |
| 145 with open("evidence.out", "w") as f: |
| 146 f.write("we are here!") |
| 147 """) |
| 148 |
| 149 self.assert_doesnt_exist("evidence.out") |
| 150 cov = coverage.Coverage() |
| 151 cov.set_option("run:plugins", ["my_plugin"]) |
| 152 cov.start() |
| 153 cov.stop() # pragma: nested |
| 154 |
| 155 with open("evidence.out") as f: |
| 156 self.assertEqual(f.read(), "we are here!") |
| 157 |
| 158 def test_missing_plugin_raises_import_error(self): |
| 159 # Prove that a missing plugin will raise an ImportError. |
| 160 with self.assertRaises(ImportError): |
| 161 cov = coverage.Coverage() |
| 162 cov.set_option("run:plugins", ["does_not_exist_woijwoicweo"]) |
| 163 cov.start() |
| 164 cov.stop() |
| 165 |
| 166 def test_bad_plugin_isnt_hidden(self): |
| 167 # Prove that a plugin with an error in it will raise the error. |
| 168 self.make_file("plugin_over_zero.py", """\ |
| 169 1/0 |
| 170 """) |
| 171 with self.assertRaises(ZeroDivisionError): |
| 172 cov = coverage.Coverage() |
| 173 cov.set_option("run:plugins", ["plugin_over_zero"]) |
| 174 cov.start() |
| 175 cov.stop() |
| 176 |
| 177 def test_plugin_sys_info(self): |
| 178 self.make_file("plugin_sys_info.py", """\ |
| 179 import coverage |
| 180 |
| 181 class Plugin(coverage.CoveragePlugin): |
| 182 def sys_info(self): |
| 183 return [("hello", "world")] |
| 184 |
| 185 def coverage_init(reg, options): |
| 186 reg.add_noop(Plugin()) |
| 187 """) |
| 188 debug_out = StringIO() |
| 189 cov = coverage.Coverage(debug=["sys"]) |
| 190 cov._debug_file = debug_out |
| 191 cov.set_option("run:plugins", ["plugin_sys_info"]) |
| 192 cov.load() |
| 193 |
| 194 out_lines = debug_out.getvalue().splitlines() |
| 195 expected_end = [ |
| 196 "-- sys: plugin_sys_info.Plugin -------------------------------", |
| 197 " hello: world", |
| 198 "-- end -------------------------------------------------------", |
| 199 ] |
| 200 self.assertEqual(expected_end, out_lines[-len(expected_end):]) |
| 201 |
| 202 def test_plugin_with_no_sys_info(self): |
| 203 self.make_file("plugin_no_sys_info.py", """\ |
| 204 import coverage |
| 205 |
| 206 class Plugin(coverage.CoveragePlugin): |
| 207 pass |
| 208 |
| 209 def coverage_init(reg, options): |
| 210 reg.add_noop(Plugin()) |
| 211 """) |
| 212 debug_out = StringIO() |
| 213 cov = coverage.Coverage(debug=["sys"]) |
| 214 cov._debug_file = debug_out |
| 215 cov.set_option("run:plugins", ["plugin_no_sys_info"]) |
| 216 cov.load() |
| 217 |
| 218 out_lines = debug_out.getvalue().splitlines() |
| 219 expected_end = [ |
| 220 "-- sys: plugin_no_sys_info.Plugin ----------------------------", |
| 221 "-- end -------------------------------------------------------", |
| 222 ] |
| 223 self.assertEqual(expected_end, out_lines[-len(expected_end):]) |
| 224 |
| 225 def test_local_files_are_importable(self): |
| 226 self.make_file("importing_plugin.py", """\ |
| 227 from coverage import CoveragePlugin |
| 228 import local_module |
| 229 class MyPlugin(CoveragePlugin): |
| 230 pass |
| 231 def coverage_init(reg, options): |
| 232 reg.add_noop(MyPlugin()) |
| 233 """) |
| 234 self.make_file("local_module.py", "CONST = 1") |
| 235 self.make_file(".coveragerc", """\ |
| 236 [run] |
| 237 plugins = importing_plugin |
| 238 """) |
| 239 self.make_file("main_file.py", "print('MAIN')") |
| 240 |
| 241 out = self.run_command("coverage run main_file.py") |
| 242 self.assertEqual(out, "MAIN\n") |
| 243 out = self.run_command("coverage html") |
| 244 self.assertEqual(out, "") |
| 245 |
| 246 |
| 247 class PluginWarningOnPyTracer(CoverageTest): |
| 248 """Test that we get a controlled exception with plugins on PyTracer.""" |
| 249 def test_exception_if_plugins_on_pytracer(self): |
| 250 if env.C_TRACER: |
| 251 self.skip("This test is only about PyTracer.") |
| 252 |
| 253 self.make_file("simple.py", """a = 1""") |
| 254 |
| 255 cov = coverage.Coverage() |
| 256 cov.set_option("run:plugins", ["tests.plugin1"]) |
| 257 |
| 258 warnings = [] |
| 259 def capture_warning(msg): |
| 260 """A fake implementation of Coverage._warn, to capture warnings.""" |
| 261 warnings.append(msg) |
| 262 cov._warn = capture_warning |
| 263 |
| 264 self.start_import_stop(cov, "simple") |
| 265 self.assertIn( |
| 266 "Plugin file tracers (tests.plugin1.Plugin) aren't supported with Py
Tracer", |
| 267 warnings |
| 268 ) |
| 269 |
| 270 |
| 271 class FileTracerTest(CoverageTest): |
| 272 """Tests of plugins that implement file_tracer.""" |
| 273 |
| 274 def setUp(self): |
| 275 super(FileTracerTest, self).setUp() |
| 276 if not env.C_TRACER: |
| 277 self.skip("Plugins are only supported with the C tracer.") |
| 278 |
| 279 |
| 280 class GoodPluginTest(FileTracerTest): |
| 281 """Tests of plugin happy paths.""" |
| 282 |
| 283 def test_plugin1(self): |
| 284 self.make_file("simple.py", """\ |
| 285 import try_xyz |
| 286 a = 1 |
| 287 b = 2 |
| 288 """) |
| 289 self.make_file("try_xyz.py", """\ |
| 290 c = 3 |
| 291 d = 4 |
| 292 """) |
| 293 |
| 294 cov = coverage.Coverage() |
| 295 CheckUniqueFilenames.hook(cov, '_should_trace') |
| 296 CheckUniqueFilenames.hook(cov, '_check_include_omit_etc') |
| 297 cov.set_option("run:plugins", ["tests.plugin1"]) |
| 298 |
| 299 # Import the Python file, executing it. |
| 300 self.start_import_stop(cov, "simple") |
| 301 |
| 302 _, statements, missing, _ = cov.analysis("simple.py") |
| 303 self.assertEqual(statements, [1, 2, 3]) |
| 304 self.assertEqual(missing, []) |
| 305 zzfile = os.path.abspath(os.path.join("/src", "try_ABC.zz")) |
| 306 _, statements, _, _ = cov.analysis(zzfile) |
| 307 self.assertEqual(statements, [105, 106, 107, 205, 206, 207]) |
| 308 |
| 309 def make_render_and_caller(self): |
| 310 """Make the render.py and caller.py files we need.""" |
| 311 # plugin2 emulates a dynamic tracing plugin: the caller's locals |
| 312 # are examined to determine the source file and line number. |
| 313 # The plugin is in tests/plugin2.py. |
| 314 self.make_file("render.py", """\ |
| 315 def render(filename, linenum): |
| 316 # This function emulates a template renderer. The plugin |
| 317 # will examine the `filename` and `linenum` locals to |
| 318 # determine the source file and line number. |
| 319 fiddle_around = 1 # not used, just chaff. |
| 320 return "[{0} @ {1}]".format(filename, linenum) |
| 321 |
| 322 def helper(x): |
| 323 # This function is here just to show that not all code in |
| 324 # this file will be part of the dynamic tracing. |
| 325 return x+1 |
| 326 """) |
| 327 self.make_file("caller.py", """\ |
| 328 import sys |
| 329 from render import helper, render |
| 330 |
| 331 assert render("foo_7.html", 4) == "[foo_7.html @ 4]" |
| 332 # Render foo_7.html again to try the CheckUniqueFilenames asserts. |
| 333 render("foo_7.html", 4) |
| 334 |
| 335 assert helper(42) == 43 |
| 336 assert render("bar_4.html", 2) == "[bar_4.html @ 2]" |
| 337 assert helper(76) == 77 |
| 338 |
| 339 # quux_5.html will be omitted from the results. |
| 340 assert render("quux_5.html", 3) == "[quux_5.html @ 3]" |
| 341 |
| 342 # In Python 2, either kind of string should be OK. |
| 343 if sys.version_info[0] == 2: |
| 344 assert render(u"uni_3.html", 2) == "[uni_3.html @ 2]" |
| 345 """) |
| 346 |
| 347 # will try to read the actual source files, so make some |
| 348 # source files. |
| 349 def lines(n): |
| 350 """Make a string with n lines of text.""" |
| 351 return "".join("line %d\n" % i for i in range(n)) |
| 352 |
| 353 self.make_file("bar_4.html", lines(4)) |
| 354 self.make_file("foo_7.html", lines(7)) |
| 355 |
| 356 def test_plugin2(self): |
| 357 self.make_render_and_caller() |
| 358 |
| 359 cov = coverage.Coverage(omit=["*quux*"]) |
| 360 CheckUniqueFilenames.hook(cov, '_should_trace') |
| 361 CheckUniqueFilenames.hook(cov, '_check_include_omit_etc') |
| 362 cov.set_option("run:plugins", ["tests.plugin2"]) |
| 363 |
| 364 self.start_import_stop(cov, "caller") |
| 365 |
| 366 # The way plugin2 works, a file named foo_7.html will be claimed to |
| 367 # have 7 lines in it. If render() was called with line number 4, |
| 368 # then the plugin will claim that lines 4 and 5 were executed. |
| 369 _, statements, missing, _ = cov.analysis("foo_7.html") |
| 370 self.assertEqual(statements, [1, 2, 3, 4, 5, 6, 7]) |
| 371 self.assertEqual(missing, [1, 2, 3, 6, 7]) |
| 372 self.assertIn("foo_7.html", cov.data.line_counts()) |
| 373 |
| 374 _, statements, missing, _ = cov.analysis("bar_4.html") |
| 375 self.assertEqual(statements, [1, 2, 3, 4]) |
| 376 self.assertEqual(missing, [1, 4]) |
| 377 self.assertIn("bar_4.html", cov.data.line_counts()) |
| 378 |
| 379 self.assertNotIn("quux_5.html", cov.data.line_counts()) |
| 380 |
| 381 if env.PY2: |
| 382 _, statements, missing, _ = cov.analysis("uni_3.html") |
| 383 self.assertEqual(statements, [1, 2, 3]) |
| 384 self.assertEqual(missing, [1]) |
| 385 self.assertIn("uni_3.html", cov.data.line_counts()) |
| 386 |
| 387 def test_plugin2_with_branch(self): |
| 388 self.make_render_and_caller() |
| 389 |
| 390 cov = coverage.Coverage(branch=True, omit=["*quux*"]) |
| 391 CheckUniqueFilenames.hook(cov, '_should_trace') |
| 392 CheckUniqueFilenames.hook(cov, '_check_include_omit_etc') |
| 393 cov.set_option("run:plugins", ["tests.plugin2"]) |
| 394 |
| 395 self.start_import_stop(cov, "caller") |
| 396 |
| 397 # The way plugin2 works, a file named foo_7.html will be claimed to |
| 398 # have 7 lines in it. If render() was called with line number 4, |
| 399 # then the plugin will claim that lines 4 and 5 were executed. |
| 400 analysis = cov._analyze("foo_7.html") |
| 401 self.assertEqual(analysis.statements, set([1, 2, 3, 4, 5, 6, 7])) |
| 402 # Plugins don't do branch coverage yet. |
| 403 self.assertEqual(analysis.has_arcs(), True) |
| 404 self.assertEqual(analysis.arc_possibilities(), []) |
| 405 |
| 406 self.assertEqual(analysis.missing, set([1, 2, 3, 6, 7])) |
| 407 |
| 408 def test_plugin2_with_text_report(self): |
| 409 self.make_render_and_caller() |
| 410 |
| 411 cov = coverage.Coverage(branch=True, omit=["*quux*"]) |
| 412 cov.set_option("run:plugins", ["tests.plugin2"]) |
| 413 |
| 414 self.start_import_stop(cov, "caller") |
| 415 |
| 416 repout = StringIO() |
| 417 total = cov.report(file=repout, include=["*.html"], omit=["uni*.html"]) |
| 418 report = repout.getvalue().splitlines() |
| 419 expected = [ |
| 420 'Name Stmts Miss Branch BrPart Cover Missing', |
| 421 '--------------------------------------------------------', |
| 422 'bar_4.html 4 2 0 0 50% 1, 4', |
| 423 'foo_7.html 7 5 0 0 29% 1-3, 6-7', |
| 424 '--------------------------------------------------------', |
| 425 'TOTAL 11 7 0 0 36% ', |
| 426 ] |
| 427 self.assertEqual(report, expected) |
| 428 self.assertAlmostEqual(total, 36.36, places=2) |
| 429 |
| 430 def test_plugin2_with_html_report(self): |
| 431 self.make_render_and_caller() |
| 432 |
| 433 cov = coverage.Coverage(branch=True, omit=["*quux*"]) |
| 434 cov.set_option("run:plugins", ["tests.plugin2"]) |
| 435 |
| 436 self.start_import_stop(cov, "caller") |
| 437 |
| 438 total = cov.html_report(include=["*.html"], omit=["uni*.html"]) |
| 439 self.assertAlmostEqual(total, 36.36, places=2) |
| 440 |
| 441 self.assert_exists("htmlcov/index.html") |
| 442 self.assert_exists("htmlcov/bar_4_html.html") |
| 443 self.assert_exists("htmlcov/foo_7_html.html") |
| 444 |
| 445 def test_plugin2_with_xml_report(self): |
| 446 self.make_render_and_caller() |
| 447 |
| 448 cov = coverage.Coverage(branch=True, omit=["*quux*"]) |
| 449 cov.set_option("run:plugins", ["tests.plugin2"]) |
| 450 |
| 451 self.start_import_stop(cov, "caller") |
| 452 |
| 453 total = cov.xml_report(include=["*.html"], omit=["uni*.html"]) |
| 454 self.assertAlmostEqual(total, 36.36, places=2) |
| 455 |
| 456 with open("coverage.xml") as fxml: |
| 457 xml = fxml.read() |
| 458 |
| 459 for snip in [ |
| 460 'filename="bar_4.html" line-rate="0.5" name="bar_4.html"', |
| 461 'filename="foo_7.html" line-rate="0.2857" name="foo_7.html"', |
| 462 ]: |
| 463 self.assertIn(snip, xml) |
| 464 |
| 465 def test_defer_to_python(self): |
| 466 # A plugin that measures, but then wants built-in python reporting. |
| 467 self.make_file("fairly_odd_plugin.py", """\ |
| 468 # A plugin that claims all the odd lines are executed, and none of |
| 469 # the even lines, and then punts reporting off to the built-in |
| 470 # Python reporting. |
| 471 import coverage.plugin |
| 472 class Plugin(coverage.CoveragePlugin): |
| 473 def file_tracer(self, filename): |
| 474 return OddTracer(filename) |
| 475 def file_reporter(self, filename): |
| 476 return "python" |
| 477 |
| 478 class OddTracer(coverage.plugin.FileTracer): |
| 479 def __init__(self, filename): |
| 480 self.filename = filename |
| 481 def source_filename(self): |
| 482 return self.filename |
| 483 def line_number_range(self, frame): |
| 484 lineno = frame.f_lineno |
| 485 if lineno % 2: |
| 486 return (lineno, lineno) |
| 487 else: |
| 488 return (-1, -1) |
| 489 |
| 490 def coverage_init(reg, options): |
| 491 reg.add_file_tracer(Plugin()) |
| 492 """) |
| 493 self.make_file("unsuspecting.py", """\ |
| 494 a = 1 |
| 495 b = 2 |
| 496 c = 3 |
| 497 d = 4 |
| 498 e = 5 |
| 499 f = 6 |
| 500 """) |
| 501 cov = coverage.Coverage(include=["unsuspecting.py"]) |
| 502 cov.set_option("run:plugins", ["fairly_odd_plugin"]) |
| 503 self.start_import_stop(cov, "unsuspecting") |
| 504 |
| 505 repout = StringIO() |
| 506 total = cov.report(file=repout) |
| 507 report = repout.getvalue().splitlines() |
| 508 expected = [ |
| 509 'Name Stmts Miss Cover Missing', |
| 510 '-----------------------------------------------', |
| 511 'unsuspecting.py 6 3 50% 2, 4, 6', |
| 512 ] |
| 513 self.assertEqual(report, expected) |
| 514 self.assertEqual(total, 50) |
| 515 |
| 516 |
| 517 class BadPluginTest(FileTracerTest): |
| 518 """Test error handling around plugins.""" |
| 519 |
| 520 def run_plugin(self, module_name): |
| 521 """Run a plugin with the given module_name. |
| 522 |
| 523 Uses a few fixed Python files. |
| 524 |
| 525 Returns the Coverage object. |
| 526 |
| 527 """ |
| 528 self.make_file("simple.py", """\ |
| 529 import other, another |
| 530 a = other.f(2) |
| 531 b = other.f(3) |
| 532 c = another.g(4) |
| 533 d = another.g(5) |
| 534 """) |
| 535 # The names of these files are important: some plugins apply themselves |
| 536 # to "*other.py". |
| 537 self.make_file("other.py", """\ |
| 538 def f(x): |
| 539 return x+1 |
| 540 """) |
| 541 self.make_file("another.py", """\ |
| 542 def g(x): |
| 543 return x-1 |
| 544 """) |
| 545 |
| 546 cov = coverage.Coverage() |
| 547 cov.set_option("run:plugins", [module_name]) |
| 548 self.start_import_stop(cov, "simple") |
| 549 return cov |
| 550 |
| 551 def run_bad_plugin(self, module_name, plugin_name, our_error=True, excmsg=No
ne): |
| 552 """Run a file, and see that the plugin failed. |
| 553 |
| 554 `module_name` and `plugin_name` is the module and name of the plugin to |
| 555 use. |
| 556 |
| 557 `our_error` is True if the error reported to the user will be an |
| 558 explicit error in our test code, marked with an '# Oh noes!' comment. |
| 559 |
| 560 `excmsg`, if provided, is text that should appear in the stderr. |
| 561 |
| 562 The plugin will be disabled, and we check that a warning is output |
| 563 explaining why. |
| 564 |
| 565 """ |
| 566 self.run_plugin(module_name) |
| 567 |
| 568 stderr = self.stderr() |
| 569 print(stderr) # for diagnosing test failures. |
| 570 |
| 571 if our_error: |
| 572 errors = stderr.count("# Oh noes!") |
| 573 # The exception we're causing should only appear once. |
| 574 self.assertEqual(errors, 1) |
| 575 |
| 576 # There should be a warning explaining what's happening, but only one. |
| 577 # The message can be in two forms: |
| 578 # Disabling plugin '...' due to previous exception |
| 579 # or: |
| 580 # Disabling plugin '...' due to an exception: |
| 581 msg = "Disabling plugin '%s.%s' due to " % (module_name, plugin_name) |
| 582 warnings = stderr.count(msg) |
| 583 self.assertEqual(warnings, 1) |
| 584 |
| 585 if excmsg: |
| 586 self.assertIn(excmsg, stderr) |
| 587 |
| 588 def test_file_tracer_has_no_file_tracer_method(self): |
| 589 self.make_file("bad_plugin.py", """\ |
| 590 class Plugin(object): |
| 591 pass |
| 592 |
| 593 def coverage_init(reg, options): |
| 594 reg.add_file_tracer(Plugin()) |
| 595 """) |
| 596 self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) |
| 597 |
| 598 def test_file_tracer_has_inherited_sourcefilename_method(self): |
| 599 self.make_file("bad_plugin.py", """\ |
| 600 import coverage |
| 601 class Plugin(coverage.CoveragePlugin): |
| 602 def file_tracer(self, filename): |
| 603 # Just grab everything. |
| 604 return FileTracer() |
| 605 |
| 606 class FileTracer(coverage.FileTracer): |
| 607 pass |
| 608 |
| 609 def coverage_init(reg, options): |
| 610 reg.add_file_tracer(Plugin()) |
| 611 """) |
| 612 self.run_bad_plugin( |
| 613 "bad_plugin", "Plugin", our_error=False, |
| 614 excmsg="Class 'bad_plugin.FileTracer' needs to implement source_file
name()", |
| 615 ) |
| 616 |
| 617 def test_plugin_has_inherited_filereporter_method(self): |
| 618 self.make_file("bad_plugin.py", """\ |
| 619 import coverage |
| 620 class Plugin(coverage.CoveragePlugin): |
| 621 def file_tracer(self, filename): |
| 622 # Just grab everything. |
| 623 return FileTracer() |
| 624 |
| 625 class FileTracer(coverage.FileTracer): |
| 626 def source_filename(self): |
| 627 return "foo.xxx" |
| 628 |
| 629 def coverage_init(reg, options): |
| 630 reg.add_file_tracer(Plugin()) |
| 631 """) |
| 632 cov = self.run_plugin("bad_plugin") |
| 633 expected_msg = "Plugin 'bad_plugin.Plugin' needs to implement file_repor
ter()" |
| 634 with self.assertRaisesRegex(NotImplementedError, expected_msg): |
| 635 cov.report() |
| 636 |
| 637 def test_file_tracer_fails(self): |
| 638 self.make_file("bad_plugin.py", """\ |
| 639 import coverage.plugin |
| 640 class Plugin(coverage.plugin.CoveragePlugin): |
| 641 def file_tracer(self, filename): |
| 642 17/0 # Oh noes! |
| 643 |
| 644 def coverage_init(reg, options): |
| 645 reg.add_file_tracer(Plugin()) |
| 646 """) |
| 647 self.run_bad_plugin("bad_plugin", "Plugin") |
| 648 |
| 649 def test_file_tracer_returns_wrong(self): |
| 650 self.make_file("bad_plugin.py", """\ |
| 651 import coverage.plugin |
| 652 class Plugin(coverage.plugin.CoveragePlugin): |
| 653 def file_tracer(self, filename): |
| 654 return 3.14159 |
| 655 |
| 656 def coverage_init(reg, options): |
| 657 reg.add_file_tracer(Plugin()) |
| 658 """) |
| 659 self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) |
| 660 |
| 661 def test_has_dynamic_source_filename_fails(self): |
| 662 self.make_file("bad_plugin.py", """\ |
| 663 import coverage.plugin |
| 664 class Plugin(coverage.plugin.CoveragePlugin): |
| 665 def file_tracer(self, filename): |
| 666 return BadFileTracer() |
| 667 |
| 668 class BadFileTracer(coverage.plugin.FileTracer): |
| 669 def has_dynamic_source_filename(self): |
| 670 23/0 # Oh noes! |
| 671 |
| 672 def coverage_init(reg, options): |
| 673 reg.add_file_tracer(Plugin()) |
| 674 """) |
| 675 self.run_bad_plugin("bad_plugin", "Plugin") |
| 676 |
| 677 def test_source_filename_fails(self): |
| 678 self.make_file("bad_plugin.py", """\ |
| 679 import coverage.plugin |
| 680 class Plugin(coverage.plugin.CoveragePlugin): |
| 681 def file_tracer(self, filename): |
| 682 return BadFileTracer() |
| 683 |
| 684 class BadFileTracer(coverage.plugin.FileTracer): |
| 685 def source_filename(self): |
| 686 42/0 # Oh noes! |
| 687 |
| 688 def coverage_init(reg, options): |
| 689 reg.add_file_tracer(Plugin()) |
| 690 """) |
| 691 self.run_bad_plugin("bad_plugin", "Plugin") |
| 692 |
| 693 def test_source_filename_returns_wrong(self): |
| 694 self.make_file("bad_plugin.py", """\ |
| 695 import coverage.plugin |
| 696 class Plugin(coverage.plugin.CoveragePlugin): |
| 697 def file_tracer(self, filename): |
| 698 return BadFileTracer() |
| 699 |
| 700 class BadFileTracer(coverage.plugin.FileTracer): |
| 701 def source_filename(self): |
| 702 return 17.3 |
| 703 |
| 704 def coverage_init(reg, options): |
| 705 reg.add_file_tracer(Plugin()) |
| 706 """) |
| 707 self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) |
| 708 |
| 709 def test_dynamic_source_filename_fails(self): |
| 710 self.make_file("bad_plugin.py", """\ |
| 711 import coverage.plugin |
| 712 class Plugin(coverage.plugin.CoveragePlugin): |
| 713 def file_tracer(self, filename): |
| 714 if filename.endswith("other.py"): |
| 715 return BadFileTracer() |
| 716 |
| 717 class BadFileTracer(coverage.plugin.FileTracer): |
| 718 def has_dynamic_source_filename(self): |
| 719 return True |
| 720 def dynamic_source_filename(self, filename, frame): |
| 721 101/0 # Oh noes! |
| 722 |
| 723 def coverage_init(reg, options): |
| 724 reg.add_file_tracer(Plugin()) |
| 725 """) |
| 726 self.run_bad_plugin("bad_plugin", "Plugin") |
| 727 |
| 728 def test_line_number_range_returns_non_tuple(self): |
| 729 self.make_file("bad_plugin.py", """\ |
| 730 import coverage.plugin |
| 731 class Plugin(coverage.plugin.CoveragePlugin): |
| 732 def file_tracer(self, filename): |
| 733 if filename.endswith("other.py"): |
| 734 return BadFileTracer() |
| 735 |
| 736 class BadFileTracer(coverage.plugin.FileTracer): |
| 737 def source_filename(self): |
| 738 return "something.foo" |
| 739 |
| 740 def line_number_range(self, frame): |
| 741 return 42.23 |
| 742 |
| 743 def coverage_init(reg, options): |
| 744 reg.add_file_tracer(Plugin()) |
| 745 """) |
| 746 self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) |
| 747 |
| 748 def test_line_number_range_returns_triple(self): |
| 749 self.make_file("bad_plugin.py", """\ |
| 750 import coverage.plugin |
| 751 class Plugin(coverage.plugin.CoveragePlugin): |
| 752 def file_tracer(self, filename): |
| 753 if filename.endswith("other.py"): |
| 754 return BadFileTracer() |
| 755 |
| 756 class BadFileTracer(coverage.plugin.FileTracer): |
| 757 def source_filename(self): |
| 758 return "something.foo" |
| 759 |
| 760 def line_number_range(self, frame): |
| 761 return (1, 2, 3) |
| 762 |
| 763 def coverage_init(reg, options): |
| 764 reg.add_file_tracer(Plugin()) |
| 765 """) |
| 766 self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) |
| 767 |
| 768 def test_line_number_range_returns_pair_of_strings(self): |
| 769 self.make_file("bad_plugin.py", """\ |
| 770 import coverage.plugin |
| 771 class Plugin(coverage.plugin.CoveragePlugin): |
| 772 def file_tracer(self, filename): |
| 773 if filename.endswith("other.py"): |
| 774 return BadFileTracer() |
| 775 |
| 776 class BadFileTracer(coverage.plugin.FileTracer): |
| 777 def source_filename(self): |
| 778 return "something.foo" |
| 779 |
| 780 def line_number_range(self, frame): |
| 781 return ("5", "7") |
| 782 |
| 783 def coverage_init(reg, options): |
| 784 reg.add_file_tracer(Plugin()) |
| 785 """) |
| 786 self.run_bad_plugin("bad_plugin", "Plugin", our_error=False) |
OLD | NEW |