pip/tests/unit/test_req_file.py

576 lines
20 KiB
Python
Raw Normal View History

import os
import subprocess
import textwrap
2015-05-02 01:04:20 +02:00
from mock import patch, Mock
import pytest
2015-04-07 06:45:10 +02:00
from pretend import stub
import pip
from pip.exceptions import (RequirementsFileParseError)
from pip.download import PipSession
from pip.index import PackageFinder
2015-04-07 06:45:10 +02:00
from pip.req.req_install import InstallRequirement
from pip.req.req_file import (parse_requirements, process_line, join_lines,
ignore_comments, break_args_options, skip_regex,
preprocess)
@pytest.fixture
def session():
return PipSession()
@pytest.fixture
def finder(session):
return PackageFinder([], [], session=session)
@pytest.fixture
def options(session):
return stub(
isolated_mode=False, default_vcs=None, index_url='default_url',
skip_requirements_regex=False,
format_control=pip.index.FormatControl(set(), set()))
class TestPreprocess(object):
"""tests for `preprocess`"""
def test_comments_processed_before_joining_case1(self):
content = textwrap.dedent("""\
req1 \\
# comment \\
req2
""")
result = preprocess(content, None)
assert list(result) == [(1, 'req1 req2')]
def test_comments_processed_before_joining_case2(self):
content = textwrap.dedent("""\
req1\\
# comment
""")
result = preprocess(content, None)
assert list(result) == [(1, 'req1')]
def test_comments_processed_before_joining_case3(self):
content = textwrap.dedent("""\
req1 \\
# comment
req2
""")
result = preprocess(content, None)
assert list(result) == [(1, 'req1 req2')]
def test_skip_regex_after_joining_case1(self, options):
content = textwrap.dedent("""\
patt\\
ern
line2
""")
options.skip_requirements_regex = 'pattern'
result = preprocess(content, options)
assert list(result) == [(3, 'line2')]
def test_skip_regex_after_joining_case2(self, options):
content = textwrap.dedent("""\
pattern \\
line2
line3
""")
options.skip_requirements_regex = 'pattern'
result = preprocess(content, options)
assert list(result) == [(3, 'line3')]
class TestIgnoreComments(object):
"""tests for `ignore_comment`"""
2015-04-07 06:45:10 +02:00
def test_ignore_line(self):
lines = [(1, ''), (2, 'req1'), (3, 'req2')]
2015-04-07 06:45:10 +02:00
result = ignore_comments(lines)
assert list(result) == [(2, 'req1'), (3, 'req2')]
def test_ignore_comment(self):
lines = [(1, 'req1'), (2, '# comment'), (3, 'req2')]
result = ignore_comments(lines)
assert list(result) == [(1, 'req1'), (3, 'req2')]
2015-04-07 06:45:10 +02:00
def test_strip_comment(self):
lines = [(1, 'req1'), (2, 'req # comment'), (3, 'req2')]
2015-04-07 06:45:10 +02:00
result = ignore_comments(lines)
assert list(result) == [(1, 'req1'), (2, 'req'), (3, 'req2')]
class TestJoinLines(object):
"""tests for `join_lines`"""
2015-04-07 06:45:10 +02:00
def test_join_lines(self):
lines = enumerate([
'line 1',
'line 2:1 \\',
'line 2:2',
'line 3:1 \\',
'line 3:2 \\',
'line 3:3',
'line 4'
], start=1)
expect = [
(1, 'line 1'),
(2, 'line 2:1 line 2:2'),
(4, 'line 3:1 line 3:2 line 3:3'),
(7, 'line 4'),
]
assert expect == list(join_lines(lines))
def test_last_line_with_escape(self):
lines = enumerate([
'line 1',
'line 2 \\',
], start=1)
expect = [
(1, 'line 1'),
(2, 'line 2 '),
]
assert expect == list(join_lines(lines))
class TestSkipRegex(object):
"""tests for `skip_reqex``"""
def test_skip_regex_pattern_match(self):
options = stub(skip_requirements_regex='.*Bad.*')
line = '--extra-index-url Bad'
assert [] == list(skip_regex(enumerate([line]), options))
def test_skip_regex_pattern_not_match(self):
options = stub(skip_requirements_regex='.*Bad.*')
line = '--extra-index-url Good'
assert [(0, line)] == list(skip_regex(enumerate([line]), options))
def test_skip_regex_no_options(self):
options = None
line = '--extra-index-url Good'
assert [(0, line)] == list(skip_regex(enumerate([line]), options))
def test_skip_regex_no_skip_option(self):
options = stub(skip_requirements_regex=None)
line = '--extra-index-url Good'
assert [(0, line)] == list(skip_regex(enumerate([line]), options))
2015-09-21 23:22:45 +02:00
class TestProcessLine(object):
"""tests for `process_line`"""
2015-04-07 06:45:10 +02:00
def test_parser_error(self):
with pytest.raises(RequirementsFileParseError):
list(process_line("--bogus", "file", 1))
def test_only_one_req_per_line(self):
# pkg_resources raises the ValueError
with pytest.raises(ValueError):
list(process_line("req1 req2", "file", 1))
def test_yield_line_requirement(self):
line = 'SomeProject'
2015-04-07 06:45:10 +02:00
filename = 'filename'
comes_from = '-r %s (line %s)' % (filename, 1)
req = InstallRequirement.from_line(line, comes_from=comes_from)
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)
2015-04-07 06:45:10 +02:00
def test_yield_line_constraint(self):
line = 'SomeProject'
filename = 'filename'
comes_from = '-c %s (line %s)' % (filename, 1)
req = InstallRequirement.from_line(
line, comes_from=comes_from, constraint=True)
found_req = list(process_line(line, filename, 1, constraint=True))[0]
assert repr(found_req) == repr(req)
assert found_req.constraint is True
def test_yield_line_requirement_with_spaces_in_specifier(self):
line = 'SomeProject >= 2'
filename = 'filename'
comes_from = '-r %s (line %s)' % (filename, 1)
req = InstallRequirement.from_line(line, comes_from=comes_from)
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)
assert req.req.specs == [('>=', '2')]
def test_yield_editable_requirement(self):
2015-04-07 06:45:10 +02:00
url = 'git+https://url#egg=SomeProject'
line = '-e %s' % url
2015-04-07 06:45:10 +02:00
filename = 'filename'
comes_from = '-r %s (line %s)' % (filename, 1)
req = InstallRequirement.from_editable(url, comes_from=comes_from)
assert repr(list(process_line(line, filename, 1))[0]) == repr(req)
2015-04-07 06:45:10 +02:00
def test_yield_editable_constraint(self):
url = 'git+https://url#egg=SomeProject'
line = '-e %s' % url
filename = 'filename'
comes_from = '-c %s (line %s)' % (filename, 1)
req = InstallRequirement.from_editable(
url, comes_from=comes_from, constraint=True)
found_req = list(process_line(line, filename, 1, constraint=True))[0]
assert repr(found_req) == repr(req)
assert found_req.constraint is True
def test_nested_requirements_file(self, monkeypatch):
line = '-r another_file'
2015-04-07 06:45:10 +02:00
req = InstallRequirement.from_line('SomeProject')
import pip.req.req_file
2015-04-08 04:50:40 +02:00
def stub_parse_requirements(req_url, finder, comes_from, options,
session, wheel_cache, constraint):
return [(req, constraint)]
parse_requirements_stub = stub(call=stub_parse_requirements)
monkeypatch.setattr(pip.req.req_file, 'parse_requirements',
parse_requirements_stub.call)
assert list(process_line(line, 'filename', 1)) == [(req, False)]
def test_nested_constraints_file(self, monkeypatch):
line = '-c another_file'
req = InstallRequirement.from_line('SomeProject')
import pip.req.req_file
def stub_parse_requirements(req_url, finder, comes_from, options,
session, wheel_cache, constraint):
return [(req, constraint)]
2015-04-08 04:50:40 +02:00
parse_requirements_stub = stub(call=stub_parse_requirements)
monkeypatch.setattr(pip.req.req_file, 'parse_requirements',
parse_requirements_stub.call)
assert list(process_line(line, 'filename', 1)) == [(req, True)]
def test_options_on_a_requirement_line(self):
line = 'SomeProject --install-option=yo1 --install-option yo2 '\
'--global-option="yo3" --global-option "yo4"'
filename = 'filename'
req = list(process_line(line, filename, 1))[0]
assert req.options == {
'global_options': ['yo3', 'yo4'],
'install_options': ['yo1', 'yo2']}
def test_set_isolated(self, options):
line = 'SomeProject'
filename = 'filename'
options.isolated_mode = True
result = process_line(line, filename, 1, options=options)
assert list(result)[0].isolated
def test_set_default_vcs(self, options):
url = 'https://url#egg=SomeProject'
line = '-e %s' % url
filename = 'filename'
options.default_vcs = 'git'
result = process_line(line, filename, 1, options=options)
assert list(result)[0].link.url == 'git+' + url
def test_set_finder_no_index(self, finder):
list(process_line("--no-index", "file", 1, finder=finder))
assert finder.index_urls == []
def test_set_finder_index_url(self, finder):
list(process_line("--index-url=url", "file", 1, finder=finder))
assert finder.index_urls == ['url']
def test_set_finder_find_links(self, finder):
list(process_line("--find-links=url", "file", 1, finder=finder))
assert finder.find_links == ['url']
def test_set_finder_extra_index_urls(self, finder):
list(process_line("--extra-index-url=url", "file", 1, finder=finder))
assert finder.index_urls == ['url']
def test_set_finder_use_wheel(self, finder):
list(process_line("--use-wheel", "file", 1, finder=finder))
no_use_wheel_fmt = pip.index.FormatControl(set(), set())
assert finder.format_control == no_use_wheel_fmt
def test_set_finder_no_use_wheel(self, finder):
list(process_line("--no-use-wheel", "file", 1, finder=finder))
no_use_wheel_fmt = pip.index.FormatControl(set([':all:']), set())
assert finder.format_control == no_use_wheel_fmt
def test_set_finder_trusted_host(self, finder):
list(process_line("--trusted-host=url", "file", 1, finder=finder))
assert finder.secure_origins == [('*', 'url', '*')]
def test_noop_always_unzip(self, finder):
# noop, but confirm it can be set
list(process_line("--always-unzip", "file", 1, finder=finder))
def test_noop_finder_no_allow_unsafe(self, finder):
# noop, but confirm it can be set
list(process_line("--no-allow-insecure", "file", 1, finder=finder))
def test_set_finder_allow_all_prereleases(self, finder):
list(process_line("--pre", "file", 1, finder=finder))
assert finder.allow_all_prereleases
2015-05-02 01:04:20 +02:00
def test_relative_local_find_links(self, finder, monkeypatch):
"""
Test a relative find_links path is joined with the req file directory
"""
2015-05-02 01:04:20 +02:00
req_file = '/path/req_file.txt'
nested_link = '/path/rel_path'
exists_ = os.path.exists
def exists(path):
if path == nested_link:
return True
else:
exists_(path)
monkeypatch.setattr(os.path, 'exists', exists)
list(process_line("--find-links=rel_path", req_file, 1,
finder=finder))
assert finder.find_links == [nested_link]
def test_relative_http_nested_req_files(self, finder, monkeypatch):
"""
Test a relative nested req file path is joined with the req file url
"""
2015-05-02 01:04:20 +02:00
req_file = 'http://me.com/me/req_file.txt'
def parse(*args, **kwargs):
return iter([])
mock_parse = Mock()
mock_parse.side_effect = parse
monkeypatch.setattr(pip.req.req_file, 'parse_requirements', mock_parse)
list(process_line("-r reqs.txt", req_file, 1, finder=finder))
call = mock_parse.mock_calls[0]
assert call[1][0] == 'http://me.com/me/reqs.txt'
def test_relative_local_nested_req_files(self, finder, monkeypatch):
"""
Test a relative nested req file path is joined with the req file dir
"""
2015-05-02 01:04:20 +02:00
req_file = '/path/req_file.txt'
def parse(*args, **kwargs):
return iter([])
mock_parse = Mock()
mock_parse.side_effect = parse
monkeypatch.setattr(pip.req.req_file, 'parse_requirements', mock_parse)
list(process_line("-r reqs.txt", req_file, 1, finder=finder))
call = mock_parse.mock_calls[0]
assert call[1][0] == '/path/reqs.txt'
def test_absolute_local_nested_req_files(self, finder, monkeypatch):
"""
Test an absolute nested req file path
"""
2015-05-02 01:04:20 +02:00
req_file = '/path/req_file.txt'
def parse(*args, **kwargs):
return iter([])
mock_parse = Mock()
mock_parse.side_effect = parse
monkeypatch.setattr(pip.req.req_file, 'parse_requirements', mock_parse)
list(process_line("-r /other/reqs.txt", req_file, 1, finder=finder))
call = mock_parse.mock_calls[0]
assert call[1][0] == '/other/reqs.txt'
def test_absolute_http_nested_req_file_in_local(self, finder, monkeypatch):
"""
Test a nested req file url in a local req file
"""
2015-05-02 01:04:20 +02:00
req_file = '/path/req_file.txt'
def parse(*args, **kwargs):
return iter([])
mock_parse = Mock()
mock_parse.side_effect = parse
monkeypatch.setattr(pip.req.req_file, 'parse_requirements', mock_parse)
2015-05-02 01:15:52 +02:00
list(process_line("-r http://me.com/me/reqs.txt", req_file, 1,
finder=finder))
2015-05-02 01:04:20 +02:00
call = mock_parse.mock_calls[0]
assert call[1][0] == 'http://me.com/me/reqs.txt'
def test_set_finder_process_dependency_links(self, finder):
list(process_line(
"--process-dependency-links", "file", 1, finder=finder))
assert finder.process_dependency_links
2015-04-18 20:53:14 +02:00
class TestBreakOptionsArgs(object):
def test_no_args(self):
assert ('', '--option') == break_args_options('--option')
def test_no_options(self):
assert ('arg arg', '') == break_args_options('arg arg')
def test_args_short_options(self):
result = break_args_options('arg arg -s')
assert ('arg arg', '-s') == result
def test_args_long_options(self):
result = break_args_options('arg arg --long')
assert ('arg arg', '--long') == result
2015-04-18 20:53:14 +02:00
class TestOptionVariants(object):
# this suite is really just testing optparse, but added it anyway
def test_variant1(self, finder):
list(process_line("-i url", "file", 1, finder=finder))
assert finder.index_urls == ['url']
def test_variant2(self, finder):
list(process_line("-i 'url'", "file", 1, finder=finder))
assert finder.index_urls == ['url']
def test_variant3(self, finder):
list(process_line("--index-url=url", "file", 1, finder=finder))
assert finder.index_urls == ['url']
def test_variant4(self, finder):
2015-04-18 20:53:14 +02:00
list(process_line("--index-url url", "file", 1, finder=finder))
assert finder.index_urls == ['url']
def test_variant5(self, finder):
2015-04-18 20:53:14 +02:00
list(process_line("--index-url='url'", "file", 1, finder=finder))
assert finder.index_urls == ['url']
class TestParseRequirements(object):
"""tests for `parse_requirements`"""
@pytest.mark.network
def test_remote_reqs_parse(self):
"""
Test parsing a simple remote requirements file
"""
2015-04-08 04:50:40 +02:00
# this requirements file just contains a comment previously this has
# failed in py3: https://github.com/pypa/pip/issues/760
for req in parse_requirements(
2015-04-08 04:50:40 +02:00
'https://raw.githubusercontent.com/pypa/'
'pip-test-package/master/'
'tests/req_just_comment.txt', session=PipSession()):
pass
def test_multiple_appending_options(self, tmpdir, finder, options):
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("--extra-index-url url1 \n")
fp.write("--extra-index-url url2 ")
list(parse_requirements(tmpdir.join("req1.txt"), finder=finder,
session=PipSession(), options=options))
assert finder.index_urls == ['url1', 'url2']
def test_skip_regex(self, tmpdir, finder, options):
options.skip_requirements_regex = '.*Bad.*'
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("--extra-index-url Bad \n")
fp.write("--extra-index-url Good ")
list(parse_requirements(tmpdir.join("req1.txt"), finder=finder,
options=options, session=PipSession()))
assert finder.index_urls == ['Good']
def test_join_lines(self, tmpdir, finder):
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("--extra-index-url url1 \\\n--extra-index-url url2")
list(parse_requirements(tmpdir.join("req1.txt"), finder=finder,
session=PipSession()))
assert finder.index_urls == ['url1', 'url2']
def test_req_file_parse_no_only_binary(self, data, finder):
list(parse_requirements(
data.reqfiles.join("supported_options2.txt"), finder,
session=PipSession()))
expected = pip.index.FormatControl(set(['fred']), set(['wilma']))
assert finder.format_control == expected
def test_req_file_parse_comment_start_of_line(self, tmpdir, finder):
"""
Test parsing comments in a requirements file
"""
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("# Comment ")
reqs = list(parse_requirements(tmpdir.join("req1.txt"), finder,
session=PipSession()))
assert not reqs
def test_req_file_parse_comment_end_of_line_with_url(self, tmpdir, finder):
"""
Test parsing comments in a requirements file
"""
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("https://example.com/foo.tar.gz # Comment ")
reqs = list(parse_requirements(tmpdir.join("req1.txt"), finder,
session=PipSession()))
assert len(reqs) == 1
assert reqs[0].link.url == "https://example.com/foo.tar.gz"
def test_req_file_parse_egginfo_end_of_line_with_url(self, tmpdir, finder):
"""
Test parsing comments in a requirements file
"""
with open(tmpdir.join("req1.txt"), "w") as fp:
fp.write("https://example.com/foo.tar.gz#egg=wat")
reqs = list(parse_requirements(tmpdir.join("req1.txt"), finder,
session=PipSession()))
assert len(reqs) == 1
assert reqs[0].name == "wat"
def test_req_file_no_finder(self, tmpdir):
"""
Test parsing a requirements file without a finder
"""
with open(tmpdir.join("req.txt"), "w") as fp:
fp.write("""
--find-links https://example.com/
--index-url https://example.com/
--extra-index-url https://two.example.com/
--no-use-wheel
--no-index
""")
parse_requirements(tmpdir.join("req.txt"), session=PipSession())
2015-04-25 07:37:29 +02:00
def test_install_requirements_with_options(self, tmpdir, finder, session,
options):
global_option = '--dry-run'
install_option = '--prefix=/opt'
content = '''
--only-binary :all:
INITools==2.0 --global-option="{global_option}" \
--install-option "{install_option}"
'''.format(global_option=global_option, install_option=install_option)
req_path = tmpdir.join('requirements.txt')
with open(req_path, 'w') as fh:
fh.write(content)
req = next(parse_requirements(
req_path, finder=finder, options=options, session=session))
req.source_dir = os.curdir
with patch.object(subprocess, 'Popen') as popen:
2015-04-16 00:06:11 +02:00
popen.return_value.stdout.readline.return_value = ""
try:
req.install([])
except:
pass
call = popen.call_args_list[0][0][0]
assert call.index(install_option) > \
call.index('install') > \
call.index(global_option) > 0
assert options.format_control.no_binary == set([':all:'])
assert options.format_control.only_binary == set([])