Files
the-littlest-jupyterhub/tests/test_installer.py

245 lines
7.0 KiB
Python
Raw Normal View History

"""
Unit test functions in installer.py
"""
import json
import os
from subprocess import PIPE, run
from unittest import mock
2019-07-16 20:18:45 +03:00
import pytest
from packaging.specifiers import SpecifierSet
from packaging.version import parse as V
from tljh import conda, installer
2019-07-15 12:02:18 +03:00
from tljh.yaml import yaml
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)
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
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(
"admins, expected_config",
[
([["a1"], ["a2"], ["a3"]], ["a1", "a2", "a3"]),
([["a1:p1"], ["a2"]], ["a1", "a2"]),
],
2019-07-16 20:18:45 +03:00
)
def test_ensure_admins(tljh_dir, admins, expected_config):
# --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
config_path = installer.CONFIG_FILE
with open(config_path) as f:
config = yaml.load(f)
2019-07-15 12:02:18 +03:00
# verify the list was flattened
assert config["users"]["admin"] == expected_config
def setup_conda(distro, version, prefix):
"""Install miniforge or miniconda in a prefix"""
if distro == "mambaforge":
installer_url, _ = installer._miniforge_url(version)
installer_url = installer_url.replace("Miniforge3", "Mambaforge")
elif distro == "miniforge":
installer_url, _ = installer._miniforge_url(version)
elif distro == "miniconda":
arch = os.uname().machine
installer_url = (
f"https://repo.anaconda.com/miniconda/Miniconda3-{version}-Linux-{arch}.sh"
)
else:
raise ValueError(
f"{distro=} must be 'miniconda' or 'mambaforge' or 'miniforge'"
)
with conda.download_miniconda_installer(installer_url, None) as installer_path:
conda.install_miniconda(installer_path, str(prefix))
# avoid auto-updating conda when we install other packages
run(
[
str(prefix / "bin/conda"),
"config",
"--system",
"--set",
"auto_update_conda",
"false",
],
input="",
check=True,
)
@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
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)
@pytest.mark.parametrize(
# - distro: None, mambaforge, or miniforge
# - distro_version: https://github.com/conda-forge/miniforge/releases
# - expected_versions: versions of python, conda, and mamba in user env
#
# TLJH of a specific version comes with a specific distro_version as
# declared in installer.py's MAMBAFORGE_VERSION variable, and it comes with
# python, conda, and mamba of certain versions.
#
"distro, distro_version, expected_versions",
[
# No previous install, start fresh
(
None,
None,
{
"python": "3.12.*",
"conda": "24.7.1",
"mamba": "1.5.9",
},
),
# previous install, 1.0
(
"mambaforge",
"23.1.0-1",
{
"python": "3.10.*",
"conda": "23.1.0",
"mamba": "1.4.1",
},
),
# 0.2 install, no upgrade needed
(
"mambaforge",
"4.10.3-7",
{
"python": "3.9.*",
"conda": "4.10.3",
"mamba": "0.16.0",
},
),
# simulate missing mamba
# will be installed but not pinned
# to avoid conflicts
(
"miniforge",
"4.10.3-7",
{
"python": "3.9.*",
"conda": "4.10.3",
"mamba": ">=1.1.0",
},
),
# too-old Python (3.7), abort
(
"miniconda",
"4.7.10",
ValueError,
),
],
)
def test_ensure_user_environment(
user_env_prefix,
distro,
distro_version,
expected_versions,
):
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")
canary_file = user_env_prefix / "test-file.txt"
canary_package = "types-backports_abc"
if distro:
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,
)
# make a file not managed by conda, to check for wipeouts
with canary_file.open("w") as f:
f.write("I'm here\n")
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("")
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}
if distro:
# make sure we didn't wipe out files
assert canary_file.exists()
# 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("")