Skip to content

Commit 5ff7637

Browse files
committed
Combining contexts works
1 parent d2f77ab commit 5ff7637

File tree

2 files changed

+111
-22
lines changed

2 files changed

+111
-22
lines changed

coverage/sqldata.py

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# TODO: run_info
1313

1414
import glob
15+
import itertools
1516
import os
1617
import sqlite3
1718

@@ -90,7 +91,7 @@ def __init__(self, basename=None, suffix=None, warn=None, debug=None):
9091
self._has_lines = False
9192
self._has_arcs = False
9293

93-
self._context_id = 0
94+
self._current_context_id = None
9495

9596
def _choose_filename(self):
9697
self.filename = self._basename
@@ -104,6 +105,7 @@ def _reset(self):
104105
self._db = None
105106
self._file_map = {}
106107
self._have_used = False
108+
self._current_context_id = None
107109

108110
def _create_db(self):
109111
if self._debug and self._debug.should('dataio'):
@@ -178,17 +180,28 @@ def _file_id(self, filename, add=False):
178180
self._file_map[filename] = cur.lastrowid
179181
return self._file_map.get(filename)
180182

183+
def _context_id(self, context):
184+
"""Get the id for a context."""
185+
assert context is not None
186+
self._start_using()
187+
with self._connect() as con:
188+
row = con.execute("select id from context where context = ?", (context,)).fetchone()
189+
if row is not None:
190+
return row[0]
191+
else:
192+
return None
193+
181194
def set_context(self, context):
182195
"""Set the current context for future `add_lines` etc."""
183196
self._start_using()
184197
context = context or ""
185198
with self._connect() as con:
186199
row = con.execute("select id from context where context = ?", (context,)).fetchone()
187200
if row is not None:
188-
self._context_id = row[0]
201+
self._current_context_id = row[0]
189202
else:
190203
cur = con.execute("insert into context (context) values (?)", (context,))
191-
self._context_id = cur.lastrowid
204+
self._current_context_id = cur.lastrowid
192205

193206
def add_lines(self, line_data):
194207
"""Add measured line data.
@@ -204,10 +217,12 @@ def add_lines(self, line_data):
204217
))
205218
self._start_using()
206219
self._choose_lines_or_arcs(lines=True)
220+
if self._current_context_id is None:
221+
self.set_context("")
207222
with self._connect() as con:
208223
for filename, linenos in iitems(line_data):
209224
file_id = self._file_id(filename, add=True)
210-
data = [(file_id, self._context_id, lineno) for lineno in linenos]
225+
data = [(file_id, self._current_context_id, lineno) for lineno in linenos]
211226
con.executemany(
212227
"insert or ignore into line (file_id, context_id, lineno) values (?, ?, ?)",
213228
data,
@@ -227,10 +242,12 @@ def add_arcs(self, arc_data):
227242
))
228243
self._start_using()
229244
self._choose_lines_or_arcs(arcs=True)
245+
if self._current_context_id is None:
246+
self.set_context("")
230247
with self._connect() as con:
231248
for filename, arcs in iitems(arc_data):
232249
file_id = self._file_id(filename, add=True)
233-
data = [(file_id, self._context_id, fromno, tono) for fromno, tono in arcs]
250+
data = [(file_id, self._current_context_id, fromno, tono) for fromno, tono in arcs]
234251
con.executemany(
235252
"insert or ignore into arc (file_id, context_id, fromno, tono) values (?, ?, ?, ?)",
236253
data,
@@ -306,19 +323,23 @@ def update(self, other_data, aliases=None):
306323

307324
# lines
308325
if other_data._has_lines:
309-
for filename in other_data.measured_files():
310-
lines = set(other_data.lines(filename))
311-
filename = aliases.map(filename)
312-
lines.update(self.lines(filename) or ())
313-
self.add_lines({filename: lines})
326+
for context in other_data.measured_contexts():
327+
self.set_context(context)
328+
for filename in other_data.measured_files():
329+
lines = set(other_data.lines(filename, context=context))
330+
filename = aliases.map(filename)
331+
lines.update(self.lines(filename, context=context) or ())
332+
self.add_lines({filename: lines})
314333

315334
# arcs
316335
if other_data._has_arcs:
317-
for filename in other_data.measured_files():
318-
arcs = set(other_data.arcs(filename))
319-
filename = aliases.map(filename)
320-
arcs.update(self.arcs(filename) or ())
321-
self.add_arcs({filename: arcs})
336+
for context in other_data.measured_contexts():
337+
self.set_context(context)
338+
for filename in other_data.measured_files():
339+
arcs = set(other_data.arcs(filename, context=context))
340+
filename = aliases.map(filename)
341+
arcs.update(self.arcs(filename, context=context) or ())
342+
self.add_arcs({filename: arcs})
322343

323344
# file_tracers
324345
for filename in other_data.measured_files():
@@ -407,12 +428,11 @@ def file_tracer(self, filename):
407428
return row[0] or ""
408429
return "" # File was measured, but no tracer associated.
409430

410-
def lines(self, filename):
431+
def lines(self, filename, context=None):
411432
self._start_using()
412433
if self.has_arcs():
413-
arcs = self.arcs(filename)
434+
arcs = self.arcs(filename, context=context)
414435
if arcs is not None:
415-
import itertools
416436
all_lines = itertools.chain.from_iterable(arcs)
417437
return list(set(l for l in all_lines if l > 0))
418438

@@ -421,18 +441,28 @@ def lines(self, filename):
421441
if file_id is None:
422442
return None
423443
else:
424-
linenos = con.execute("select lineno from line where file_id = ?", (file_id,))
444+
query = "select lineno from line where file_id = ?"
445+
data = [file_id]
446+
if context is not None:
447+
query += " and context_id = ?"
448+
data += [self._context_id(context)]
449+
linenos = con.execute(query, data)
425450
return [lineno for lineno, in linenos]
426451

427-
def arcs(self, filename):
452+
def arcs(self, filename, context=None):
428453
self._start_using()
429454
with self._connect() as con:
430455
file_id = self._file_id(filename)
431456
if file_id is None:
432457
return None
433458
else:
434-
arcs = con.execute("select fromno, tono from arc where file_id = ?", (file_id,))
435-
return [pair for pair in arcs]
459+
query = "select fromno, tono from arc where file_id = ?"
460+
data = [file_id]
461+
if context is not None:
462+
query += " and context_id = ?"
463+
data += [self._context_id(context)]
464+
arcs = con.execute(query, data)
465+
return list(arcs)
436466

437467
def run_infos(self):
438468
return [] # TODO

tests/test_context.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
"""Tests for context support."""
55

6+
import os.path
7+
68
import coverage
9+
from coverage.data import CoverageData, combine_parallel_data
710

811
from tests.coveragetest import CoverageTest
912

@@ -28,3 +31,59 @@ def test_global_context(self):
2831
self.start_import_stop(cov, "main")
2932
data = cov.get_data()
3033
self.assertCountEqual(data.measured_contexts(), ["gooey"])
34+
35+
def run_red_blue(self, **options):
36+
self.make_file("red.py", """\
37+
a = 1
38+
if a > 2:
39+
a = 3
40+
assert a == 1
41+
""")
42+
red_cov = coverage.Coverage(context="red", data_suffix="r", source=["."], **options)
43+
self.start_import_stop(red_cov, "red")
44+
red_cov.save()
45+
46+
self.make_file("blue.py", """\
47+
b = 1
48+
if b > 2:
49+
b = 3
50+
assert b == 1
51+
""")
52+
blue_cov = coverage.Coverage(context="blue", data_suffix="b", source=["."], **options)
53+
self.start_import_stop(blue_cov, "blue")
54+
blue_cov.save()
55+
56+
def test_combining_line_contexts(self):
57+
self.run_red_blue()
58+
combined = CoverageData()
59+
combine_parallel_data(combined)
60+
61+
self.assertEqual(combined.measured_contexts(), {'red', 'blue'})
62+
63+
full_names = {os.path.basename(f): f for f in combined.measured_files()}
64+
self.assertCountEqual(full_names, ['red.py', 'blue.py'])
65+
66+
self.assertEqual(combined.lines(full_names['red.py'], context='red'), [1, 2, 4])
67+
self.assertEqual(combined.lines(full_names['red.py'], context='blue'), [])
68+
self.assertEqual(combined.lines(full_names['blue.py'], context='red'), [])
69+
self.assertEqual(combined.lines(full_names['blue.py'], context='blue'), [1, 2, 4])
70+
71+
def test_combining_arc_contexts(self):
72+
self.run_red_blue(branch=True)
73+
combined = CoverageData()
74+
combine_parallel_data(combined)
75+
76+
self.assertEqual(combined.measured_contexts(), {'red', 'blue'})
77+
78+
full_names = {os.path.basename(f): f for f in combined.measured_files()}
79+
self.assertCountEqual(full_names, ['red.py', 'blue.py'])
80+
81+
self.assertEqual(combined.lines(full_names['red.py'], context='red'), [1, 2, 4])
82+
self.assertEqual(combined.lines(full_names['red.py'], context='blue'), [])
83+
self.assertEqual(combined.lines(full_names['blue.py'], context='red'), [])
84+
self.assertEqual(combined.lines(full_names['blue.py'], context='blue'), [1, 2, 4])
85+
86+
self.assertEqual(combined.arcs(full_names['red.py'], context='red'), [(-1, 1), (1, 2), (2, 4), (4, -1)])
87+
self.assertEqual(combined.arcs(full_names['red.py'], context='blue'), [])
88+
self.assertEqual(combined.arcs(full_names['blue.py'], context='red'), [])
89+
self.assertEqual(combined.arcs(full_names['blue.py'], context='blue'), [(-1, 1), (1, 2), (2, 4), (4, -1)])

0 commit comments

Comments
 (0)