summaryrefslogtreecommitdiffstats
path: root/tests/test_env.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_env.py')
-rw-r--r--tests/test_env.py177
1 files changed, 177 insertions, 0 deletions
diff --git a/tests/test_env.py b/tests/test_env.py
new file mode 100644
index 0000000..f6f381a
--- /dev/null
+++ b/tests/test_env.py
@@ -0,0 +1,177 @@
+# SPDX-License-Identifier: MIT
+import collections
+import inspect
+import logging
+import platform
+import subprocess
+import sys
+import sysconfig
+
+import pytest
+
+from packaging.version import Version
+
+import build.env
+
+
+IS_PYPY3 = platform.python_implementation() == 'PyPy'
+
+
+@pytest.mark.isolated
+def test_isolation():
+ subprocess.check_call([sys.executable, '-c', 'import build.env'])
+ with build.env.IsolatedEnvBuilder() as env:
+ with pytest.raises(subprocess.CalledProcessError):
+ debug = 'import sys; import os; print(os.linesep.join(sys.path));'
+ subprocess.check_call([env.executable, '-c', f'{debug} import build.env'])
+
+
+@pytest.mark.isolated
+def test_isolated_environment_install(mocker):
+ with build.env.IsolatedEnvBuilder() as env:
+ mocker.patch('build.env._subprocess')
+
+ env.install([])
+ build.env._subprocess.assert_not_called()
+
+ env.install(['some', 'requirements'])
+ build.env._subprocess.assert_called()
+ args = build.env._subprocess.call_args[0][0][:-1]
+ assert args == [
+ env.executable,
+ '-Im',
+ 'pip',
+ 'install',
+ '--use-pep517',
+ '--no-warn-script-location',
+ '-r',
+ ]
+
+
+@pytest.mark.skipif(IS_PYPY3, reason='PyPy3 uses get path to create and provision venv')
+@pytest.mark.skipif(sys.platform != 'darwin', reason='workaround for Apple Python')
+def test_can_get_venv_paths_with_conflicting_default_scheme(mocker):
+ get_scheme_names = mocker.patch('sysconfig.get_scheme_names', return_value=('osx_framework_library',))
+ with build.env.IsolatedEnvBuilder():
+ pass
+ assert get_scheme_names.call_count == 1
+
+
+@pytest.mark.skipif('posix_local' not in sysconfig.get_scheme_names(), reason='workaround for Debian/Ubuntu Python')
+def test_can_get_venv_paths_with_posix_local_default_scheme(mocker):
+ get_paths = mocker.spy(sysconfig, 'get_paths')
+ # We should never call this, but we patch it to ensure failure if we do
+ get_default_scheme = mocker.patch('sysconfig.get_default_scheme', return_value='posix_local')
+ with build.env.IsolatedEnvBuilder():
+ pass
+ get_paths.assert_called_once_with(scheme='posix_prefix', vars=mocker.ANY)
+ assert get_default_scheme.call_count == 0
+
+
+def test_executable_missing_post_creation(mocker):
+ venv_create = mocker.patch('venv.EnvBuilder.create')
+ with pytest.raises(RuntimeError, match='Virtual environment creation failed, executable .* missing'):
+ with build.env.IsolatedEnvBuilder():
+ pass
+ assert venv_create.call_count == 1
+
+
+def test_isolated_env_abstract():
+ with pytest.raises(TypeError):
+ build.env.IsolatedEnv()
+
+
+def test_isolated_env_has_executable_still_abstract():
+ class Env(build.env.IsolatedEnv):
+ @property
+ def executable(self):
+ raise NotImplementedError
+
+ with pytest.raises(TypeError):
+ Env()
+
+
+def test_isolated_env_has_install_still_abstract():
+ class Env(build.env.IsolatedEnv):
+ def install(self, requirements):
+ raise NotImplementedError
+
+ with pytest.raises(TypeError):
+ Env()
+
+
+@pytest.mark.pypy3323bug
+def test_isolated_env_log(mocker, caplog, package_test_flit):
+ mocker.patch('build.env._subprocess')
+ caplog.set_level(logging.DEBUG)
+
+ builder = build.env.IsolatedEnvBuilder()
+ frameinfo = inspect.getframeinfo(inspect.currentframe())
+ builder.log('something') # line number 106
+ with builder as env:
+ env.install(['something'])
+
+ assert [(record.levelname, record.message) for record in caplog.records] == [
+ ('INFO', 'something'),
+ ('INFO', 'Creating venv isolated environment...'),
+ ('INFO', 'Installing packages in isolated environment... (something)'),
+ ]
+ if sys.version_info >= (3, 8): # stacklevel
+ assert [(record.lineno) for record in caplog.records] == [
+ frameinfo.lineno + 1,
+ frameinfo.lineno - 6,
+ frameinfo.lineno + 85,
+ ]
+
+
+@pytest.mark.isolated
+def test_default_pip_is_never_too_old():
+ with build.env.IsolatedEnvBuilder() as env:
+ version = subprocess.check_output(
+ [env.executable, '-c', 'import pip; print(pip.__version__)'], universal_newlines=True
+ ).strip()
+ assert Version(version) >= Version('19.1')
+
+
+@pytest.mark.isolated
+@pytest.mark.parametrize('pip_version', ['20.2.0', '20.3.0', '21.0.0', '21.0.1'])
+@pytest.mark.parametrize('arch', ['x86_64', 'arm64'])
+def test_pip_needs_upgrade_mac_os_11(mocker, pip_version, arch):
+ SimpleNamespace = collections.namedtuple('SimpleNamespace', 'version')
+
+ _subprocess = mocker.patch('build.env._subprocess')
+ mocker.patch('platform.system', return_value='Darwin')
+ mocker.patch('platform.machine', return_value=arch)
+ mocker.patch('platform.mac_ver', return_value=('11.0', ('', '', ''), ''))
+ metadata_name = 'importlib_metadata' if sys.version_info < (3, 8) else 'importlib.metadata'
+ mocker.patch(metadata_name + '.distributions', return_value=(SimpleNamespace(version=pip_version),))
+
+ min_version = Version('20.3' if arch == 'x86_64' else '21.0.1')
+ with build.env.IsolatedEnvBuilder():
+ if Version(pip_version) < min_version:
+ print(_subprocess.call_args_list)
+ upgrade_call, uninstall_call = _subprocess.call_args_list
+ answer = 'pip>=20.3.0' if arch == 'x86_64' else 'pip>=21.0.1'
+ assert upgrade_call[0][0][1:] == ['-m', 'pip', 'install', answer]
+ assert uninstall_call[0][0][1:] == ['-m', 'pip', 'uninstall', 'setuptools', '-y']
+ else:
+ (uninstall_call,) = _subprocess.call_args_list
+ assert uninstall_call[0][0][1:] == ['-m', 'pip', 'uninstall', 'setuptools', '-y']
+
+
+@pytest.mark.isolated
+@pytest.mark.skipif(IS_PYPY3 and sys.platform.startswith('win'), reason='Isolated tests not supported on PyPy3 + Windows')
+@pytest.mark.parametrize('has_symlink', [True, False] if sys.platform.startswith('win') else [True])
+def test_venv_symlink(mocker, has_symlink):
+ if has_symlink:
+ mocker.patch('os.symlink')
+ mocker.patch('os.unlink')
+ else:
+ mocker.patch('os.symlink', side_effect=OSError())
+
+ # Cache must be cleared to rerun
+ build.env._fs_supports_symlink.cache_clear()
+ supports_symlink = build.env._fs_supports_symlink()
+ build.env._fs_supports_symlink.cache_clear()
+
+ assert supports_symlink is has_symlink