diff --git a/tests/test_installer.py b/tests/test_installer.py index 9b42d70..bf06aae 100644 --- a/tests/test_installer.py +++ b/tests/test_installer.py @@ -1,10 +1,16 @@ """ Unit test functions in installer.py """ +import json import os +from unittest import mock +from subprocess import run, PIPE + import pytest +from tljh import conda from tljh import installer +from tljh.utils import parse_version as V from tljh.yaml import yaml @@ -36,3 +42,111 @@ def test_ensure_admins(tljh_dir, admins, expected_config): # verify the list was flattened assert config["users"]["admin"] == expected_config + + +def setup_conda(distro, version, prefix): + """Install mambaforge or miniconda in a prefix""" + if distro == "mambaforge": + installer_url, _ = installer._mambaforge_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'") + with conda.download_miniconda_installer(installer_url, None) as installer_path: + conda.install_miniconda(installer_path, str(prefix)) + + +@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 + + +@pytest.mark.parametrize( + "distro, version, conda_version, mamba_version", + [ + ( + None, + None, + installer.MAMBAFORGE_CONDA_VERSION, + installer.MAMBAFORGE_MAMBA_VERSION, + ), + ( + "exists", + None, + installer.MAMBAFORGE_CONDA_VERSION, + installer.MAMBAFORGE_MAMBA_VERSION, + ), + ( + "mambaforge", + "22.11.1-4", + installer.MAMBAFORGE_CONDA_VERSION, + installer.MAMBAFORGE_MAMBA_VERSION, + ), + ("mambaforge", "4.10.3-7", "4.10.3", "0.16.0"), + ( + "miniconda", + "4.7.10", + installer.MAMBAFORGE_CONDA_VERSION, + installer.MAMBAFORGE_MAMBA_VERSION, + ), + ( + "miniconda", + "4.5.1", + installer.MAMBAFORGE_CONDA_VERSION, + installer.MAMBAFORGE_MAMBA_VERSION, + ), + ], +) +def test_ensure_user_environment( + user_env_prefix, + distro, + version, + conda_version, + mamba_version, +): + if version and V(version) < V("4.10.1") and os.uname().machine == "aarch64": + pytest.skip(f"Miniconda {version} not available for aarch64") + canary_file = user_env_prefix / "test-file.txt" + canary_package = "types-backports_abc" + if distro: + if distro == "exists": + user_env_prefix.mkdir() + else: + setup_conda(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 + run( + [str(user_env_prefix / "bin/conda"), "install", "-y", canary_package], + 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") + + 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() + if distro != "exists": + # make sure we didn't delete the installed package + assert canary_package in packages + + assert "conda" in packages + assert packages["conda"]["version"] == conda_version + assert "mamba" in packages + assert packages["mamba"]["version"] == mamba_version diff --git a/tljh/conda.py b/tljh/conda.py index 6f1f9ce..19dc937 100644 --- a/tljh/conda.py +++ b/tljh/conda.py @@ -96,7 +96,7 @@ def download_miniconda_installer(installer_url, sha256sum): t = time.perf_counter() - tic logger.info(f"Downloaded conda installer {installer_url} in {t:.1f}s") - if sha256_file(f.name) != sha256sum: + if sha256sum and sha256_file(f.name) != sha256sum: raise Exception("sha256sum hash mismatch! Downloaded file corrupted") yield f.name diff --git a/tljh/installer.py b/tljh/installer.py index df94592..b463ff1 100644 --- a/tljh/installer.py +++ b/tljh/installer.py @@ -224,7 +224,7 @@ def ensure_user_environment(user_requirements_txt_file): if not found_conda: if os.path.exists(USER_ENV_PREFIX): logger.warning( - f"Found prefix at {USER_ENV_PREFIX}, but too old or missing conda/mamba ({have_versions}). Rebuilding env from scratch!!" + f"Found prefix at {USER_ENV_PREFIX}, but too old or missing conda/mamba ({have_versions}). Upgrading from mambaforge." ) # FIXME: should this fail? I'm not sure how destructive it is logger.info("Downloading & setting up user environment...") diff --git a/tljh/utils.py b/tljh/utils.py index a78fb80..d4bf127 100644 --- a/tljh/utils.py +++ b/tljh/utils.py @@ -2,6 +2,7 @@ Miscellaneous functions useful in at least two places unrelated to each other """ import logging +import re import subprocess # Copied into bootstrap/bootstrap.py. Make sure these two copies are exactly the same! @@ -67,4 +68,6 @@ def parse_version(version_string): Finds all numbers and returns a tuple of ints _very_ loose version parsing, like the old distutils.version.LooseVersion """ - return tuple(int(part) for part in version_string.split(".")) + # return a tuple of all the numbers in the version string + # always succeeds, even if passed nonsense + return tuple(int(part) for part in re.findall(r"\d+", version_string))