-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
warn before overwriting files owned by previously installed wheels #8119
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Warn when a file owned by another distribution is overwritten. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
""" | ||
Utility functions for building messages. | ||
""" | ||
|
||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
|
||
if MYPY_CHECK_RUNNING: | ||
from typing import List | ||
|
||
|
||
def oxford_comma_join(values, conjunction='and'): | ||
# type: (List[str], str) -> str | ||
"Join a list of strings for output in a message." | ||
if not values: | ||
return '' | ||
if len(values) == 1: | ||
return values[0] | ||
comma = '' | ||
if len(values) > 2: | ||
comma = ',' | ||
return '{}{} {} {}'.format( | ||
', '.join(values[:-1]), | ||
comma, | ||
conjunction, | ||
values[-1], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -635,3 +635,126 @@ def test_correct_package_name_while_creating_wheel_bug(script, package_name): | |
package = create_basic_wheel_for_package(script, package_name, '1.0') | ||
wheel_name = os.path.basename(package) | ||
assert wheel_name == 'simple_package-1.0-py2.py3-none-any.whl' | ||
|
||
|
||
def test_report_file_owner_conflicts(script, tmpdir): | ||
""" | ||
Test installing from a wheel that wants to overwrite | ||
files owned by another wheel to ensure that a warning | ||
is reported and that the second wheel does overwrite | ||
the first. | ||
""" | ||
from tests.lib import TestFailure | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see a few other tests in this file importing this inside the test. Maybe it's a good time to move this import along with the others at the top? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just following the local coding patterns in this PR. Changing all of that seems like a task for another PR. |
||
|
||
make_wheel_with_file( | ||
name="wheel1", | ||
version="1.0", | ||
extra_files={ | ||
'thetop/a.py': '# from wheel1', | ||
}, | ||
extra_metadata_files={ | ||
'top_level.txt': 'thetop', | ||
}, | ||
).save_to_dir(tmpdir) | ||
|
||
make_wheel_with_file( | ||
name="wheel2", | ||
version="2.0", | ||
extra_files={ | ||
'thetop/a.py': '# from wheel2', | ||
}, | ||
extra_metadata_files={ | ||
'top_level.txt': 'thetop', | ||
}, | ||
).save_to_dir(tmpdir) | ||
|
||
result = script.pip( | ||
'install', 'wheel1', 'wheel2', '--no-index', | ||
'--find-links', tmpdir, | ||
) | ||
|
||
# FIXME: Both wheels should be installed, for now. In the future | ||
# when the warning is changed to a hard error this part of the | ||
# test needs to be updated to show that only wheel1 is installed. | ||
with pytest.raises(TestFailure): | ||
result.assert_installed('wheel1') | ||
result.assert_installed('wheel2') | ||
|
||
# FIXME: When the warning is changed to a hard error, wheel2 won't | ||
# be installed and the file contents will be those from wheel1. | ||
thetop = script.site_packages_path / 'thetop' | ||
assert (thetop / 'a.py').read_text() == '# from wheel2' | ||
|
||
full_path = thetop / 'a.py' | ||
msg = 'Overwriting or removing {} for {} which is also owned by {}'.format( | ||
full_path, 'wheel2', 'wheel1 1.0') | ||
assert msg in result.stderr | ||
|
||
|
||
def test_report_file_owner_conflicts_multiple(script, tmpdir): | ||
""" | ||
Test installing from a wheel that wants to overwrite | ||
files owned by two other wheels to ensure that a warning | ||
is reported and that the second wheel does overwrite | ||
the first. | ||
""" | ||
from tests.lib import TestFailure | ||
|
||
make_wheel_with_file( | ||
name="wheel1", | ||
version="1.0", | ||
extra_files={ | ||
'thetop/a.py': '# from wheel1', | ||
}, | ||
extra_metadata_files={ | ||
'top_level.txt': 'thetop', | ||
}, | ||
).save_to_dir(tmpdir) | ||
|
||
make_wheel_with_file( | ||
name="wheel2", | ||
version="2.0", | ||
extra_files={ | ||
'thetop/a.py': '# from wheel2', | ||
}, | ||
extra_metadata_files={ | ||
'top_level.txt': 'thetop', | ||
}, | ||
).save_to_dir(tmpdir) | ||
|
||
make_wheel_with_file( | ||
name="wheel3", | ||
version="3.0", | ||
extra_files={ | ||
'thetop/a.py': '# from wheel3', | ||
}, | ||
extra_metadata_files={ | ||
'top_level.txt': 'thetop', | ||
}, | ||
).save_to_dir(tmpdir) | ||
|
||
result = script.pip( | ||
'install', 'wheel1', 'wheel2', 'wheel3', '--no-index', | ||
'--find-links', tmpdir, | ||
) | ||
|
||
# FIXME: Both wheels should be installed, for now. In the future | ||
# when the warning is changed to a hard error this part of the | ||
# test needs to be updated to show that only wheel1 is installed. | ||
with pytest.raises(TestFailure): | ||
result.assert_installed('wheel1') | ||
result.assert_installed('wheel2') | ||
|
||
# FIXME: When the warning is changed to a hard error, wheel2 won't | ||
# be installed and the file contents will be those from wheel1. | ||
thetop = script.site_packages_path / 'thetop' | ||
assert (thetop / 'a.py').read_text() == '# from wheel3' | ||
|
||
full_path = thetop / 'a.py' | ||
msg = 'Overwriting or removing {} for {} which is also owned by {}'.format( | ||
full_path, 'wheel2', 'wheel1 1.0') | ||
assert msg in result.stderr | ||
|
||
msg = 'Overwriting or removing {} for {} which is also owned by {}'.format( | ||
full_path, 'wheel3', 'wheel1 1.0 and wheel2 2.0') | ||
assert msg in result.stderr |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"""Tests for pip._internal.messages | ||
""" | ||
|
||
import pytest | ||
|
||
from pip._internal.utils.messages import oxford_comma_join | ||
|
||
|
||
@pytest.mark.parametrize("test_input,expected", | ||
[([], ''), | ||
(['a'], 'a'), | ||
(['a', 'b'], 'a and b'), | ||
(['a', 'b', 'c'], 'a, b, and c')]) | ||
def test_oxford_comma_join_implicit_conjunction(test_input, expected): | ||
assert expected == oxford_comma_join(test_input) | ||
|
||
|
||
@pytest.mark.parametrize("test_input,conjunction,expected", | ||
[(['a', 'b'], 'and', 'a and b',), | ||
(['a', 'b'], 'or', 'a or b')]) | ||
def test_oxford_comma_join_explicit_conjunction( | ||
test_input, conjunction, expected): | ||
assert expected == oxford_comma_join(test_input, conjunction) |
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 don't think it should be necessary to ignore the installing package, since we should have uninstalled the existing distribution before trying to install a new version.
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.
Eventually a file conflict will be an error that prevents installation. We want to report the error before removing the existing version and breaking the environment.
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.
We currently have a rollback mechanism that reverts an upgrading package on any installation failure. I think this should have us covered, if I understand the concern you're raising.