2018-07-19 17:30:09 -07:00
|
|
|
"""
|
|
|
|
|
Unit test functions in installer.py
|
|
|
|
|
"""
|
2023-03-23 12:34:44 +01:00
|
|
|
import json
|
2018-07-19 17:30:09 -07:00
|
|
|
import os
|
2023-03-23 12:34:44 +01:00
|
|
|
from unittest import mock
|
|
|
|
|
from subprocess import run, PIPE
|
|
|
|
|
|
2023-03-27 13:10:31 +02:00
|
|
|
from packaging.version import parse as V
|
|
|
|
|
from packaging.specifiers import SpecifierSet
|
2019-07-16 20:18:45 +03:00
|
|
|
import pytest
|
2018-08-28 14:16:09 +02:00
|
|
|
|
2023-03-23 12:34:44 +01:00
|
|
|
from tljh import conda
|
2018-08-28 14:16:09 +02:00
|
|
|
from tljh import installer
|
2019-07-15 12:02:18 +03:00
|
|
|
from tljh.yaml import yaml
|
2018-08-28 14:16:09 +02:00
|
|
|
|
2018-07-19 17:30:09 -07:00
|
|
|
|
2018-08-28 14:16:09 +02:00
|
|
|
def test_ensure_config_yaml(tljh_dir):
|
|
|
|
|
pm = installer.setup_plugins()
|
|
|
|
|
installer.ensure_config_yaml(pm)
|
|
|
|
|
assert os.path.exists(installer.CONFIG_FILE)
|
|
|
|
|
assert os.path.isdir(installer.CONFIG_DIR)
|
2021-11-03 23:55:34 +01:00
|
|
|
assert os.path.isdir(os.path.join(installer.CONFIG_DIR, "jupyterhub_config.d"))
|
2018-08-31 12:17:16 +02:00
|
|
|
# verify that old config doesn't exist
|
2021-11-03 23:55:34 +01:00
|
|
|
assert not os.path.exists(os.path.join(tljh_dir, "config.yaml"))
|
2019-07-15 12:02:18 +03:00
|
|
|
|
2019-07-16 20:18:45 +03:00
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
2021-11-01 09:42:45 +01:00
|
|
|
"admins, expected_config",
|
|
|
|
|
[
|
2021-11-03 23:55:34 +01:00
|
|
|
([["a1"], ["a2"], ["a3"]], ["a1", "a2", "a3"]),
|
|
|
|
|
([["a1:p1"], ["a2"]], ["a1", "a2"]),
|
2021-11-01 09:42:45 +01:00
|
|
|
],
|
2019-07-16 20:18:45 +03:00
|
|
|
)
|
|
|
|
|
def test_ensure_admins(tljh_dir, admins, expected_config):
|
2021-11-01 09:42:45 +01:00
|
|
|
# --admin option called multiple times on the installer
|
|
|
|
|
# creates a list of argument lists.
|
|
|
|
|
installer.ensure_admins(admins)
|
2019-07-15 12:02:18 +03:00
|
|
|
|
2021-11-01 09:42:45 +01:00
|
|
|
config_path = installer.CONFIG_FILE
|
|
|
|
|
with open(config_path) as f:
|
|
|
|
|
config = yaml.load(f)
|
2019-07-15 12:02:18 +03:00
|
|
|
|
2021-11-01 09:42:45 +01:00
|
|
|
# verify the list was flattened
|
2021-11-03 23:55:34 +01:00
|
|
|
assert config["users"]["admin"] == expected_config
|
2023-03-23 12:34:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def setup_conda(distro, version, prefix):
|
|
|
|
|
"""Install mambaforge or miniconda in a prefix"""
|
|
|
|
|
if distro == "mambaforge":
|
|
|
|
|
installer_url, _ = installer._mambaforge_url(version)
|
2023-03-27 13:10:31 +02:00
|
|
|
elif distro == "miniforge":
|
|
|
|
|
installer_url, _ = installer._mambaforge_url(version)
|
|
|
|
|
installer_url = installer_url.replace("Mambaforge", "Miniforge3")
|
2023-03-23 12:34:44 +01:00
|
|
|
elif distro == "miniconda":
|
|
|
|
|
arch = os.uname().machine
|
|
|
|
|
installer_url = (
|
|
|
|
|
f"https://repo.anaconda.com/miniconda/Miniconda3-{version}-Linux-{arch}.sh"
|
|
|
|
|
)
|
|
|
|
|
else:
|
2023-03-27 13:10:31 +02:00
|
|
|
raise ValueError(
|
|
|
|
|
f"{distro=} must be 'miniconda' or 'mambaforge' or 'miniforge'"
|
|
|
|
|
)
|
2023-03-23 12:34:44 +01:00
|
|
|
with conda.download_miniconda_installer(installer_url, None) as installer_path:
|
|
|
|
|
conda.install_miniconda(installer_path, str(prefix))
|
2023-03-23 16:26:34 +01:00
|
|
|
# avoid auto-updating conda when we install other packages
|
|
|
|
|
run(
|
|
|
|
|
[
|
|
|
|
|
str(prefix / "bin/conda"),
|
|
|
|
|
"config",
|
|
|
|
|
"--system",
|
|
|
|
|
"--set",
|
|
|
|
|
"auto_update_conda",
|
|
|
|
|
"false",
|
|
|
|
|
],
|
|
|
|
|
input="",
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
2023-03-23 12:34:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def user_env_prefix(tmp_path):
|
|
|
|
|
user_env_prefix = tmp_path / "user_env"
|
|
|
|
|
with mock.patch.object(installer, "USER_ENV_PREFIX", str(user_env_prefix)):
|
|
|
|
|
yield user_env_prefix
|
|
|
|
|
|
|
|
|
|
|
2023-03-27 13:10:31 +02:00
|
|
|
def _specifier(version):
|
|
|
|
|
"""Convert version string to SpecifierSet
|
|
|
|
|
|
|
|
|
|
If just a version number, add == to make it a specifier
|
|
|
|
|
|
|
|
|
|
Any missing fields are replaced with .*
|
|
|
|
|
|
|
|
|
|
If it's already a specifier string, pass it directly to SpecifierSet
|
|
|
|
|
|
|
|
|
|
e.g.
|
|
|
|
|
|
|
|
|
|
- 3.7 -> ==3.7.*
|
|
|
|
|
- 1.2.3 -> ==1.2.3
|
|
|
|
|
"""
|
|
|
|
|
if version[0].isdigit():
|
|
|
|
|
# it's a version number, not a specifier
|
|
|
|
|
if version.count(".") < 2:
|
|
|
|
|
# pad missing fields
|
|
|
|
|
version += ".*"
|
|
|
|
|
version = f"=={version}"
|
|
|
|
|
return SpecifierSet(version)
|
|
|
|
|
|
|
|
|
|
|
2023-03-23 12:34:44 +01:00
|
|
|
@pytest.mark.parametrize(
|
2023-03-27 13:10:31 +02:00
|
|
|
"distro, distro_version, expected_versions",
|
2023-03-23 12:34:44 +01:00
|
|
|
[
|
2023-03-27 13:10:31 +02:00
|
|
|
# No previous install, start fresh
|
2023-03-23 12:34:44 +01:00
|
|
|
(
|
|
|
|
|
None,
|
|
|
|
|
None,
|
2023-03-27 13:10:31 +02:00
|
|
|
{
|
|
|
|
|
"python": "3.10.*",
|
|
|
|
|
"conda": "22.11.1",
|
|
|
|
|
"mamba": "1.1.0",
|
|
|
|
|
},
|
2023-03-23 12:34:44 +01:00
|
|
|
),
|
2023-03-27 13:10:31 +02:00
|
|
|
# previous install, 1.0
|
2023-03-23 12:34:44 +01:00
|
|
|
(
|
2023-03-27 13:10:31 +02:00
|
|
|
"mambaforge",
|
|
|
|
|
"22.11.1-4",
|
|
|
|
|
{
|
|
|
|
|
"python": "3.10.*",
|
|
|
|
|
"conda": "22.11.1",
|
|
|
|
|
"mamba": "1.1.0",
|
|
|
|
|
},
|
2023-03-23 12:34:44 +01:00
|
|
|
),
|
2023-03-27 13:10:31 +02:00
|
|
|
# 0.2 install, no upgrade needed
|
2023-03-23 12:34:44 +01:00
|
|
|
(
|
|
|
|
|
"mambaforge",
|
2023-03-27 13:10:31 +02:00
|
|
|
"4.10.3-7",
|
|
|
|
|
{
|
|
|
|
|
"conda": "4.10.3",
|
|
|
|
|
"mamba": "0.16.0",
|
|
|
|
|
"python": "3.9.*",
|
|
|
|
|
},
|
2023-03-23 12:34:44 +01:00
|
|
|
),
|
2023-03-27 13:10:31 +02:00
|
|
|
# simulate missing mamba
|
|
|
|
|
# will be installed but not pinned
|
|
|
|
|
# to avoid conflicts
|
2023-03-23 12:34:44 +01:00
|
|
|
(
|
2023-03-27 13:10:31 +02:00
|
|
|
"miniforge",
|
|
|
|
|
"4.10.3-7",
|
|
|
|
|
{
|
|
|
|
|
"conda": "4.10.3",
|
|
|
|
|
"mamba": ">=1.1.0",
|
|
|
|
|
"python": "3.9.*",
|
|
|
|
|
},
|
2023-03-23 12:34:44 +01:00
|
|
|
),
|
2023-03-27 13:10:31 +02:00
|
|
|
# too-old Python (3.7), abort
|
2023-03-23 12:34:44 +01:00
|
|
|
(
|
|
|
|
|
"miniconda",
|
2023-03-27 13:10:31 +02:00
|
|
|
"4.7.10",
|
|
|
|
|
ValueError,
|
2023-03-23 12:34:44 +01:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_ensure_user_environment(
|
|
|
|
|
user_env_prefix,
|
|
|
|
|
distro,
|
2023-03-27 13:10:31 +02:00
|
|
|
distro_version,
|
|
|
|
|
expected_versions,
|
2023-03-23 12:34:44 +01:00
|
|
|
):
|
2023-03-27 13:10:31 +02:00
|
|
|
if (
|
|
|
|
|
distro_version
|
|
|
|
|
and V(distro_version) < V("4.10.1")
|
|
|
|
|
and os.uname().machine == "aarch64"
|
|
|
|
|
):
|
|
|
|
|
pytest.skip(f"{distro} {distro_version} not available for aarch64")
|
2023-03-23 12:34:44 +01:00
|
|
|
canary_file = user_env_prefix / "test-file.txt"
|
|
|
|
|
canary_package = "types-backports_abc"
|
|
|
|
|
if distro:
|
2023-03-27 13:10:31 +02:00
|
|
|
setup_conda(distro, distro_version, user_env_prefix)
|
|
|
|
|
# install a noarch: python package that won't be used otherwise
|
|
|
|
|
# should depend on Python, so it will interact with possible upgrades
|
|
|
|
|
pkgs = [canary_package]
|
|
|
|
|
run(
|
|
|
|
|
[
|
|
|
|
|
str(user_env_prefix / "bin/conda"),
|
|
|
|
|
"install",
|
|
|
|
|
"-S",
|
|
|
|
|
"-y",
|
|
|
|
|
"-c",
|
|
|
|
|
"conda-forge",
|
|
|
|
|
]
|
|
|
|
|
+ pkgs,
|
|
|
|
|
input="",
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
2023-03-23 12:34:44 +01:00
|
|
|
|
|
|
|
|
# make a file not managed by conda, to check for wipeouts
|
|
|
|
|
with canary_file.open("w") as f:
|
|
|
|
|
f.write("I'm here\n")
|
|
|
|
|
|
2023-03-27 13:10:31 +02:00
|
|
|
if isinstance(expected_versions, type) and issubclass(expected_versions, Exception):
|
|
|
|
|
exc_class = expected_versions
|
|
|
|
|
with pytest.raises(exc_class):
|
|
|
|
|
installer.ensure_user_environment("")
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
installer.ensure_user_environment("")
|
|
|
|
|
|
2023-03-23 12:34:44 +01:00
|
|
|
p = run(
|
|
|
|
|
[str(user_env_prefix / "bin/conda"), "list", "--json"],
|
|
|
|
|
stdout=PIPE,
|
|
|
|
|
text=True,
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
|
|
|
|
package_list = json.loads(p.stdout)
|
|
|
|
|
packages = {package["name"]: package for package in package_list}
|
2023-03-27 13:10:31 +02:00
|
|
|
|
2023-03-23 12:34:44 +01:00
|
|
|
if distro:
|
|
|
|
|
# make sure we didn't wipe out files
|
|
|
|
|
assert canary_file.exists()
|
2023-03-27 13:10:31 +02:00
|
|
|
# make sure we didn't delete the installed package
|
|
|
|
|
assert canary_package in packages
|
|
|
|
|
|
|
|
|
|
for pkg, version in expected_versions.items():
|
|
|
|
|
assert pkg in packages
|
|
|
|
|
assert V(packages[pkg]["version"]) in _specifier(version)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ensure_user_environment_no_clobber(user_env_prefix):
|
|
|
|
|
# don't clobber existing user-env dir if it's non-empty and not a conda install
|
|
|
|
|
user_env_prefix.mkdir()
|
|
|
|
|
canary_file = user_env_prefix / "test-file.txt"
|
|
|
|
|
with canary_file.open("w") as f:
|
|
|
|
|
pass
|
|
|
|
|
with pytest.raises(OSError):
|
|
|
|
|
installer.ensure_user_environment("")
|