Skip to content

Commit e6bd4f2

Browse files
committed
fix: remapping paths during combining needs to follow relative_files=True. #1147
1 parent 2f5c7ae commit e6bd4f2

File tree

5 files changed

+88
-57
lines changed

5 files changed

+88
-57
lines changed

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ This list is detailed and covers changes in each pre-release version.
2222
Unreleased
2323
----------
2424

25+
- Fix: When remapping file paths through the ``[paths]`` setting while
26+
combining, the ``[run] relative_files`` setting was ignored, resulting in
27+
absolute paths for remapped file names (`issue 1147`_). This is now fixed.
28+
2529
- Fix: Complex conditionals over excluded lines could have incorrectly reported
2630
a missing branch (`issue 1271`_). This is now fixed.
2731

@@ -33,6 +37,7 @@ Unreleased
3337
I'd rather not "fix" unsupported interfaces, it's actually nicer with a
3438
default value.
3539

40+
.. _issue 1147: https://github.com/nedbat/coveragepy/issues/1147
3641
.. _issue 1271: https://github.com/nedbat/coveragepy/issues/1271
3742
.. _issue 1273: https://github.com/nedbat/coveragepy/issues/1273
3843

coverage/control.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ def combine(self, data_paths=None, strict=False, keep=False):
706706

707707
aliases = None
708708
if self.config.paths:
709-
aliases = PathAliases()
709+
aliases = PathAliases(relative=self.config.relative_files)
710710
for paths in self.config.paths.values():
711711
result = paths[0]
712712
for pattern in paths[1:]:

coverage/files.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,13 @@ class PathAliases:
326326
map a path through those aliases to produce a unified path.
327327
328328
"""
329-
def __init__(self):
329+
def __init__(self, relative=False):
330330
self.aliases = []
331+
self.relative = relative
331332

332333
def pprint(self): # pragma: debugging
333334
"""Dump the important parts of the PathAliases, for debugging."""
335+
print(f"Aliases (relative={self.relative}):")
334336
for regex, result in self.aliases:
335337
print(f"{regex.pattern!r} --> {result!r}")
336338

@@ -393,7 +395,8 @@ def map(self, path):
393395
if m:
394396
new = path.replace(m.group(0), result)
395397
new = new.replace(sep(path), sep(result))
396-
new = canonical_filename(new)
398+
if not self.relative:
399+
new = canonical_filename(new)
397400
return new
398401
return path
399402

tests/test_api.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,16 +1151,19 @@ def test_moving_stuff_with_relative(self):
11511151
assert res == 100
11521152

11531153
def test_combine_relative(self):
1154-
self.make_file("dir1/foo.py", "a = 1")
1155-
self.make_file("dir1/.coveragerc", """\
1154+
self.make_file("foo.py", """\
1155+
import mod
1156+
a = 1
1157+
""")
1158+
self.make_file("lib/mod/__init__.py", "x = 1")
1159+
self.make_file(".coveragerc", """\
11561160
[run]
11571161
relative_files = true
11581162
""")
1159-
with change_dir("dir1"):
1160-
cov = coverage.Coverage(source=["."], data_suffix=True)
1161-
self.start_import_stop(cov, "foo")
1162-
cov.save()
1163-
shutil.move(glob.glob(".coverage.*")[0], "..")
1163+
sys.path.append("lib")
1164+
cov = coverage.Coverage(source=["."], data_suffix=True)
1165+
self.start_import_stop(cov, "foo")
1166+
cov.save()
11641167

11651168
self.make_file("dir2/bar.py", "a = 1")
11661169
self.make_file("dir2/.coveragerc", """\
@@ -1176,17 +1179,22 @@ def test_combine_relative(self):
11761179
self.make_file(".coveragerc", """\
11771180
[run]
11781181
relative_files = true
1182+
[paths]
1183+
source =
1184+
modsrc
1185+
*/mod
11791186
""")
11801187
cov = coverage.Coverage()
11811188
cov.combine()
11821189
cov.save()
11831190

11841191
self.make_file("foo.py", "a = 1")
11851192
self.make_file("bar.py", "a = 1")
1193+
self.make_file("modsrc/__init__.py", "x = 1")
11861194

11871195
cov = coverage.Coverage()
11881196
cov.load()
11891197
files = cov.get_data().measured_files()
1190-
assert files == {'foo.py', 'bar.py'}
1198+
assert files == {'foo.py', 'bar.py', 'modsrc/__init__.py'}
11911199
res = cov.report()
11921200
assert res == 100

tests/test_files.py

Lines changed: 61 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -227,56 +227,61 @@ def test_fnmatch_windows_paths(self):
227227
self.assertMatches(fnm, r"dir\foo.py", True)
228228

229229

230+
@pytest.fixture(params=[False, True], name="rel_yn")
231+
def relative_setting(request):
232+
"""Parameterized fixture to choose whether PathAliases is relative or not."""
233+
return request.param
234+
235+
230236
class PathAliasesTest(CoverageTest):
231237
"""Tests for coverage/files.py:PathAliases"""
232238

233239
run_in_temp_dir = False
234240

235-
def assert_mapped(self, aliases, inp, out):
241+
def assert_mapped(self, aliases, inp, out, relative=False):
236242
"""Assert that `inp` mapped through `aliases` produces `out`.
237243
238-
`out` is canonicalized first, since aliases always produce
239-
canonicalized paths.
244+
`out` is canonicalized first, since aliases produce canonicalized
245+
paths by default.
240246
241247
"""
242-
aliases.pprint()
243-
print(inp)
244-
print(out)
245-
assert aliases.map(inp) == files.canonical_filename(out)
248+
mapped = aliases.map(inp)
249+
expected = files.canonical_filename(out) if not relative else out
250+
assert mapped == expected
246251

247252
def assert_unchanged(self, aliases, inp):
248253
"""Assert that `inp` mapped through `aliases` is unchanged."""
249254
assert aliases.map(inp) == inp
250255

251-
def test_noop(self):
252-
aliases = PathAliases()
256+
def test_noop(self, rel_yn):
257+
aliases = PathAliases(relative=rel_yn)
253258
self.assert_unchanged(aliases, '/ned/home/a.py')
254259

255-
def test_nomatch(self):
256-
aliases = PathAliases()
260+
def test_nomatch(self, rel_yn):
261+
aliases = PathAliases(relative=rel_yn)
257262
aliases.add('/home/*/src', './mysrc')
258263
self.assert_unchanged(aliases, '/home/foo/a.py')
259264

260-
def test_wildcard(self):
261-
aliases = PathAliases()
265+
def test_wildcard(self, rel_yn):
266+
aliases = PathAliases(relative=rel_yn)
262267
aliases.add('/ned/home/*/src', './mysrc')
263-
self.assert_mapped(aliases, '/ned/home/foo/src/a.py', './mysrc/a.py')
268+
self.assert_mapped(aliases, '/ned/home/foo/src/a.py', './mysrc/a.py', relative=rel_yn)
264269

265-
aliases = PathAliases()
270+
aliases = PathAliases(relative=rel_yn)
266271
aliases.add('/ned/home/*/src/', './mysrc')
267-
self.assert_mapped(aliases, '/ned/home/foo/src/a.py', './mysrc/a.py')
272+
self.assert_mapped(aliases, '/ned/home/foo/src/a.py', './mysrc/a.py', relative=rel_yn)
268273

269-
def test_no_accidental_match(self):
270-
aliases = PathAliases()
274+
def test_no_accidental_match(self, rel_yn):
275+
aliases = PathAliases(relative=rel_yn)
271276
aliases.add('/home/*/src', './mysrc')
272277
self.assert_unchanged(aliases, '/home/foo/srcetc')
273278

274-
def test_multiple_patterns(self):
275-
aliases = PathAliases()
279+
def test_multiple_patterns(self, rel_yn):
280+
aliases = PathAliases(relative=rel_yn)
276281
aliases.add('/home/*/src', './mysrc')
277282
aliases.add('/lib/*/libsrc', './mylib')
278-
self.assert_mapped(aliases, '/home/foo/src/a.py', './mysrc/a.py')
279-
self.assert_mapped(aliases, '/lib/foo/libsrc/a.py', './mylib/a.py')
283+
self.assert_mapped(aliases, '/home/foo/src/a.py', './mysrc/a.py', relative=rel_yn)
284+
self.assert_mapped(aliases, '/lib/foo/libsrc/a.py', './mylib/a.py', relative=rel_yn)
280285

281286
def test_cant_have_wildcard_at_end(self):
282287
aliases = PathAliases()
@@ -295,80 +300,90 @@ def test_no_accidental_munging(self):
295300
self.assert_mapped(aliases, r'c:\Zoo\boo\foo.py', 'src/foo.py')
296301
self.assert_mapped(aliases, r'/home/ned$/foo.py', 'src/foo.py')
297302

298-
def test_paths_are_os_corrected(self):
299-
aliases = PathAliases()
303+
def test_paths_are_os_corrected(self, rel_yn):
304+
aliases = PathAliases(relative=rel_yn)
300305
aliases.add('/home/ned/*/src', './mysrc')
301306
aliases.add(r'c:\ned\src', './mysrc')
302-
self.assert_mapped(aliases, r'C:\Ned\src\sub\a.py', './mysrc/sub/a.py')
307+
self.assert_mapped(aliases, r'C:\Ned\src\sub\a.py', './mysrc/sub/a.py', relative=rel_yn)
303308

304-
aliases = PathAliases()
309+
aliases = PathAliases(relative=rel_yn)
305310
aliases.add('/home/ned/*/src', r'.\mysrc')
306311
aliases.add(r'c:\ned\src', r'.\mysrc')
307-
self.assert_mapped(aliases, r'/home/ned/foo/src/sub/a.py', r'.\mysrc\sub\a.py')
312+
self.assert_mapped(
313+
aliases,
314+
r'/home/ned/foo/src/sub/a.py',
315+
r'.\mysrc\sub\a.py',
316+
relative=rel_yn,
317+
)
308318

309-
def test_windows_on_linux(self):
319+
def test_windows_on_linux(self, rel_yn):
310320
# https://github.com/nedbat/coveragepy/issues/618
311321
lin = "*/project/module/"
312322
win = "*\\project\\module\\"
313323

314324
# Try the paths in both orders.
315325
for paths in [[lin, win], [win, lin]]:
316-
aliases = PathAliases()
326+
aliases = PathAliases(relative=rel_yn)
317327
for path in paths:
318328
aliases.add(path, "project/module")
319329
self.assert_mapped(
320330
aliases,
321331
"C:\\a\\path\\somewhere\\coveragepy_test\\project\\module\\tests\\file.py",
322-
"project/module/tests/file.py"
332+
"project/module/tests/file.py",
333+
relative=rel_yn,
323334
)
324335

325-
def test_linux_on_windows(self):
336+
def test_linux_on_windows(self, rel_yn):
326337
# https://github.com/nedbat/coveragepy/issues/618
327338
lin = "*/project/module/"
328339
win = "*\\project\\module\\"
329340

330341
# Try the paths in both orders.
331342
for paths in [[lin, win], [win, lin]]:
332-
aliases = PathAliases()
343+
aliases = PathAliases(relative=rel_yn)
333344
for path in paths:
334345
aliases.add(path, "project\\module")
335346
self.assert_mapped(
336347
aliases,
337348
"C:/a/path/somewhere/coveragepy_test/project/module/tests/file.py",
338-
"project\\module\\tests\\file.py"
349+
"project\\module\\tests\\file.py",
350+
relative=rel_yn,
339351
)
340352

341-
def test_multiple_wildcard(self):
342-
aliases = PathAliases()
353+
def test_multiple_wildcard(self, rel_yn):
354+
aliases = PathAliases(relative=rel_yn)
343355
aliases.add('/home/jenkins/*/a/*/b/*/django', './django')
344356
self.assert_mapped(
345357
aliases,
346358
'/home/jenkins/xx/a/yy/b/zz/django/foo/bar.py',
347-
'./django/foo/bar.py'
359+
'./django/foo/bar.py',
360+
relative=rel_yn,
348361
)
349362

350-
def test_windows_root_paths(self):
351-
aliases = PathAliases()
363+
def test_windows_root_paths(self, rel_yn):
364+
aliases = PathAliases(relative=rel_yn)
352365
aliases.add('X:\\', '/tmp/src')
353366
self.assert_mapped(
354367
aliases,
355368
"X:\\a\\file.py",
356-
"/tmp/src/a/file.py"
369+
"/tmp/src/a/file.py",
370+
relative=rel_yn,
357371
)
358372
self.assert_mapped(
359373
aliases,
360374
"X:\\file.py",
361-
"/tmp/src/file.py"
375+
"/tmp/src/file.py",
376+
relative=rel_yn,
362377
)
363378

364-
def test_leading_wildcard(self):
365-
aliases = PathAliases()
379+
def test_leading_wildcard(self, rel_yn):
380+
aliases = PathAliases(relative=rel_yn)
366381
aliases.add('*/d1', './mysrc1')
367382
aliases.add('*/d2', './mysrc2')
368-
self.assert_mapped(aliases, '/foo/bar/d1/x.py', './mysrc1/x.py')
369-
self.assert_mapped(aliases, '/foo/bar/d2/y.py', './mysrc2/y.py')
383+
self.assert_mapped(aliases, '/foo/bar/d1/x.py', './mysrc1/x.py', relative=rel_yn)
384+
self.assert_mapped(aliases, '/foo/bar/d2/y.py', './mysrc2/y.py', relative=rel_yn)
370385

371-
def test_dot(self):
386+
def test_dot(self, rel_yn):
372387
cases = ['.', '..', '../other']
373388
if not env.WINDOWS:
374389
# The root test case was added for the manylinux Docker images,
@@ -382,7 +397,7 @@ def test_dot(self):
382397
the_file = os.path.abspath(os.path.realpath(the_file))
383398

384399
assert '~' not in the_file # to be sure the test is pure.
385-
self.assert_mapped(aliases, the_file, '/the/source/a.py')
400+
self.assert_mapped(aliases, the_file, '/the/source/a.py', relative=rel_yn)
386401

387402

388403
class FindPythonFilesTest(CoverageTest):

0 commit comments

Comments
 (0)