Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(233)

Side by Side Diff: tools/telemetry/third_party/coverage/tests/test_oddball.py

Issue 1366913004: Add coverage Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 """Oddball cases for testing coverage.py"""
5
6 import sys
7
8 import coverage
9 from coverage.files import abs_file
10
11 from tests.coveragetest import CoverageTest
12 from tests import osinfo
13
14
15 class ThreadingTest(CoverageTest):
16 """Tests of the threading support."""
17
18 def test_threading(self):
19 self.check_coverage("""\
20 import threading
21
22 def fromMainThread():
23 return "called from main thread"
24
25 def fromOtherThread():
26 return "called from other thread"
27
28 def neverCalled():
29 return "no one calls me"
30
31 other = threading.Thread(target=fromOtherThread)
32 other.start()
33 fromMainThread()
34 other.join()
35 """,
36 [1, 3, 4, 6, 7, 9, 10, 12, 13, 14, 15], "10")
37
38 def test_thread_run(self):
39 self.check_coverage("""\
40 import threading
41
42 class TestThread(threading.Thread):
43 def run(self):
44 self.a = 5
45 self.do_work()
46 self.a = 7
47
48 def do_work(self):
49 self.a = 10
50
51 thd = TestThread()
52 thd.start()
53 thd.join()
54 """,
55 [1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14], "")
56
57
58 class RecursionTest(CoverageTest):
59 """Check what happens when recursive code gets near limits."""
60
61 def test_short_recursion(self):
62 # We can definitely get close to 500 stack frames.
63 self.check_coverage("""\
64 def recur(n):
65 if n == 0:
66 return 0
67 else:
68 return recur(n-1)+1
69
70 recur(495) # We can get at least this many stack frames.
71 i = 8 # and this line will be traced
72 """,
73 [1, 2, 3, 5, 7, 8], "")
74
75 def test_long_recursion(self):
76 # We can't finish a very deep recursion, but we don't crash.
77 with self.assertRaises(RuntimeError):
78 self.check_coverage("""\
79 def recur(n):
80 if n == 0:
81 return 0
82 else:
83 return recur(n-1)+1
84
85 recur(100000) # This is definitely too many frames.
86 """,
87 [1, 2, 3, 5, 7], ""
88 )
89
90 def test_long_recursion_recovery(self):
91 # Test the core of bug 93: http://bitbucket.org/ned/coveragepy/issue/93
92 # When recovering from a stack overflow, the Python trace function is
93 # disabled, but the C trace function is not. So if we're using a
94 # Python trace function, we won't trace anything after the stack
95 # overflow, and there should be a warning about it. If we're using
96 # the C trace function, only line 3 will be missing, and all else
97 # will be traced.
98
99 self.make_file("recur.py", """\
100 def recur(n):
101 if n == 0:
102 return 0 # never hit
103 else:
104 return recur(n-1)+1
105
106 try:
107 recur(100000) # This is definitely too many frames.
108 except RuntimeError:
109 i = 10
110 i = 11
111 """)
112
113 cov = coverage.Coverage()
114 self.start_import_stop(cov, "recur")
115
116 pytrace = (cov.collector.tracer_name() == "PyTracer")
117 expected_missing = [3]
118 if pytrace:
119 expected_missing += [9, 10, 11]
120
121 _, statements, missing, _ = cov.analysis("recur.py")
122 self.assertEqual(statements, [1, 2, 3, 5, 7, 8, 9, 10, 11])
123 self.assertEqual(missing, expected_missing)
124
125 # Get a warning about the stackoverflow effect on the tracing function.
126 if pytrace:
127 self.assertEqual(cov._warnings,
128 ["Trace function changed, measurement is likely wrong: None"]
129 )
130 else:
131 self.assertEqual(cov._warnings, [])
132
133
134 class MemoryLeakTest(CoverageTest):
135 """Attempt the impossible: test that memory doesn't leak.
136
137 Note: this test is truly unusual, and has had a colorful history. See
138 for example: https://bitbucket.org/ned/coveragepy/issue/186
139
140 It may still fail occasionally, especially on PyPy.
141
142 """
143 def test_for_leaks(self):
144 # Our original bad memory leak only happened on line numbers > 255, so
145 # make a code object with more lines than that. Ugly string mumbo
146 # jumbo to get 300 blank lines at the beginning..
147 code = """\
148 # blank line\n""" * 300 + """\
149 def once(x): # line 301
150 if x % 100 == 0:
151 raise Exception("100!")
152 elif x % 2:
153 return 10
154 else: # line 306
155 return 11
156 i = 0 # Portable loop without alloc'ing memory.
157 while i < ITERS:
158 try:
159 once(i)
160 except:
161 pass
162 i += 1 # line 315
163 """
164 lines = list(range(301, 315))
165 lines.remove(306) # Line 306 is the "else".
166
167 # This is a non-deterministic test, so try it a few times, and fail it
168 # only if it predominantly fails.
169 fails = 0
170 for _ in range(10):
171 ram_0 = osinfo.process_ram()
172 self.check_coverage(code.replace("ITERS", "10"), lines, "")
173 ram_10 = osinfo.process_ram()
174 self.check_coverage(code.replace("ITERS", "10000"), lines, "")
175 ram_10k = osinfo.process_ram()
176 # Running the code 10k times shouldn't grow the ram much more than
177 # running it 10 times.
178 ram_growth = (ram_10k - ram_10) - (ram_10 - ram_0)
179 if ram_growth > 100000: # pragma: only failure
180 fails += 1
181
182 if fails > 8: # pragma: only failure
183 self.fail("RAM grew by %d" % (ram_growth))
184
185
186 class PyexpatTest(CoverageTest):
187 """Pyexpat screws up tracing. Make sure we've counter-defended properly."""
188
189 def test_pyexpat(self):
190 # pyexpat calls the trace function explicitly (inexplicably), and does
191 # it wrong for exceptions. Parsing a DOCTYPE for some reason throws
192 # an exception internally, and triggers its wrong behavior. This test
193 # checks that our fake PyTrace_RETURN hack in tracer.c works. It will
194 # also detect if the pyexpat bug is fixed unbeknownst to us, meaning
195 # we'd see two RETURNs where there should only be one.
196
197 self.make_file("trydom.py", """\
198 import xml.dom.minidom
199
200 XML = '''\\
201 <!DOCTYPE fooey SYSTEM "http://www.example.com/example.dtd">
202 <root><child/><child/></root>
203 '''
204
205 def foo():
206 dom = xml.dom.minidom.parseString(XML)
207 assert len(dom.getElementsByTagName('child')) == 2
208 a = 11
209
210 foo()
211 """)
212
213 self.make_file("outer.py", "\n"*100 + "import trydom\na = 102\n")
214
215 cov = coverage.Coverage()
216 cov.erase()
217
218 # Import the Python file, executing it.
219 self.start_import_stop(cov, "outer")
220
221 _, statements, missing, _ = cov.analysis("trydom.py")
222 self.assertEqual(statements, [1, 3, 8, 9, 10, 11, 13])
223 self.assertEqual(missing, [])
224
225 _, statements, missing, _ = cov.analysis("outer.py")
226 self.assertEqual(statements, [101, 102])
227 self.assertEqual(missing, [])
228
229
230 class ExceptionTest(CoverageTest):
231 """I suspect different versions of Python deal with exceptions differently
232 in the trace function.
233 """
234
235 def test_exception(self):
236 # Python 2.3's trace function doesn't get called with "return" if the
237 # scope is exiting due to an exception. This confounds our trace
238 # function which relies on scope announcements to track which files to
239 # trace.
240 #
241 # This test is designed to sniff this out. Each function in the call
242 # stack is in a different file, to try to trip up the tracer. Each
243 # file has active lines in a different range so we'll see if the lines
244 # get attributed to the wrong file.
245
246 self.make_file("oops.py", """\
247 def oops(args):
248 a = 2
249 raise Exception("oops")
250 a = 4
251 """)
252
253 self.make_file("fly.py", "\n"*100 + """\
254 def fly(calls):
255 a = 2
256 calls[0](calls[1:])
257 a = 4
258 """)
259
260 self.make_file("catch.py", "\n"*200 + """\
261 def catch(calls):
262 try:
263 a = 3
264 calls[0](calls[1:])
265 a = 5
266 except:
267 a = 7
268 """)
269
270 self.make_file("doit.py", "\n"*300 + """\
271 def doit(calls):
272 try:
273 calls[0](calls[1:])
274 except:
275 a = 5
276 """)
277
278 # Import all the modules before starting coverage, so the def lines
279 # won't be in all the results.
280 for mod in "oops fly catch doit".split():
281 self.import_local_file(mod)
282
283 # Each run nests the functions differently to get different
284 # combinations of catching exceptions and letting them fly.
285 runs = [
286 ("doit fly oops", {
287 'doit.py': [302, 303, 304, 305],
288 'fly.py': [102, 103],
289 'oops.py': [2, 3],
290 }),
291 ("doit catch oops", {
292 'doit.py': [302, 303],
293 'catch.py': [202, 203, 204, 206, 207],
294 'oops.py': [2, 3],
295 }),
296 ("doit fly catch oops", {
297 'doit.py': [302, 303],
298 'fly.py': [102, 103, 104],
299 'catch.py': [202, 203, 204, 206, 207],
300 'oops.py': [2, 3],
301 }),
302 ("doit catch fly oops", {
303 'doit.py': [302, 303],
304 'catch.py': [202, 203, 204, 206, 207],
305 'fly.py': [102, 103],
306 'oops.py': [2, 3],
307 }),
308 ]
309
310 for callnames, lines_expected in runs:
311
312 # Make the list of functions we'll call for this test.
313 callnames = callnames.split()
314 calls = [getattr(sys.modules[cn], cn) for cn in callnames]
315
316 cov = coverage.Coverage()
317 cov.start()
318 # Call our list of functions: invoke the first, with the rest as
319 # an argument.
320 calls[0](calls[1:]) # pragma: nested
321 cov.stop() # pragma: nested
322
323 # Clean the line data and compare to expected results.
324 # The file names are absolute, so keep just the base.
325 clean_lines = {}
326 data = cov.get_data()
327 for callname in callnames:
328 filename = callname + ".py"
329 lines = data.lines(abs_file(filename))
330 clean_lines[filename] = sorted(lines)
331
332 self.assertEqual(clean_lines, lines_expected)
333
334
335 class DoctestTest(CoverageTest):
336 """Tests invoked with doctest should measure properly."""
337
338 def setUp(self):
339 super(DoctestTest, self).setUp()
340
341 # Oh, the irony! This test case exists because Python 2.4's
342 # doctest module doesn't play well with coverage. But nose fixes
343 # the problem by monkeypatching doctest. I want to undo the
344 # monkeypatch to be sure I'm getting the doctest module that users
345 # of coverage will get. Deleting the imported module here is
346 # enough: when the test imports doctest again, it will get a fresh
347 # copy without the monkeypatch.
348 del sys.modules['doctest']
349
350 def test_doctest(self):
351 self.check_coverage('''\
352 def return_arg_or_void(arg):
353 """If <arg> is None, return "Void"; otherwise return <arg>
354
355 >>> return_arg_or_void(None)
356 'Void'
357 >>> return_arg_or_void("arg")
358 'arg'
359 >>> return_arg_or_void("None")
360 'None'
361 """
362 if arg is None:
363 return "Void"
364 else:
365 return arg
366
367 import doctest, sys
368 doctest.testmod(sys.modules[__name__]) # we're not __main__ :(
369 ''',
370 [1, 11, 12, 14, 16, 17], "")
371
372
373 class GettraceTest(CoverageTest):
374 """Tests that we work properly with `sys.gettrace()`."""
375 def test_round_trip(self):
376 self.check_coverage('''\
377 import sys
378 def foo(n):
379 return 3*n
380 def bar(n):
381 return 5*n
382 a = foo(6)
383 sys.settrace(sys.gettrace())
384 a = bar(8)
385 ''',
386 [1, 2, 3, 4, 5, 6, 7, 8], "")
387
388 def test_multi_layers(self):
389 self.check_coverage('''\
390 import sys
391 def level1():
392 a = 3
393 level2()
394 b = 5
395 def level2():
396 c = 7
397 sys.settrace(sys.gettrace())
398 d = 9
399 e = 10
400 level1()
401 f = 12
402 ''',
403 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], "")
404
405
406 class ExecTest(CoverageTest):
407 """Tests of exec."""
408 def test_correct_filename(self):
409 # https://bitbucket.org/ned/coveragepy/issues/380/code-executed-by-exec- excluded-from
410 # Bug was that exec'd files would have their lines attributed to the
411 # calling file. Make two files, both with ~30 lines, but no lines in
412 # common. Line 30 in to_exec.py was recorded as line 30 in main.py,
413 # but now it's fixed. :)
414 self.make_file("to_exec.py", """\
415 \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
416 print("var is {0}".format(var)) # line 31
417 """)
418 self.make_file("main.py", """\
419 namespace = {'var': 17}
420 with open("to_exec.py") as to_exec_py:
421 code = compile(to_exec_py.read(), 'to_exec.py', 'exec')
422 exec(code, globals(), namespace)
423 \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
424 print("done") # line 35
425 """)
426
427 cov = coverage.Coverage()
428 self.start_import_stop(cov, "main")
429
430 _, statements, missing, _ = cov.analysis("main.py")
431 self.assertEqual(statements, [1, 2, 3, 4, 35])
432 self.assertEqual(missing, [])
433 _, statements, missing, _ = cov.analysis("to_exec.py")
434 self.assertEqual(statements, [31])
435 self.assertEqual(missing, [])
OLDNEW
« no previous file with comments | « tools/telemetry/third_party/coverage/tests/test_misc.py ('k') | tools/telemetry/third_party/coverage/tests/test_parser.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698