flit/tests/test_install.py

366 lines
15 KiB
Python

import json
import os
import pathlib
import sys
import tempfile
from unittest import TestCase, SkipTest
from unittest.mock import patch
import pytest
from testpath import (
assert_isfile, assert_isdir, assert_islink, assert_not_path_exists, MockCommand
)
from flit import install
from flit.install import Installer, _requires_dist_to_pip_requirement, DependencyError
import flit_core.tests
samples_dir = pathlib.Path(__file__).parent / 'samples'
core_samples_dir = pathlib.Path(flit_core.tests.__file__).parent / 'samples'
class InstallTests(TestCase):
def setUp(self):
td = tempfile.TemporaryDirectory()
self.addCleanup(td.cleanup)
self.get_dirs_patch = patch('flit.install.get_dirs',
return_value={
'scripts': os.path.join(td.name, 'scripts'),
'purelib': os.path.join(td.name, 'site-packages'),
'data': os.path.join(td.name, 'data'),
})
self.get_dirs_patch.start()
self.tmpdir = pathlib.Path(td.name)
def tearDown(self):
self.get_dirs_patch.stop()
def _assert_direct_url(self, directory, package, version, expected_editable):
direct_url_file = (
self.tmpdir
/ 'site-packages'
/ '{}-{}.dist-info'.format(package, version)
/ 'direct_url.json'
)
assert_isfile(direct_url_file)
with direct_url_file.open() as f:
direct_url = json.load(f)
assert direct_url['url'].startswith('file:///')
assert direct_url['url'] == directory.as_uri()
assert direct_url['dir_info'].get('editable') is expected_editable
def test_install_module(self):
Installer.from_ini_path(samples_dir / 'module1_toml' / 'pyproject.toml').install_directly()
assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
self._assert_direct_url(
samples_dir / 'module1_toml', 'module1', '0.1', expected_editable=False
)
def test_install_module_pep621(self):
Installer.from_ini_path(
core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml',
).install_directly()
assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info')
self._assert_direct_url(
core_samples_dir / 'pep621_nodynamic', 'module1', '0.3',
expected_editable=False
)
def test_install_package(self):
oldcwd = os.getcwd()
os.chdir(str(samples_dir / 'package1'))
try:
Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly()
finally:
os.chdir(oldcwd)
assert_isdir(self.tmpdir / 'site-packages' / 'package1')
assert_isdir(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info')
assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')
with (self.tmpdir / 'scripts' / 'pkg_script').open() as f:
assert f.readline().strip() == "#!" + sys.executable
self._assert_direct_url(
samples_dir / 'package1', 'package1', '0.1', expected_editable=False
)
def test_install_module_in_src(self):
oldcwd = os.getcwd()
os.chdir(samples_dir / 'packageinsrc')
try:
Installer.from_ini_path(pathlib.Path('pyproject.toml')).install_directly()
finally:
os.chdir(oldcwd)
assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
def test_install_ns_package_native(self):
Installer.from_ini_path(samples_dir / 'ns1-pkg' / 'pyproject.toml').install_directly()
assert_isdir(self.tmpdir / 'site-packages' / 'ns1')
assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'pkg' / '__init__.py')
assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py')
assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info')
def test_install_ns_package_module_native(self):
Installer.from_ini_path(samples_dir / 'ns1-pkg-mod' / 'pyproject.toml').install_directly()
assert_isfile(self.tmpdir / 'site-packages' / 'ns1' / 'module.py')
assert_not_path_exists(self.tmpdir / 'site-packages' / 'ns1' / '__init__.py')
def test_install_ns_package_native_symlink(self):
if os.name == 'nt':
raise SkipTest('symlink')
Installer.from_ini_path(
samples_dir / 'ns1-pkg' / 'pyproject.toml', symlink=True
).install_directly()
Installer.from_ini_path(
samples_dir / 'ns1-pkg2' / 'pyproject.toml', symlink=True
).install_directly()
Installer.from_ini_path(
samples_dir / 'ns1-pkg-mod' / 'pyproject.toml', symlink=True
).install_directly()
assert_isdir(self.tmpdir / 'site-packages' / 'ns1')
assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg')
assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg',
to=str(samples_dir / 'ns1-pkg' / 'ns1' / 'pkg'))
assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg-0.1.dist-info')
assert_isdir(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2')
assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'pkg2',
to=str(samples_dir / 'ns1-pkg2' / 'ns1' / 'pkg2'))
assert_isdir(self.tmpdir / 'site-packages' / 'ns1_pkg2-0.1.dist-info')
assert_islink(self.tmpdir / 'site-packages' / 'ns1' / 'module.py',
to=samples_dir / 'ns1-pkg-mod' / 'ns1' / 'module.py')
assert_isdir(self.tmpdir / 'site-packages' / 'ns1_module-0.1.dist-info')
def test_install_ns_package_pth_file(self):
Installer.from_ini_path(
samples_dir / 'ns1-pkg' / 'pyproject.toml', pth=True
).install_directly()
pth_file = self.tmpdir / 'site-packages' / 'ns1.pkg.pth'
assert_isfile(pth_file)
assert pth_file.read_text('utf-8').strip() == str(samples_dir / 'ns1-pkg')
def test_symlink_package(self):
if os.name == 'nt':
raise SkipTest("symlink")
Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', symlink=True).install()
assert_islink(self.tmpdir / 'site-packages' / 'package1',
to=samples_dir / 'package1' / 'package1')
assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')
with (self.tmpdir / 'scripts' / 'pkg_script').open() as f:
assert f.readline().strip() == "#!" + sys.executable
self._assert_direct_url(
samples_dir / 'package1', 'package1', '0.1', expected_editable=True
)
def test_symlink_module_pep621(self):
if os.name == 'nt':
raise SkipTest("symlink")
Installer.from_ini_path(
core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml', symlink=True
).install_directly()
assert_islink(self.tmpdir / 'site-packages' / 'module1.py',
to=core_samples_dir / 'pep621_nodynamic' / 'module1.py')
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.3.dist-info')
self._assert_direct_url(
core_samples_dir / 'pep621_nodynamic', 'module1', '0.3',
expected_editable=True
)
def test_symlink_module_in_src(self):
if os.name == 'nt':
raise SkipTest("symlink")
oldcwd = os.getcwd()
os.chdir(samples_dir / 'packageinsrc')
try:
Installer.from_ini_path(
pathlib.Path('pyproject.toml'), symlink=True
).install_directly()
finally:
os.chdir(oldcwd)
assert_islink(self.tmpdir / 'site-packages' / 'module1.py',
to=(samples_dir / 'packageinsrc' / 'src' / 'module1.py'))
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
def test_pth_package(self):
Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', pth=True).install()
assert_isfile(self.tmpdir / 'site-packages' / 'package1.pth')
with open(str(self.tmpdir / 'site-packages' / 'package1.pth')) as f:
assert f.read() == str(samples_dir / 'package1')
assert_isfile(self.tmpdir / 'scripts' / 'pkg_script')
self._assert_direct_url(
samples_dir / 'package1', 'package1', '0.1', expected_editable=True
)
def test_pth_module_in_src(self):
oldcwd = os.getcwd()
os.chdir(samples_dir / 'packageinsrc')
try:
Installer.from_ini_path(
pathlib.Path('pyproject.toml'), pth=True
).install_directly()
finally:
os.chdir(oldcwd)
pth_path = self.tmpdir / 'site-packages' / 'module1.pth'
assert_isfile(pth_path)
assert pth_path.read_text('utf-8').strip() == str(
samples_dir / 'packageinsrc' / 'src'
)
assert_isdir(self.tmpdir / 'site-packages' / 'module1-0.1.dist-info')
def test_dist_name(self):
Installer.from_ini_path(samples_dir / 'altdistname' / 'pyproject.toml').install_directly()
assert_isdir(self.tmpdir / 'site-packages' / 'package1')
assert_isdir(self.tmpdir / 'site-packages' / 'package_dist1-0.1.dist-info')
def test_entry_points(self):
Installer.from_ini_path(samples_dir / 'entrypoints_valid' / 'pyproject.toml').install_directly()
assert_isfile(self.tmpdir / 'site-packages' / 'package1-0.1.dist-info' / 'entry_points.txt')
def test_pip_install(self):
ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python',
user=False)
with MockCommand('mock_python') as mock_py:
ins.install()
calls = mock_py.get_calls()
assert len(calls) == 1
cmd = calls[0]['argv']
assert cmd[1:4] == ['-m', 'pip', 'install']
assert cmd[4].endswith('package1')
def test_symlink_other_python(self):
if os.name == 'nt':
raise SkipTest('symlink')
(self.tmpdir / 'site-packages2').mkdir()
(self.tmpdir / 'scripts2').mkdir()
# Called by Installer._auto_user() :
script1 = ("#!{python}\n"
"import sysconfig\n"
"print(True)\n" # site.ENABLE_USER_SITE
"print({purelib!r})" # sysconfig.get_path('purelib')
).format(python=sys.executable,
purelib=str(self.tmpdir / 'site-packages2'))
# Called by Installer._get_dirs() :
script2 = ("#!{python}\n"
"import json, sys\n"
"json.dump({{'purelib': {purelib!r}, 'scripts': {scripts!r}, 'data': {data!r} }}, "
"sys.stdout)"
).format(python=sys.executable,
purelib=str(self.tmpdir / 'site-packages2'),
scripts=str(self.tmpdir / 'scripts2'),
data=str(self.tmpdir / 'data'),
)
with MockCommand('mock_python', content=script1):
ins = Installer.from_ini_path(samples_dir / 'package1' / 'pyproject.toml', python='mock_python',
symlink=True)
with MockCommand('mock_python', content=script2):
ins.install()
assert_islink(self.tmpdir / 'site-packages2' / 'package1',
to=samples_dir / 'package1' / 'package1')
assert_isfile(self.tmpdir / 'scripts2' / 'pkg_script')
with (self.tmpdir / 'scripts2' / 'pkg_script').open() as f:
assert f.readline().strip() == "#!mock_python"
def test_install_requires(self):
ins = Installer.from_ini_path(samples_dir / 'requires-requests.toml',
user=False, python='mock_python')
with MockCommand('mock_python') as mockpy:
ins.install_requirements()
calls = mockpy.get_calls()
assert len(calls) == 1
assert calls[0]['argv'][1:5] == ['-m', 'pip', 'install', '-r']
def test_install_reqs_my_python_if_needed_pep621(self):
ins = Installer.from_ini_path(
core_samples_dir / 'pep621_nodynamic' / 'pyproject.toml',
deps='none',
)
# This shouldn't try to get version & docstring from the module
ins.install_reqs_my_python_if_needed()
def test_extras_error(self):
with pytest.raises(DependencyError):
Installer.from_ini_path(samples_dir / 'requires-requests.toml',
user=False, deps='none', extras='dev')
def test_install_data_dir(self):
Installer.from_ini_path(
core_samples_dir / 'with_data_dir' / 'pyproject.toml',
).install_directly()
assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
assert_isfile(self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1')
def test_symlink_data_dir(self):
if os.name == 'nt':
raise SkipTest("symlink")
Installer.from_ini_path(
core_samples_dir / 'with_data_dir' / 'pyproject.toml', symlink=True
).install_directly()
assert_isfile(self.tmpdir / 'site-packages' / 'module1.py')
assert_islink(
self.tmpdir / 'data' / 'share' / 'man' / 'man1' / 'foo.1',
to=core_samples_dir / 'with_data_dir' / 'data' / 'share' / 'man' / 'man1' / 'foo.1'
)
@pytest.mark.parametrize(('deps', 'extras', 'installed'), [
('none', [], set()),
('develop', [], {'pytest ;', 'toml ;'}),
('production', [], {'toml ;'}),
('all', [], {'toml ;', 'pytest ;', 'requests ;'}),
])
def test_install_requires_extra(deps, extras, installed):
it = InstallTests()
try:
it.setUp()
ins = Installer.from_ini_path(samples_dir / 'extras' / 'pyproject.toml', python='mock_python',
user=False, deps=deps, extras=extras)
cmd = MockCommand('mock_python')
get_reqs = (
"#!{python}\n"
"import sys\n"
"with open({recording_file!r}, 'wb') as w, open(sys.argv[-1], 'rb') as r:\n"
" w.write(r.read())"
).format(python=sys.executable, recording_file=cmd.recording_file)
cmd.content = get_reqs
with cmd as mock_py:
ins.install_requirements()
with open(mock_py.recording_file) as f:
str_deps = f.read()
deps = str_deps.split('\n') if str_deps else []
assert set(deps) == installed
finally:
it.tearDown()
def test_requires_dist_to_pip_requirement():
rd = 'pathlib2 (>=2.3); python_version == "2.7"'
assert _requires_dist_to_pip_requirement(rd) == \
'pathlib2>=2.3 ; python_version == "2.7"'
def test_test_writable_dir_win():
with tempfile.TemporaryDirectory() as td:
assert install._test_writable_dir_win(td) is True
# Ironically, I don't know how to make a non-writable dir on Windows,
# so although the functionality is for Windows, the test is for Posix
if os.name != 'posix':
return
# Remove write permissions from the directory
os.chmod(td, 0o444)
try:
assert install._test_writable_dir_win(td) is False
finally:
os.chmod(td, 0o644)