Skip to content

Commit 9c04f27

Browse files
author
Eric Snow
authored
Return relfile and location as-is (do no normcase). (#6781)
1 parent 6666604 commit 9c04f27

2 files changed

Lines changed: 94 additions & 29 deletions

File tree

pythonFiles/testing_tools/adapter/pytest/_pytest_item.py

Lines changed: 92 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,10 @@ def parse_item(item, _normcase, _pathsep):
151151
# This can result from importing a test function from another module.
152152

153153
# Figure out the file.
154-
relfile = fileid
155-
fspath = _normcase(str(item.fspath))
156-
if not fspath.endswith(relfile[1:]):
157-
raise should_never_reach_here(
158-
item,
159-
fspath,
160-
relfile,
161-
)
162-
testroot = fspath[:-len(relfile) + 1]
163-
location, fullname = _get_location(item, relfile, _normcase, _pathsep)
154+
testroot, relfile = _split_fspath(str(item.fspath), fileid, item,
155+
_normcase)
156+
location, fullname = _get_location(item, testroot, relfile,
157+
_normcase, _pathsep)
164158
if kind == 'function':
165159
if testfunc and fullname != testfunc + parameterized:
166160
raise should_never_reach_here(
@@ -218,43 +212,114 @@ def parse_item(item, _normcase, _pathsep):
218212
return test, parents
219213

220214

221-
def _get_location(item, relfile, _normcase, _pathsep):
215+
def _split_fspath(fspath, fileid, item, _normcase):
216+
"""Return (testroot, relfile) for the given fspath.
217+
218+
"relfile" will match "fileid".
219+
"""
220+
# "fileid" comes from nodeid, is always normcased, and is always
221+
# relative to the testroot (with a "./" prefix.
222+
_relsuffix = fileid[1:] # Drop (only) the "." prefix.
223+
if not _normcase(fspath).endswith(_relsuffix):
224+
raise should_never_reach_here(
225+
item,
226+
fspath,
227+
fileid,
228+
)
229+
testroot = fspath[:-len(fileid) + 1] # Ignore the "./" prefix.
230+
relfile = '.' + fspath[-len(fileid) + 1:] # Keep the pathsep.
231+
return testroot, relfile
232+
233+
234+
def _get_location(item, testroot, relfile, _normcase, _pathsep):
222235
"""Return (loc str, fullname) for the given item."""
223236
srcfile, lineno, fullname = item.location
224-
srcfile = _normcase(srcfile)
225-
if srcfile in (relfile, relfile[len(_pathsep) + 1:]):
237+
if _matches_relfile(srcfile, testroot, relfile, _normcase, _pathsep):
226238
srcfile = relfile
227239
else:
228240
# pytest supports discovery of tests imported from other
229241
# modules. This is reflected by a different filename
230242
# in item.location.
231-
srcfile, lineno = _find_location(
232-
srcfile, lineno, relfile, item.function, _pathsep)
243+
244+
if _is_legacy_wrapper(srcfile, _pathsep):
245+
srcfile = relfile
246+
unwrapped = _unwrap_decorator(item.function)
247+
if unwrapped is None:
248+
# It was an invalid legacy wrapper so we just say
249+
# "somewhere in relfile".
250+
lineno = None
251+
else:
252+
_srcfile, lineno = unwrapped
253+
if not _matches_relfile(_srcfile, testroot, relfile, _normcase, _pathsep):
254+
# For legacy wrappers we really expect the wrapped
255+
# function to be in relfile. So here we ignore any
256+
# other file and just say "somewhere in relfile".
257+
lineno = None
258+
if lineno is None:
259+
lineno = -1 # i.e. "unknown"
260+
elif _matches_relfile(srcfile, testroot, relfile, _normcase, _pathsep):
261+
srcfile = relfile
262+
# Otherwise we just return the info from item.location as-is.
263+
233264
if not srcfile.startswith('.' + _pathsep):
234265
srcfile = '.' + _pathsep + srcfile
235266
# from pytest, line numbers are 0-based
236267
location = '{}:{}'.format(srcfile, int(lineno) + 1)
237268
return location, fullname
238269

239270

240-
def _find_location(srcfile, lineno, relfile, func, _pathsep):
241-
"""Return (filename, lno) for the given location info."""
242-
if sys.version_info > (3,):
243-
return srcfile, lineno
271+
def _matches_relfile(srcfile, testroot, relfile, _normcase, _pathsep):
272+
"""Return True if "srcfile" matches the given relfile."""
273+
srcfile = _normcase(srcfile)
274+
relfile = _normcase(relfile)
275+
if srcfile == relfile:
276+
return True
277+
elif srcfile == relfile[len(_pathsep) + 1:]:
278+
return True
279+
elif srcfile == testroot + relfile[1:]:
280+
return True
281+
else:
282+
return False
283+
284+
285+
def _is_legacy_wrapper(srcfile, _pathsep, _pyversion=sys.version_info):
286+
"""Return True if the test might be wrapped.
287+
288+
In Python 2 unittest's decorators (e.g. unittest.skip) do not wrap
289+
properly, so we must manually unwrap them.
290+
"""
291+
if _pyversion > (3,):
292+
return False
244293
if (_pathsep + 'unittest' + _pathsep + 'case.py') not in srcfile:
245-
return srcfile, lineno
294+
return False
295+
return True
296+
246297

247-
# Unwrap the decorator (e.g. unittest.skip).
248-
srcfile = relfile
249-
lineno = -1
298+
def _unwrap_decorator(func):
299+
"""Return (filename, lineno) for the func the given func wraps.
300+
301+
If the wrapped func cannot be identified then return None. Likewise
302+
for the wrapped filename. "lineno" is None if it cannot be found
303+
but the filename could.
304+
"""
250305
try:
251306
func = func.__closure__[0].cell_contents
252307
except (IndexError, AttributeError):
253-
return srcfile, lineno
308+
return None
254309
else:
255-
if callable(func) and func.__code__.co_filename.endswith(relfile[1:]):
256-
lineno = func.__code__.co_firstlineno - 1
257-
return srcfile, lineno
310+
if not callable(func):
311+
return None
312+
try:
313+
filename = func.__code__.co_filename
314+
except AttributeError:
315+
return None
316+
else:
317+
try:
318+
lineno = func.__code__.co_firstlineno - 1
319+
except AttributeError:
320+
return (filename, None)
321+
else:
322+
return filename, lineno
258323

259324

260325
def _parse_node_id(testid, kind, _pathsep, _normcase):

pythonFiles/tests/testing_tools/adapter/pytest/test_discovery.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -826,11 +826,11 @@ def normcase(path):
826826
name='test_spam',
827827
path=TestPath(
828828
root=testroot,
829-
relfile=r'.\x\y\z\test_eggs.py',
829+
relfile=r'.\X\Y\Z\test_eggs.py',
830830
func='SpamTests.test_spam',
831831
sub=None,
832832
),
833-
source=r'.\x\y\z\test_eggs.py:{}'.format(13),
833+
source=r'.\X\Y\Z\test_eggs.py:{}'.format(13),
834834
markers=None,
835835
parentid=r'.\x\y\z\test_eggs.py::SpamTests',
836836
),

0 commit comments

Comments
 (0)