Coverage for tests / test_oddball.py: 100.000%
171 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""Oddball cases for testing coverage.py"""
6from __future__ import annotations 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
8import os.path 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
9import re 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
10import sys 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
11import warnings 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
13import pytest 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
15import coverage 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
16from coverage import env 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
17from coverage.data import sorted_lines 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
18from coverage.files import abs_file 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
19from coverage.misc import import_local_file 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
21from tests import osinfo, testenv 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
22from tests.coveragetest import CoverageTest 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
23from tests.helpers import swallow_warnings 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
26class ThreadingTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
27 """Tests of the threading support."""
29 def test_threading(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
30 self.check_coverage( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
31 """\
32 import threading
34 def fromMainThread():
35 return "called from main thread"
37 def fromOtherThread():
38 return "called from other thread"
40 def neverCalled():
41 return "no one calls me"
43 other = threading.Thread(target=fromOtherThread)
44 other.start()
45 fromMainThread()
46 other.join()
47 """,
48 lines=[1, 3, 4, 6, 7, 9, 10, 12, 13, 14, 15],
49 missing="10",
50 )
52 def test_thread_run(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
53 self.check_coverage( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
54 """\
55 import threading
57 class TestThread(threading.Thread):
58 def run(self):
59 self.a = 5
60 self.do_work()
61 self.a = 7
63 def do_work(self):
64 self.a = 10
66 thd = TestThread()
67 thd.start()
68 thd.join()
69 """,
70 lines=[1, 3, 4, 5, 6, 7, 9, 10, 12, 13, 14],
71 missing="",
72 )
75class RecursionTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
76 """Check what happens when recursive code gets near limits."""
78 def test_short_recursion(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
79 # We can definitely get close to 500 stack frames.
80 self.check_coverage( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
81 """\
82 def recur(n):
83 if n == 0:
84 return 0
85 else:
86 return recur(n-1)+1
88 recur(495) # We can get at least this many stack frames.
89 i = 8 # and this line will be traced
90 """,
91 lines=[1, 2, 3, 5, 7, 8],
92 missing="",
93 )
95 @pytest.mark.flaky(max_runs=3) 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
96 def test_long_recursion(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
97 # We can't finish a very deep recursion, but we don't crash.
98 with pytest.raises(RuntimeError): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
99 with swallow_warnings("Trace function changed, data is likely wrong: None"): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
100 self.check_coverage( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
101 """\
102 def recur(n):
103 if n == 0:
104 return 0
105 else:
106 return recur(n-1)+1
108 recur(100000) # This is definitely too many frames.
109 """,
110 lines=[1, 2, 3, 5, 7],
111 missing="",
112 )
114 def test_long_recursion_recovery(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
115 # Test the core of bug 93: https://github.com/coveragepy/coveragepy/issues/93
116 # When recovering from a stack overflow, the Python trace function is
117 # disabled, but the C trace function is not. So if we're using a
118 # Python trace function, we won't trace anything after the stack
119 # overflow, and there should be a warning about it. If we're using
120 # the C trace function, only line 3 will be missing, and all else
121 # will be traced.
123 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
124 "recur.py",
125 """\
126 import sys #; sys.setrecursionlimit(70)
127 def recur(n):
128 if n == 0:
129 return 0 # never hit
130 else:
131 return recur(n-1)+1
133 try:
134 recur(100000) # This is definitely too many frames.
135 except RuntimeError:
136 i = 11
137 i = 12
138 """,
139 )
141 cov = coverage.Coverage() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
142 with swallow_warnings("Trace function changed, data is likely wrong: None"): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
143 self.start_import_stop(cov, "recur") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
145 assert cov._collector is not None 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
146 pytrace = (cov._collector.tracer_name() == "PyTracer") # fmt: skip 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
147 expected_missing = [4] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
148 if pytrace: # pragma: partial metacov 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
149 expected_missing += [10, 11, 12] 1BCDEFGHIJKLMNOPQRSTUVWXYZ01234
151 _, statements, missing, _ = cov.analysis("recur.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
152 assert statements == [1, 2, 3, 4, 6, 8, 9, 10, 11, 12] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
153 assert expected_missing == missing 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
155 # Get a warning about the stackoverflow effect on the tracing function.
156 if pytrace and not env.METACOV: # pragma: partial metacov 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
157 assert len(cov._warnings) == 1
158 assert re.fullmatch(
159 r"Trace function changed, data is likely wrong: None != "
160 + r"<bound method PyTracer._trace of "
161 + "<PyTracer at 0x[0-9a-fA-F]+: 6 data points in 1 files>>",
162 cov._warnings[0],
163 )
164 else:
165 assert not cov._warnings 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
168class MemoryLeakTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
169 """Attempt the impossible: test that memory doesn't leak.
171 Note: this test is truly unusual, and has had a colorful history. See
172 for example: https://github.com/coveragepy/coveragepy/issues/186
174 It may still fail occasionally, especially on PyPy.
176 """
178 @pytest.mark.flaky 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
179 @pytest.mark.skipif(not testenv.C_TRACER, reason="Only the C tracer has refcounting issues") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
180 def test_for_leaks(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
181 # Our original bad memory leak only happened on line numbers > 255, so
182 # make a code object with more lines than that. Ugly string mumbo
183 # jumbo to get 300 blank lines at the beginning..
184 code = ( 1ijklmanbopcqAyzrstudevfgwhx
185 """\
186 # blank line\n"""
187 * 300
188 + """\
189 def once(x): # line 301
190 if x % 100 == 0:
191 raise Exception("100!")
192 elif x % 2:
193 return 10
194 else: # line 306
195 return 11
196 i = 0 # Portable loop without alloc'ing memory.
197 while i < ITERS:
198 try:
199 once(i)
200 except:
201 pass
202 i += 1 # line 315
203 """
204 )
205 lines = list(range(301, 315)) 1ijklmanbopcqAyzrstudevfgwhx
206 lines.remove(306) # Line 306 is the "else". 1ijklmanbopcqAyzrstudevfgwhx
208 # This is a non-deterministic test, so try it a few times, and fail it
209 # only if it predominantly fails.
210 fails = 0 1ijklmanbopcqAyzrstudevfgwhx
211 for _ in range(10): 1ijklmanbopcqAyzrstudevfgwhx
212 ram_0 = osinfo.process_ram() 1ijklmanbopcqAyzrstudevfgwhx
213 self.check_coverage(code.replace("ITERS", "10"), lines=lines, missing="") 1ijklmanbopcqAyzrstudevfgwhx
214 ram_10 = osinfo.process_ram() 1ijklmanbopcqAyzrstudevfgwhx
215 self.check_coverage(code.replace("ITERS", "10000"), lines=lines, missing="") 1ijklmanbopcqAyzrstudevfgwhx
216 ram_10k = osinfo.process_ram() 1ijklmanbopcqAyzrstudevfgwhx
217 # Running the code 10k times shouldn't grow the ram much more than
218 # running it 10 times.
219 ram_growth = (ram_10k - ram_10) - (ram_10 - ram_0) 1ijklmanbopcqAyzrstudevfgwhx
220 if ram_growth > 100000: 1ijklmanbopcqAyzrstudevfgwhx
221 fails += 1 # pragma: only failure 1abcyzdefgh
223 if fails > 8: 1ijklmanbopcqAyzrstudevfgwhx
224 pytest.fail("RAM grew by %d" % (ram_growth)) # pragma: only failure
226 @pytest.mark.skipif( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
227 not testenv.C_TRACER,
228 reason="Only the C tracer has refcounting issues",
229 # In fact, sysmon explicitly holds onto all code objects,
230 # so this will definitely fail with sysmon.
231 )
232 @pytest.mark.skipif( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
233 env.PYVERSION[:2] == (3, 13) and not env.GIL,
234 reason="3.13t never frees code objects: https://github.com/python/cpython/pull/131989",
235 )
236 @pytest.mark.parametrize("branch", [False, True]) 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
237 def test_eval_codeobject_leak(self, branch: bool) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
238 # https://github.com/coveragepy/coveragepy/issues/1924
239 code = """\ 1ijklmanbopcqrstudevfgwhx
240 for i in range(10_000):
241 r = eval("'a' + '1'")
242 assert r == 'a1'
243 """
244 # Looking for leaks is hard. We consider the leak fixed if at least
245 # one of our loops only increased the footprint by a small amount.
246 base = osinfo.process_ram() 1ijklmanbopcqrstudevfgwhx
247 deltas = [] 1ijklmanbopcqrstudevfgwhx
248 for _ in range(30): 1ijklmanbopcqrstudevfgwhx
249 self.check_coverage(code, lines=[1, 2, 3], missing="", branch=branch) 1ijklmanbopcqrstudevfgwhx
250 now = osinfo.process_ram() 1ijklmanbopcqrstudevfgwhx
251 deltas.append(now - base) 1ijklmanbopcqrstudevfgwhx
252 print(f"Mem delta: {(now - base) // 1024}") 1ijklmanbopcqrstudevfgwhx
253 base = now 1ijklmanbopcqrstudevfgwhx
254 assert any(d < 50 * 1024 for d in deltas) 1ijklmanbopcqrstudevfgwhx
257class MemoryFumblingTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
258 """Test that we properly manage the None refcount."""
260 @pytest.mark.skipif(not testenv.C_TRACER, reason="Only the C tracer has refcounting issues") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
261 def test_dropping_none(self) -> None: # pragma: not covered 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
262 # TODO: Mark this so it will only be run sometimes.
263 pytest.skip("This is too expensive for now (30s)") 1ijklmanbopcqAyzrstudevfgwhx
264 # Start and stop coverage thousands of times to flush out bad
265 # reference counting, maybe.
266 _ = "this is just here to put a type comment on" # type: ignore[unreachable]
267 self.make_file(
268 "the_code.py",
269 """\
270 import random
271 def f():
272 if random.random() > .5:
273 x = 1
274 else:
275 x = 2
276 """,
277 )
278 self.make_file(
279 "main.py",
280 """\
281 import coverage
282 import sys
283 from the_code import f
284 for i in range(10000):
285 cov = coverage.Coverage(branch=True)
286 cov.start()
287 f()
288 cov.stop()
289 cov.erase()
290 print("Final None refcount: %d" % (sys.getrefcount(None)))
291 """,
292 )
293 status, out = self.run_command_status("python main.py")
294 assert status == 0
295 assert "Final None refcount" in out
296 assert "Fatal" not in out
299class PyexpatTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
300 """Pyexpat screws up tracing. Make sure we've counter-defended properly."""
302 def test_pyexpat(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
303 # pyexpat calls the trace function explicitly (inexplicably), and does
304 # it wrong for exceptions. Parsing a DOCTYPE for some reason throws
305 # an exception internally, and triggers its wrong behavior. This test
306 # checks that our fake PyTrace_RETURN hack in tracer.c works. It will
307 # also detect if the pyexpat bug is fixed unbeknownst to us, meaning
308 # we'd see two RETURNs where there should only be one.
310 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
311 "trydom.py",
312 """\
313 import xml.dom.minidom
315 XML = '''\\
316 <!DOCTYPE fooey SYSTEM "http://www.example.com/example.dtd">
317 <root><child/><child/></root>
318 '''
320 def foo():
321 dom = xml.dom.minidom.parseString(XML)
322 assert len(dom.getElementsByTagName('child')) == 2
323 a = 11
325 foo()
326 """,
327 )
329 self.make_file("outer.py", "\n" * 100 + "import trydom\na = 102\n") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
331 cov = coverage.Coverage() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
332 cov.erase() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
334 # Import the Python file, executing it.
335 self.start_import_stop(cov, "outer") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
337 _, statements, missing, _ = cov.analysis("trydom.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
338 assert statements == [1, 3, 8, 9, 10, 11, 13] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
339 assert missing == [] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
341 _, statements, missing, _ = cov.analysis("outer.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
342 assert statements == [101, 102] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
343 assert missing == [] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
345 # Make sure pyexpat isn't recorded as a source file.
346 # https://github.com/coveragepy/coveragepy/issues/419
347 files = cov.get_data().measured_files() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
348 msg = f"Pyexpat.c is in the measured files!: {files!r}:" 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
349 assert not any(f.endswith("pyexpat.c") for f in files), msg 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
352class ExceptionTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
353 """I suspect different versions of Python deal with exceptions differently
354 in the trace function.
355 """
357 def test_exception(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
358 # Python 2.3's trace function doesn't get called with "return" if the
359 # scope is exiting due to an exception. This confounds our trace
360 # function which relies on scope announcements to track which files to
361 # trace.
362 #
363 # This test is designed to sniff this out. Each function in the call
364 # stack is in a different file, to try to trip up the tracer. Each
365 # file has active lines in a different range so we'll see if the lines
366 # get attributed to the wrong file.
368 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
369 "oops.py",
370 """\
371 def oops(args):
372 a = 2
373 raise Exception("oops")
374 a = 4
375 """,
376 )
378 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
379 "fly.py",
380 "\n" * 100
381 + """\
382 def fly(calls):
383 a = 2
384 calls[0](calls[1:])
385 a = 4
386 """,
387 )
389 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
390 "catch.py",
391 "\n" * 200
392 + """\
393 def catch(calls):
394 try:
395 a = 3
396 calls[0](calls[1:])
397 a = 5
398 except:
399 a = 7
400 """,
401 )
403 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
404 "doit.py",
405 "\n" * 300
406 + """\
407 def doit(calls):
408 try:
409 calls[0](calls[1:])
410 except:
411 a = 5
412 """,
413 )
415 # Import all the modules before starting coverage, so the def lines
416 # won't be in all the results.
417 for mod in "oops fly catch doit".split(): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
418 import_local_file(mod) 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
420 # Each run nests the functions differently to get different
421 # combinations of catching exceptions and letting them fly.
422 runs = [ 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
423 (
424 "doit fly oops",
425 {
426 "doit.py": [302, 303, 304, 305],
427 "fly.py": [102, 103],
428 "oops.py": [2, 3],
429 },
430 ),
431 (
432 "doit catch oops",
433 {
434 "doit.py": [302, 303],
435 "catch.py": [202, 203, 204, 206, 207],
436 "oops.py": [2, 3],
437 },
438 ),
439 (
440 "doit fly catch oops",
441 {
442 "doit.py": [302, 303],
443 "fly.py": [102, 103, 104],
444 "catch.py": [202, 203, 204, 206, 207],
445 "oops.py": [2, 3],
446 },
447 ),
448 (
449 "doit catch fly oops",
450 {
451 "doit.py": [302, 303],
452 "catch.py": [202, 203, 204, 206, 207],
453 "fly.py": [102, 103],
454 "oops.py": [2, 3],
455 },
456 ),
457 ]
459 for callnames, lines_expected in runs: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
460 # Make the list of functions we'll call for this test.
461 callnames_list = callnames.split() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
462 calls = [getattr(sys.modules[cn], cn) for cn in callnames_list] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
464 cov = coverage.Coverage() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
465 with cov.collect(): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
466 # Call our list of functions: invoke the first, with the rest as
467 # an argument.
468 calls[0](calls[1:]) 1BCDEFGHIJKLMNOPQRSTUVWXYZ01234
470 # Clean the line data and compare to expected results.
471 # The file names are absolute, so keep just the base.
472 clean_lines = {} 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
473 data = cov.get_data() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
474 for callname in callnames_list: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
475 filename = callname + ".py" 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
476 clean_lines[filename] = sorted_lines(data, abs_file(filename)) 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
478 assert clean_lines == lines_expected 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
481class DoctestTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
482 """Tests invoked with doctest should measure properly."""
484 def test_doctest(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
485 # Doctests used to be traced, with their line numbers credited to the
486 # file they were in. Below, one of the doctests has four lines (1-4),
487 # which would incorrectly claim that lines 1-4 of the file were
488 # executed. In this file, line 2 is not executed.
489 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
490 "the_doctest.py",
491 '''\
492 if "x" in "abc":
493 print("hello")
494 def return_arg_or_void(arg):
495 """If <arg> is None, return "Void"; otherwise return <arg>
497 >>> return_arg_or_void(None)
498 'Void'
499 >>> return_arg_or_void("arg")
500 'arg'
501 >>> return_arg_or_void("None")
502 'None'
503 >>> if "x" in "xyz": # line 1
504 ... if "a" in "aswed": # line 2
505 ... if "a" in "abc": # line 3
506 ... return_arg_or_void(12) # line 4
507 12
508 """
509 if arg is None:
510 return "Void"
511 else:
512 return arg
514 import doctest, sys
515 doctest.testmod(sys.modules[__name__]) # we're not __main__ :(
516 ''',
517 )
518 cov = coverage.Coverage() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
519 with warnings.catch_warnings(): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
520 # Doctest calls pdb which opens ~/.pdbrc without an encoding argument,
521 # but we don't care. PYVERSIONS: this was needed for 3.10 only.
522 warnings.filterwarnings("ignore", r".*'encoding' argument not specified.*") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
523 self.start_import_stop(cov, "the_doctest") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
524 data = cov.get_data() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
525 assert len(data.measured_files()) == 1 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
526 lines = sorted_lines(data, data.measured_files().pop()) 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
527 assert lines == [1, 3, 18, 19, 21, 23, 24] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
530class GettraceTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
531 """Tests that we work properly with `sys.gettrace()`."""
533 def test_round_trip_in_untraced_function(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
534 # https://github.com/coveragepy/coveragepy/issues/575
535 self.make_file("main.py", """import sample""") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
536 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
537 "sample.py",
538 """\
539 from swap import swap_it
540 def doit():
541 print(3)
542 swap_it()
543 print(5)
544 def doit_soon():
545 print(7)
546 doit()
547 print(9)
548 print(10)
549 doit_soon()
550 print(12)
551 """,
552 )
553 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
554 "swap.py",
555 """\
556 import sys
557 def swap_it():
558 sys.settrace(sys.gettrace())
559 """,
560 )
562 # Use --source=sample to prevent measurement of swap.py.
563 cov = coverage.Coverage(source=["sample"]) 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
564 self.start_import_stop(cov, "main") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
566 assert self.stdout() == "10\n7\n3\n5\n9\n12\n" 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
568 _, statements, missing, _ = cov.analysis("sample.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
569 assert statements == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
570 assert missing == [] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
572 def test_setting_new_trace_function(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
573 # https://github.com/coveragepy/coveragepy/issues/436
574 if testenv.SETTRACE_CORE or not env.PYBEHAVIOR.branch_right_left: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
575 missing = "5-7, 13-14" 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
576 else:
577 missing = "5-7" 156789!#$%'()
578 self.check_coverage( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
579 """\
580 import os.path
581 import sys
583 def tracer(frame, event, arg):
584 filename = os.path.basename(frame.f_code.co_filename) # 5
585 print(f"{event}: {filename} @ {frame.f_lineno}") # 6
586 return tracer # 7
588 def begin():
589 sys.settrace(tracer)
591 def collect():
592 t = sys.gettrace() # 13
593 assert t is tracer, t # 14
595 def test_unsets_trace() -> None:
596 begin()
597 collect()
599 old = sys.gettrace()
600 test_unsets_trace()
601 sys.settrace(old)
602 a = 21
603 b = 22
604 """,
605 lines=[1, 2, 4, 5, 6, 7, 9, 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 23, 24],
606 missing=missing,
607 )
609 assert self.last_module_name is not None 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
610 out = self.stdout().replace(self.last_module_name, "coverage_test") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
611 expected = ( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
612 "call: coverage_test.py @ 12\n"
613 + "line: coverage_test.py @ 13\n"
614 + "line: coverage_test.py @ 14\n"
615 + "return: coverage_test.py @ 14\n"
616 )
617 assert expected == out 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
619 @pytest.mark.skipif(env.METACOV, reason="Can't set trace functions during meta-coverage") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
620 def test_atexit_gettrace(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
621 # This is not a test of coverage at all, but of our understanding
622 # of this edge-case behavior in various Pythons.
624 self.make_file(
625 "atexit_gettrace.py",
626 """\
627 import atexit, sys
629 def trace_function(frame, event, arg):
630 return trace_function
631 sys.settrace(trace_function)
633 def show_trace_function():
634 tfn = sys.gettrace()
635 if tfn is not None:
636 tfn = tfn.__name__
637 print(tfn)
638 atexit.register(show_trace_function)
640 # This will show what the trace function is at the end of the program.
641 """,
642 )
643 status, out = self.run_command_status("python atexit_gettrace.py")
644 assert status == 0
645 if env.PYPY:
646 # PyPy clears the trace function before atexit runs.
647 assert out == "None\n"
648 else:
649 # Other Pythons leave the trace function in place.
650 assert out == "trace_function\n"
653class ExecTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
654 """Tests of exec."""
656 def test_correct_filename(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
657 # https://github.com/coveragepy/coveragepy/issues/380
658 # Bug was that exec'd files would have their lines attributed to the
659 # calling file. Make two files, both with ~30 lines, but no lines in
660 # common. Line 30 in to_exec.py was recorded as line 30 in main.py,
661 # but now it's fixed. :)
662 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
663 "to_exec.py",
664 """\
665 \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
666 print("var is {}".format(var)) # line 31
667 """,
668 )
669 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
670 "main.py",
671 """\
672 namespace = {'var': 17}
673 with open("to_exec.py", encoding="utf-8") as to_exec_py:
674 code = compile(to_exec_py.read(), 'to_exec.py', 'exec')
675 exec(code, globals(), namespace)
676 \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
677 print("done") # line 35
678 """,
679 )
681 cov = coverage.Coverage() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
682 self.start_import_stop(cov, "main") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
684 _, statements, missing, _ = cov.analysis("main.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
685 assert statements == [1, 2, 3, 4, 35] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
686 assert missing == [] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
687 _, statements, missing, _ = cov.analysis("to_exec.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
688 assert statements == [31] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
689 assert missing == [] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
691 def test_unencodable_filename(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
692 # https://github.com/coveragepy/coveragepy/issues/891
693 self.make_file("bug891.py", r"""exec(compile("pass", "\udcff.py", "exec"))""") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
694 cov = coverage.Coverage() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
695 self.start_import_stop(cov, "bug891") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
696 # Saving would fail trying to encode \udcff.py
697 cov.save() 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
698 files = [os.path.basename(f) for f in cov.get_data().measured_files()] 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
699 assert "bug891.py" in files 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQsRtSuTdUeVvWfXgYwZh0x1234
702class MockingProtectionTest(CoverageTest): 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
703 """Tests about protecting ourselves from aggressive mocking.
705 https://github.com/coveragepy/coveragepy/issues/416
707 """
709 def test_isolate_module_os(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
710 # To see if this test still detects the problem, change isolate_module
711 # in misc.py to simply return its argument. It should fail with a
712 # stack trace like:
713 # File "/tmp/pytest-of-ned/pytest-118/t0/bug416.py", line 11, in test_path_exists
714 # import bug416a
715 # File "/Users/ned/coverage/trunk/coverage/control.py", line 446, in _should_trace
716 # disp = self._inorout.should_trace(filename, frame)
717 # File "/Users/ned/coverage/trunk/coverage/inorout.py", line 343, in should_trace
718 # orig = os.path.basename(original_filename)
719 # File "/tmp/pytest-of-ned/pytest-118/t0/bug416.py", line 6, in __getattr__
720 # raise ZeroDivisionError(f"boom: {name}")
721 # ZeroDivisionError: boom: basename
723 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
724 "bug416.py",
725 """\
726 import os.path
727 from unittest import mock
729 class BadMod:
730 def __getattr__(self, name):
731 raise ZeroDivisionError(f"boom: {name}")
733 @mock.patch("os.path", new=BadMod())
734 def test_path_exists():
735 print("in test")
736 import bug416a
737 print(bug416a.foo)
738 try:
739 os.path.exists(".")
740 except ZeroDivisionError as e:
741 print(f"Got {e}!")
743 test_path_exists()
744 """,
745 )
746 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
747 "bug416a.py",
748 """\
749 print("bug416a.py")
750 foo = 23
751 """,
752 )
754 import py_compile 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
756 py_compile.compile("bug416a.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
757 out = self.run_command("coverage run bug416.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
758 assert out == "in test\nbug416a.py\n23\nGot boom: exists!\n" 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
760 def test_defend_against_mock_open(self) -> None: 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
761 # We defend against mocking of builtins.open, which some test suites
762 # do although they shouldn't.
763 # See https://github.com/coveragepy/coveragepy/issues/2083
764 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
765 "mock_open.py",
766 """\
767 from unittest import mock
769 @mock.patch("builtins.open", new=lambda *a, **k: 1/0)
770 def test_path_exists():
771 print("in test")
772 try:
773 open("somefile")
774 except ZeroDivisionError as e:
775 print(f"Got {e}!")
777 test_path_exists()
778 """,
779 )
780 self.make_file( 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
781 ".coveragerc",
782 """\
783 [run]
784 branch = True
785 disable_warnings = no-sysmon,no-ctracer
786 """,
787 )
788 out = self.run_command("coverage run --branch mock_open.py") 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234
789 assert out == "in test\nGot division by zero!\n" 1iBjCkDlEmFaGnHbIoJpKcLqMANyOzPrQ5sR6tS7uT8dU9eV!vW#fX$gY%wZ'h0(x1)234