-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Fix assertion rewriter crash if cwd changes mid-testing #3980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix assertion rewriter crash if cwd changes mid-testing #3980
Conversation
src/_pytest/assertion/rewrite.py
Outdated
| fn_pypath = py.path.local(os.path.sep.join(parts)) | ||
| try: | ||
| fn_pypath = py.path.local(os.path.sep.join(parts)) | ||
| except EnvironmentError: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not happy with that approach, and I'm open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets use a pathlib path instance or a string instead, i - anything thats not turning itself into a absolute path by force will be good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem is the fnmatch call we need to perform a little further down in the code, see my description on the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't know about that function TBH, but the main point is that py.path.local.fmatch has its own implementation, which we know from past experience (#2140) that it diverges from the standard fnmatch module and from pathlib2.PurePath.match.
If we use different fnmatch algorithms, I'm afraid we will start to see false positives here (like happened in #2140) and not rewrite some modules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i reviewed pathlib.Path.match - it does it completely correct and better than localpath - as such i consider a port to pathlib required
|
Let's extract the smarter than thou fnmatcher then |
RonnyPfannschmidt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets use pathlib with purepaths here
|
Thanks for the leg work. I wrote a test to check the compatibility, and they do match the same paths a lot, with a few exceptions: import os
import sys
import py
import pytest
from _pytest.compat import PurePath
class Test:
@pytest.fixture(params=['py.path', 'pathlib'])
def match(self, request):
if request.param == 'py.path':
def match_(pattern, path):
return py.path.local(path).fnmatch(pattern)
else:
assert request.param == 'pathlib'
def match_(pattern, path):
return PurePath(path).match(pattern)
return match_
if sys.platform == 'win32':
drv1 = 'c:'
drv2 = 'd:'
else:
drv1 = ''
drv2 = ''
@pytest.mark.parametrize('pattern, path', [
('*.py', 'foo.py'),
('*.py', 'foo/foo.py'),
('tests/*.py', 'tests/foo.py'),
(drv1 + '/*.py', drv1 + '/foo.py'),
(drv1 + '/foo/*.py', drv1 + '/foo/foo.py'),
('tests/**/test*.py', 'tests/test_foo.py'),
('tests/**/test*.py', 'tests/foo/test_foo.py'),
('tests/**/doc/test*.py', 'tests/foo/bar/doc/test_foo.py'),
('tests/**/doc/**/test*.py', 'tests/foo/doc/bar/test_foo.py'),
])
@pytest.mark.parametrize('normalize', [ False])
def test_matching(self, match, pattern, path, normalize):
assert match(pattern, path if not normalize else os.path.normpath(path))
@pytest.mark.parametrize('pattern, path', [
('*.py', 'foo.pyc'),
('*.py', 'foo/foo.pyc'),
('tests/*.py', 'foo/foo.py'),
(drv1 + '/*.py', drv2 + '/foo.py'),
(drv1 + '/foo/*.py', drv2 + '/foo/foo.py'),
('tests/**/test*.py', 'tests/foo.py'),
('tests/**/test*.py', 'foo/test_foo.py'),
('tests/**/doc/test*.py', 'tests/foo/bar/doc/foo.py'),
('tests/**/doc/test*.py', 'tests/foo/bar/test_foo.py'),
])
@pytest.mark.parametrize('normalize', [True, False])
def test_not_matching(self, match, pattern, path, normalize):
assert not match(pattern, path if not normalize else os.path.normpath(path))Here are the failures on my system: |
|
thanks for the detail analysis test automation - i believe this is caused by path libs segment splitting, which sorts out different pathological cases than pytest does |
|
Yep that was my analysis as well. How do you like us to move forward? I can port the What are your thoughts? |
|
lets go for path.local right now ,record the finding and plan it as a breaking change - since it is one (stuff like this is how setuptools is >40 today |
Just to confirm, you mean porting FNMatcher correct? |
|
@nicoddemus no - i mean still using a py.path.local as is right now and preparing for the breaking release later there is zero value in porting fnmatcher now if we will kill it later - we dont have time for that kind of high cost throw-away code |
|
Oh OK. Anything else you would like to do here then? |
RonnyPfannschmidt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, sorry for forgetting the approval
|
@RonnyPfannschmidt I was thinking about this a little more in the shower today: the FNMatcher implementation is really simple, and it does allow for matches that |
|
@nicoddemus in that case i'll agree in which case i'd also like to see a bug report against pathlib accompanying it |
Unfortunately we need to get a `py.path.local` object to perform the fnmatch operation, it is different from the standard `fnmatch` module because it implements its own custom logic. So we need to use `py.path` to perform the fnmatch for backward compatibility reasons. Ideally we should be able to use a "pure path" in `pathlib` terms (a path not bound to the file system), but we don't have those in pylib. Fix pytest-dev#3973
2a5ad99 to
f642728
Compare
…rewrite This way we don't need to have real file system path, which prevents the original pytest-dev#3973 bug.
f642728 to
37d2469
Compare
|
@RonnyPfannschmidt ready for review (ignore the CI failure as that's due to codecov) |
RonnyPfannschmidt
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is fabulously done, i'd like to report a bug against python still to refer it here (i consider it a oversight in pathlib)
|
https://bugs.python.org/issue34731 tracks this for python - lets add a reference, then its ready to merge |
|
https://bugs.python.org/issue29249 is an older issue thats the relevant one |
Thanks for searching the relevant issues, done: 1e2e65f |
Codecov Report
@@ Coverage Diff @@
## master #3980 +/- ##
==========================================
+ Coverage 94.48% 94.53% +0.05%
==========================================
Files 107 108 +1
Lines 23663 23703 +40
Branches 2349 2353 +4
==========================================
+ Hits 22358 22408 +50
+ Misses 994 988 -6
+ Partials 311 307 -4
Continue to review full report at Codecov.
|
Unfortunately we need to get a
py.path.localobject to perform the fnmatchoperation, it is different from the standard
fnmatchmodule because itimplements its own custom logic. So we need to use
py.pathto performthe fnmatch for backward compatibility reasons.
Ideally we should be able to use a "pure path" in
pathlibterms (a pathnot bound to the file system), but we don't have those in pylib.
Fix #3973