Index: tools/telemetry/third_party/coverage/tests/test_oddball.py |
diff --git a/tools/telemetry/third_party/coverage/tests/test_oddball.py b/tools/telemetry/third_party/coverage/tests/test_oddball.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2ca1aa7558a5f447fbae0164296c3fe1a4f57e89 |
--- /dev/null |
+++ b/tools/telemetry/third_party/coverage/tests/test_oddball.py |
@@ -0,0 +1,435 @@ |
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
+ |
+"""Oddball cases for testing coverage.py""" |
+ |
+import sys |
+ |
+import coverage |
+from coverage.files import abs_file |
+ |
+from tests.coveragetest import CoverageTest |
+from tests import osinfo |
+ |
+ |
+class ThreadingTest(CoverageTest): |
+ """Tests of the threading support.""" |
+ |
+ def test_threading(self): |
+ self.check_coverage("""\ |
+ import threading |
+ |
+ def fromMainThread(): |
+ return "called from main thread" |
+ |
+ def fromOtherThread(): |
+ return "called from other thread" |
+ |
+ def neverCalled(): |
+ return "no one calls me" |
+ |
+ other = threading.Thread(target=fromOtherThread) |
+ other.start() |
+ fromMainThread() |
+ other.join() |
+ """, |
+ [1, 3, 4, 6, 7, 9, 10, 12, 13, 14, 15], "10") |
+ |
+ def test_thread_run(self): |
+ self.check_coverage("""\ |
+ import threading |
+ |
+ class TestThread(threading.Thread): |
+ def run(self): |
+ self.a = 5 |
+ self.do_work() |
+ self.a = 7 |
+ |
+ def do_work(self): |
+ self.a = 10 |
+ |
+ thd = TestThread() |
+ thd.start() |
+ thd.join() |
+ """, |
+ [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14], "") |
+ |
+ |
+class RecursionTest(CoverageTest): |
+ """Check what happens when recursive code gets near limits.""" |
+ |
+ def test_short_recursion(self): |
+ # We can definitely get close to 500 stack frames. |
+ self.check_coverage("""\ |
+ def recur(n): |
+ if n == 0: |
+ return 0 |
+ else: |
+ return recur(n-1)+1 |
+ |
+ recur(495) # We can get at least this many stack frames. |
+ i = 8 # and this line will be traced |
+ """, |
+ [1, 2, 3, 5, 7, 8], "") |
+ |
+ def test_long_recursion(self): |
+ # We can't finish a very deep recursion, but we don't crash. |
+ with self.assertRaises(RuntimeError): |
+ self.check_coverage("""\ |
+ def recur(n): |
+ if n == 0: |
+ return 0 |
+ else: |
+ return recur(n-1)+1 |
+ |
+ recur(100000) # This is definitely too many frames. |
+ """, |
+ [1, 2, 3, 5, 7], "" |
+ ) |
+ |
+ def test_long_recursion_recovery(self): |
+ # Test the core of bug 93: http://bitbucket.org/ned/coveragepy/issue/93 |
+ # When recovering from a stack overflow, the Python trace function is |
+ # disabled, but the C trace function is not. So if we're using a |
+ # Python trace function, we won't trace anything after the stack |
+ # overflow, and there should be a warning about it. If we're using |
+ # the C trace function, only line 3 will be missing, and all else |
+ # will be traced. |
+ |
+ self.make_file("recur.py", """\ |
+ def recur(n): |
+ if n == 0: |
+ return 0 # never hit |
+ else: |
+ return recur(n-1)+1 |
+ |
+ try: |
+ recur(100000) # This is definitely too many frames. |
+ except RuntimeError: |
+ i = 10 |
+ i = 11 |
+ """) |
+ |
+ cov = coverage.Coverage() |
+ self.start_import_stop(cov, "recur") |
+ |
+ pytrace = (cov.collector.tracer_name() == "PyTracer") |
+ expected_missing = [3] |
+ if pytrace: |
+ expected_missing += [9, 10, 11] |
+ |
+ _, statements, missing, _ = cov.analysis("recur.py") |
+ self.assertEqual(statements, [1, 2, 3, 5, 7, 8, 9, 10, 11]) |
+ self.assertEqual(missing, expected_missing) |
+ |
+ # Get a warning about the stackoverflow effect on the tracing function. |
+ if pytrace: |
+ self.assertEqual(cov._warnings, |
+ ["Trace function changed, measurement is likely wrong: None"] |
+ ) |
+ else: |
+ self.assertEqual(cov._warnings, []) |
+ |
+ |
+class MemoryLeakTest(CoverageTest): |
+ """Attempt the impossible: test that memory doesn't leak. |
+ |
+ Note: this test is truly unusual, and has had a colorful history. See |
+ for example: https://bitbucket.org/ned/coveragepy/issue/186 |
+ |
+ It may still fail occasionally, especially on PyPy. |
+ |
+ """ |
+ def test_for_leaks(self): |
+ # Our original bad memory leak only happened on line numbers > 255, so |
+ # make a code object with more lines than that. Ugly string mumbo |
+ # jumbo to get 300 blank lines at the beginning.. |
+ code = """\ |
+ # blank line\n""" * 300 + """\ |
+ def once(x): # line 301 |
+ if x % 100 == 0: |
+ raise Exception("100!") |
+ elif x % 2: |
+ return 10 |
+ else: # line 306 |
+ return 11 |
+ i = 0 # Portable loop without alloc'ing memory. |
+ while i < ITERS: |
+ try: |
+ once(i) |
+ except: |
+ pass |
+ i += 1 # line 315 |
+ """ |
+ lines = list(range(301, 315)) |
+ lines.remove(306) # Line 306 is the "else". |
+ |
+ # This is a non-deterministic test, so try it a few times, and fail it |
+ # only if it predominantly fails. |
+ fails = 0 |
+ for _ in range(10): |
+ ram_0 = osinfo.process_ram() |
+ self.check_coverage(code.replace("ITERS", "10"), lines, "") |
+ ram_10 = osinfo.process_ram() |
+ self.check_coverage(code.replace("ITERS", "10000"), lines, "") |
+ ram_10k = osinfo.process_ram() |
+ # Running the code 10k times shouldn't grow the ram much more than |
+ # running it 10 times. |
+ ram_growth = (ram_10k - ram_10) - (ram_10 - ram_0) |
+ if ram_growth > 100000: # pragma: only failure |
+ fails += 1 |
+ |
+ if fails > 8: # pragma: only failure |
+ self.fail("RAM grew by %d" % (ram_growth)) |
+ |
+ |
+class PyexpatTest(CoverageTest): |
+ """Pyexpat screws up tracing. Make sure we've counter-defended properly.""" |
+ |
+ def test_pyexpat(self): |
+ # pyexpat calls the trace function explicitly (inexplicably), and does |
+ # it wrong for exceptions. Parsing a DOCTYPE for some reason throws |
+ # an exception internally, and triggers its wrong behavior. This test |
+ # checks that our fake PyTrace_RETURN hack in tracer.c works. It will |
+ # also detect if the pyexpat bug is fixed unbeknownst to us, meaning |
+ # we'd see two RETURNs where there should only be one. |
+ |
+ self.make_file("trydom.py", """\ |
+ import xml.dom.minidom |
+ |
+ XML = '''\\ |
+ <!DOCTYPE fooey SYSTEM "http://www.example.com/example.dtd"> |
+ <root><child/><child/></root> |
+ ''' |
+ |
+ def foo(): |
+ dom = xml.dom.minidom.parseString(XML) |
+ assert len(dom.getElementsByTagName('child')) == 2 |
+ a = 11 |
+ |
+ foo() |
+ """) |
+ |
+ self.make_file("outer.py", "\n"*100 + "import trydom\na = 102\n") |
+ |
+ cov = coverage.Coverage() |
+ cov.erase() |
+ |
+ # Import the Python file, executing it. |
+ self.start_import_stop(cov, "outer") |
+ |
+ _, statements, missing, _ = cov.analysis("trydom.py") |
+ self.assertEqual(statements, [1, 3, 8, 9, 10, 11, 13]) |
+ self.assertEqual(missing, []) |
+ |
+ _, statements, missing, _ = cov.analysis("outer.py") |
+ self.assertEqual(statements, [101, 102]) |
+ self.assertEqual(missing, []) |
+ |
+ |
+class ExceptionTest(CoverageTest): |
+ """I suspect different versions of Python deal with exceptions differently |
+ in the trace function. |
+ """ |
+ |
+ def test_exception(self): |
+ # Python 2.3's trace function doesn't get called with "return" if the |
+ # scope is exiting due to an exception. This confounds our trace |
+ # function which relies on scope announcements to track which files to |
+ # trace. |
+ # |
+ # This test is designed to sniff this out. Each function in the call |
+ # stack is in a different file, to try to trip up the tracer. Each |
+ # file has active lines in a different range so we'll see if the lines |
+ # get attributed to the wrong file. |
+ |
+ self.make_file("oops.py", """\ |
+ def oops(args): |
+ a = 2 |
+ raise Exception("oops") |
+ a = 4 |
+ """) |
+ |
+ self.make_file("fly.py", "\n"*100 + """\ |
+ def fly(calls): |
+ a = 2 |
+ calls[0](calls[1:]) |
+ a = 4 |
+ """) |
+ |
+ self.make_file("catch.py", "\n"*200 + """\ |
+ def catch(calls): |
+ try: |
+ a = 3 |
+ calls[0](calls[1:]) |
+ a = 5 |
+ except: |
+ a = 7 |
+ """) |
+ |
+ self.make_file("doit.py", "\n"*300 + """\ |
+ def doit(calls): |
+ try: |
+ calls[0](calls[1:]) |
+ except: |
+ a = 5 |
+ """) |
+ |
+ # Import all the modules before starting coverage, so the def lines |
+ # won't be in all the results. |
+ for mod in "oops fly catch doit".split(): |
+ self.import_local_file(mod) |
+ |
+ # Each run nests the functions differently to get different |
+ # combinations of catching exceptions and letting them fly. |
+ runs = [ |
+ ("doit fly oops", { |
+ 'doit.py': [302, 303, 304, 305], |
+ 'fly.py': [102, 103], |
+ 'oops.py': [2, 3], |
+ }), |
+ ("doit catch oops", { |
+ 'doit.py': [302, 303], |
+ 'catch.py': [202, 203, 204, 206, 207], |
+ 'oops.py': [2, 3], |
+ }), |
+ ("doit fly catch oops", { |
+ 'doit.py': [302, 303], |
+ 'fly.py': [102, 103, 104], |
+ 'catch.py': [202, 203, 204, 206, 207], |
+ 'oops.py': [2, 3], |
+ }), |
+ ("doit catch fly oops", { |
+ 'doit.py': [302, 303], |
+ 'catch.py': [202, 203, 204, 206, 207], |
+ 'fly.py': [102, 103], |
+ 'oops.py': [2, 3], |
+ }), |
+ ] |
+ |
+ for callnames, lines_expected in runs: |
+ |
+ # Make the list of functions we'll call for this test. |
+ callnames = callnames.split() |
+ calls = [getattr(sys.modules[cn], cn) for cn in callnames] |
+ |
+ cov = coverage.Coverage() |
+ cov.start() |
+ # Call our list of functions: invoke the first, with the rest as |
+ # an argument. |
+ calls[0](calls[1:]) # pragma: nested |
+ cov.stop() # pragma: nested |
+ |
+ # Clean the line data and compare to expected results. |
+ # The file names are absolute, so keep just the base. |
+ clean_lines = {} |
+ data = cov.get_data() |
+ for callname in callnames: |
+ filename = callname + ".py" |
+ lines = data.lines(abs_file(filename)) |
+ clean_lines[filename] = sorted(lines) |
+ |
+ self.assertEqual(clean_lines, lines_expected) |
+ |
+ |
+class DoctestTest(CoverageTest): |
+ """Tests invoked with doctest should measure properly.""" |
+ |
+ def setUp(self): |
+ super(DoctestTest, self).setUp() |
+ |
+ # Oh, the irony! This test case exists because Python 2.4's |
+ # doctest module doesn't play well with coverage. But nose fixes |
+ # the problem by monkeypatching doctest. I want to undo the |
+ # monkeypatch to be sure I'm getting the doctest module that users |
+ # of coverage will get. Deleting the imported module here is |
+ # enough: when the test imports doctest again, it will get a fresh |
+ # copy without the monkeypatch. |
+ del sys.modules['doctest'] |
+ |
+ def test_doctest(self): |
+ self.check_coverage('''\ |
+ def return_arg_or_void(arg): |
+ """If <arg> is None, return "Void"; otherwise return <arg> |
+ |
+ >>> return_arg_or_void(None) |
+ 'Void' |
+ >>> return_arg_or_void("arg") |
+ 'arg' |
+ >>> return_arg_or_void("None") |
+ 'None' |
+ """ |
+ if arg is None: |
+ return "Void" |
+ else: |
+ return arg |
+ |
+ import doctest, sys |
+ doctest.testmod(sys.modules[__name__]) # we're not __main__ :( |
+ ''', |
+ [1, 11, 12, 14, 16, 17], "") |
+ |
+ |
+class GettraceTest(CoverageTest): |
+ """Tests that we work properly with `sys.gettrace()`.""" |
+ def test_round_trip(self): |
+ self.check_coverage('''\ |
+ import sys |
+ def foo(n): |
+ return 3*n |
+ def bar(n): |
+ return 5*n |
+ a = foo(6) |
+ sys.settrace(sys.gettrace()) |
+ a = bar(8) |
+ ''', |
+ [1, 2, 3, 4, 5, 6, 7, 8], "") |
+ |
+ def test_multi_layers(self): |
+ self.check_coverage('''\ |
+ import sys |
+ def level1(): |
+ a = 3 |
+ level2() |
+ b = 5 |
+ def level2(): |
+ c = 7 |
+ sys.settrace(sys.gettrace()) |
+ d = 9 |
+ e = 10 |
+ level1() |
+ f = 12 |
+ ''', |
+ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "") |
+ |
+ |
+class ExecTest(CoverageTest): |
+ """Tests of exec.""" |
+ def test_correct_filename(self): |
+ # https://bitbucket.org/ned/coveragepy/issues/380/code-executed-by-exec-excluded-from |
+ # Bug was that exec'd files would have their lines attributed to the |
+ # calling file. Make two files, both with ~30 lines, but no lines in |
+ # common. Line 30 in to_exec.py was recorded as line 30 in main.py, |
+ # but now it's fixed. :) |
+ self.make_file("to_exec.py", """\ |
+ \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n |
+ print("var is {0}".format(var)) # line 31 |
+ """) |
+ self.make_file("main.py", """\ |
+ namespace = {'var': 17} |
+ with open("to_exec.py") as to_exec_py: |
+ code = compile(to_exec_py.read(), 'to_exec.py', 'exec') |
+ exec(code, globals(), namespace) |
+ \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n |
+ print("done") # line 35 |
+ """) |
+ |
+ cov = coverage.Coverage() |
+ self.start_import_stop(cov, "main") |
+ |
+ _, statements, missing, _ = cov.analysis("main.py") |
+ self.assertEqual(statements, [1, 2, 3, 4, 35]) |
+ self.assertEqual(missing, []) |
+ _, statements, missing, _ = cov.analysis("to_exec.py") |
+ self.assertEqual(statements, [31]) |
+ self.assertEqual(missing, []) |