Skip to content
Snippets Groups Projects
test_mpsd_software.py 31.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Hans Fangohr's avatar
    Hans Fangohr committed
    """Tests for mpsd-software-environment.py."""
    
    
    import copy
    import datetime
    
    import logging
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    import shutil
    
    import subprocess
    
    import sys
    
    from pathlib import Path
    
    
    mod = importlib.import_module("mpsd_software_manager.mpsd_software")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
    # set loglevel to debug - useful for understanding problems.
    # (if the tests pass, pytest doesn't show any output)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    mod.set_up_logging(loglevel="debug", file_path="tests.log")
    
    logging.debug(f"We have set up logging from {__file__}")
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
    
    def create_mock_git_repository(target_directory, create_directory=True):
        """
        Create a git repository in the directory `target_directory`.
    
        Arguments
        ---------
        target_directory : pathlib.Path
          - path at which the root of the repository should be located (i.e. `.git` folder)
    
        create_directory : bool
          - create `target_directory` and parent directories if True
    
        """
        # create directory first
        if create_directory:
            target_directory.mkdir(parents=True)
    
        # then create git repository:
        with mod.os_chdir(str(target_directory)):
            subprocess.run("git init .", shell=True, check=True)
            subprocess.run("echo 'fake content' > readme.txt", shell=True, check=True)
            subprocess.run("git add readme.txt", shell=True, check=True)
            subprocess.run("pwd", shell=True)
    
    
            # if email and username are not available (such as on naked test container),
            # git may complain. We set a temporary user for this one commit to work around
            # that.
            user_details = "-c user.name='Tes Ta' -c user.email='tester@some-ci.org'"
    
    Hans Fangohr's avatar
    Hans Fangohr committed
                f'git {user_details} commit -m "first commit" readme.txt',
                shell=True,
                check=True,
    
    def test_os_chdir(tmp_path):
    
        """Test the os_chdir context manager."""
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # create a temporary directory for testing
    
        temp_dir = tmp_path / "test_os_chdir"
        temp_dir.mkdir()
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # initial current working directory
        initial_cwd = os.getcwd()
    
        # change to the temporary directory using os_chdir
    
        with mod.os_chdir(str(temp_dir)):
            assert os.getcwd() == str(temp_dir)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
        # current working directory should be back to initial directory
        assert os.getcwd() == initial_cwd
    
    
    def test_run_method(tmp_path):
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        """Run tests for run method."""
    
        run = mod.run
    
        # test a command with options:
        assert run(["date", "+%Y-%m-%d"]).returncode == 0
        assert run("date +%Y-%m-%d", shell=True).returncode == 0
    
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        # tests interacting with the file system
    
        with mod.os_chdir(str(tmp_path)):
            # ensure single string command works
    
    Hans Fangohr's avatar
    Hans Fangohr committed
            assert run(("ls -l"), shell=True).returncode == 0
    
            # test spaces are handled correctly:
    
    Hans Fangohr's avatar
    Hans Fangohr committed
            assert run(["touch", "file1", "file2"]).returncode == 0
    
            assert os.path.exists("file1")
            assert os.path.exists("file2")
            # test output is captured:
            assert (
                b"Hello, world!\n"
                in run(["echo", "Hello, world!"], capture_output=True).stdout
            )
    
        # check exceptions
        with pytest.raises(FileNotFoundError):
            run(["doesnotexistcommand"])
    
        # check error code is checked
        # 1. expect this to parse: return code is non-zero, but we don't check
        run(["ls", "/doesnotexist"]),
        # 2. expect this to fail:
        with pytest.raises(subprocess.CalledProcessError):
            run(["ls", "/doesnotexist"], check=True)
    
    def test_prepare_environment(tmp_path):
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        """Simulate running preparation of environment.
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        Simulate running ./install-software-environment.py --release dev-23a \
          --target-directory /tmp/test_prepare_env
        prepare_env is run when cmd is not specified, we can test cmd='prepare'
        and cmd=None to check both cases
        """
    
        root_dir = tmp_path / "mpsd_opt" / "linux_debian_11"
    
        spack_environments = "spack-environments"
        mpsd_release_to_test = "dev-23a"
    
        release_base_dir = root_dir / mpsd_release_to_test
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # check that the test directory does not exist
    
        assert not root_dir.exists()
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
        # prepare_environment expects to be executed in git repository
        # (mpsd-software-environments). It queries the commit on which we are to
        # log that information. For this to work, we need to execute the command
        # within a directory tree that has a git repository at the same or high
        # level. Let's create one:
    
        create_mock_git_repository(root_dir)
    
    
        # now call the function we want to test
    
        result = mod.prepare_environment(
    
            mpsd_release=mpsd_release_to_test, root_dir=root_dir
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # check if the directory now is created
    
        assert release_base_dir.exists()
    
        # check for spack-environments directory
    
        assert spack_environments in os.listdir(release_base_dir)
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
        # check if the git branch is correctly checked out. We expect output such as
        # git_branch_stdout = '* dev-23a\n  develop\n'
        # The entry with the '* ' prefix is the active branch.
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        git_branch_output_raw = subprocess.run(
            f"cd {str(release_base_dir/spack_environments)} && git branch",
            shell=True,
            capture_output=True,
        )
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        git_branch_stdout = git_branch_output_raw.stdout.decode("utf-8")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        assert f"* releases/{mpsd_release_to_test}" in git_branch_stdout
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # check that result is a list and contains atleast ['global','foss2021a-mpi']
    
        assert isinstance(result, list)
        assert "global" in result
        assert "foss2021a-mpi" in result
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
        # Expect an Exception when wrong mpsd_release is provided
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        with pytest.raises(Exception):
    
            result = mod.prepare_environment(
    
                mpsd_release="wrong-mpsd-release", root_dir=(root_dir)
    
    def test_write_to_cmd_log(tmp_path):
        """Check that we write to the correct log file"""
        cmd_log_file = mod.config_vars["cmd_log_file"]
        mod.write_to_cmd_log(root_dir=tmp_path, msg="test_cmd")
        assert os.path.exists(tmp_path / cmd_log_file)
        with open(tmp_path / cmd_log_file, "r") as f:
            assert "test_cmd" in f.read()
    
    
    
    def test_record_script_execution_summary(tmp_path):
    
        """Check that cmd log is updated with header
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
        Check that logs/install-software-environment.log is updated when the module is run
        """
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        cmd_log_file = mod.config_vars["cmd_log_file"]
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    
    
        root_dir = tmp_path / "test_prepare_env"
    
        script_version = mod.__version__
    
        if os.path.exists(root_dir / cmd_log_file):
    
            initial_bytes = os.path.getsize(cmd_log_file)
    
        else:
            initial_bytes = 0
    
    
        # run the init functionality to check the creation of log file
    
        create_mock_git_repository(target_directory=root_dir, create_directory=True)
    
        mod.initialise_environment(root_dir=(root_dir))
    
    
        # check that logs/install-software-environment.log is updated
    
        assert os.path.exists(root_dir / cmd_log_file)
        assert os.path.getsize(root_dir / cmd_log_file) > initial_bytes
    
    
        # Check that the log file has "Spack environments branch: dev-23a " in the last line
    
        with open(root_dir / cmd_log_file, "r") as f:
    
            lines = f.readlines()
    
            assert f"Initialising MPSD software instance at {tmp_path}" in lines[-2]
            assert f"MPSD Software manager version: {script_version}" in lines[-1]
    
    def test_install_environment_wrong_package_set(tmp_path):
        """Test exception is raised for non-existing package_set."""
    
        # exits with exit code 20 when wrong package_sets are provided
    
        with pytest.raises(SystemExit) as e:
    
            mod.install_environment(
                mpsd_release="dev-23a",
    
                package_sets=["wrong-package_set"],
    
                root_dir=(tmp_path),
    
        assert e.type == SystemExit
    
        assert e.value.code == 20
    
    
    def test_install_environment_wrong_mpsd_release(tmp_path):
        """Test exception is raised for non-existing mpsd release."""
    
        # Expect an Exception when wrong mpsd_release is provided (part of
        # prepare_environment)
    
        with pytest.raises(Exception):
            mod.install_environment(
                mpsd_release="wrong-mpsd-release",
    
                package_sets=["foss2021a-mpi"],
    
                root_dir=(tmp_path),
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    @pytest.mark.skipif(sys.platform == "darwin", reason="install not working on OSX")
    
    def test_install_environment_zlib():
    
        """Test installation of package_set."""
    
        # Prepare a test installation of global generic
    
        # with only zlib to test the installation
        # This is a long test,
        # its handy to test this with print statements printed to
        # stdout, use:
        #   pytest -s
        # for this installation avoid tmp_path as
        # the length of the path becomes too long and spack complains
    
        root_dir = Path("/tmp/test_global_generic")
        if root_dir.exists():
            shutil.rmtree(root_dir)
        root_dir.mkdir(exist_ok=True, parents=True)
    
        mpsd_release_to_test = "dev-23a"
    
        package_set_to_test = "global_generic"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        cmd_log_file = mod.config_vars["cmd_log_file"]
    
        microarch = mod.get_native_microarchitecture()
    
        release_base_dir = root_dir / mpsd_release_to_test
        create_mock_git_repository(target_directory=root_dir, create_directory=False)
        mod.prepare_environment(mpsd_release=mpsd_release_to_test, root_dir=(root_dir))
    
        # Patch the spack environments to create a fake global_generic
    
        # create a test package_set
        package_set_src_dir = release_base_dir / "spack-environments" / "toolchains"
        # with mod.os_chdir(package_set_src_dir):
    
        #     subprocess.run(
        #         "cp -r foss2021a-mpi fuss1999a", shell=True, capture_output=True
        #     )
        # add zlib as a spec to global_generic
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        with open(
            package_set_src_dir / "global_generic" / "global_packages.list", "w"
        ) as f:
    
            f.write("zlib@1.2.13 \nzstd@1.5.2\n")
    
        # add zlib to whitelist of module creation file by replacing anaconda3%gcc@10.2.1
        # with zlib@1.2.13
    
        # in release_base_dir / "spack-environments/spack_overlay/etc/spack/modules.yaml"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        module_file = (
            release_base_dir / "spack-environments/spack_overlay/etc/spack/modules.yaml"
        )
        with open(module_file, "r") as f:
            lines = f.read().replace("anaconda3%gcc@10.2.1", "zlib@1.2.13")
        with open(module_file, "w") as f:
    
        # Replace gcc@10.2.1 with gcc#13.1.1 or available system gcc for testing on laptop
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        gcc_ver = (
            subprocess.run(["gcc -dumpfullversion"], shell=True, capture_output=True)
            .stdout.decode("utf-8")
            .strip()
        )
    
        assert len(gcc_ver) > 3, f"Couldn't find gcc {gcc_ver=}"
    
    
        setup_file = release_base_dir / "spack-environments/spack_setup.sh"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        with open(setup_file, "r") as f:
            lines = f.read().replace(
                'system_compiler="gcc@10.2.1"', f'system_compiler="gcc@{gcc_ver}"'
            )
        with open(setup_file, "w") as f:
    
        # install global_generic package_set
    
        mod.set_up_logging(
            "WARNING",
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            mod.get_log_file_path(mpsd_release_to_test, "install", root_dir),
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        mod.install_environment(
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            mpsd_release=mpsd_release_to_test,
    
            package_sets=[package_set_to_test],
    
            root_dir=root_dir,
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            enable_build_cache=False,
        )
    
        # test that the build log is created correctly
    
        # check that a file with glob build_globale_generic_dev-23a*.log exists at
    
        # release_base_dir/microarch
    
        # print("Debug here ")
        # time.sleep(10)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        log_files = list(
    
            (release_base_dir / "logs").glob(
    
                f"{mpsd_release_to_test}_{microarch}_*_install.log"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            )
        )
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        assert len(log_files) == 2
        # take the most recent log as build log
        apex_log = sorted(log_files)[0]
        build_log = sorted(log_files)[1]
        assert "APEX" in str(apex_log)
        assert "BUILD" in str(build_log)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # check that the build log contains statement ##### Installation finished
        with open(build_log, "r") as f:
    
            lines = f.read()
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            assert "##### Installation finished" in lines
    
        os.path.basename(build_log)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # assert that APEX log file points to the build log file
        with open(apex_log, "r") as f:
            lines = f.read()
            assert (
                f"> Logging installation of {package_set_to_test} at {build_log}" in lines
            )
    
        # assert that cmd log files exists
    
        assert os.path.exists(root_dir / cmd_log_file)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # assert that the mpsd release and hash is written to the cmd log file
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        os.path.basename(build_log)
    
        with open(root_dir / cmd_log_file, "r") as f:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            lines = f.read()
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            assert f"Spack environments branch: releases/{mpsd_release_to_test}" in lines
            # assert (
            #     f"> logging to {apex_log}" in lines
            # ) # TODO this has to be tested when main() called ie via CLI
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # assert that the module files are created correctly
    
        assert os.path.exists(release_base_dir / microarch)
        assert os.path.exists(release_base_dir / microarch / "lmod")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # assert that lmod/module-index.yaml contains zlib
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        with open(release_base_dir / microarch / "lmod" / "module-index.yaml", "r") as f:
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            lines = f.read()
            assert "zlib" in lines
    
        # install again to ensure that
        # commands that skip creation of folders when
    
        # they are already present works as expected
    
        # reload the module to ensure that date changes
        importlib.reload(mod)
    
        mod.set_up_logging(
            "WARNING",
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            mod.get_log_file_path(mpsd_release_to_test, "install", root_dir),
    
        mod.install_environment(
            mpsd_release=mpsd_release_to_test,
    
            package_sets=[package_set_to_test],
    
            root_dir=root_dir,
    
            enable_build_cache=False,
        )
        build_log = list(
            (release_base_dir / "logs").glob(
    
                f"{mpsd_release_to_test}_{microarch}_*_install.log"
    
        assert len(build_log) == 4
    
        # test that the removal now works
    
        # mod.remove_environment(
        #     mpsd_release=mpsd_release_to_test,
        #     package_sets=[package_set_to_test],
        #     root_dir=root_dir,
        # )
        # # ensure that the module files are removed
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
    
    def test_metadata_logging(tmp_path):
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        """Test that metadata is logged and read correctly."""
        # Test that the metadata is logged correctly
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        filename = tmp_path / "test-metadata.log"
        print(f"Writing to {filename}")
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        mod.set_up_logging(loglevel="debug", file_path=filename)
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
        # our test data
    
        keys = ["important_key", "important_key2"]
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        values = ["important_value", "important_value2"]
    
        expected_log_entries = []
        for key, value in zip(keys, values):
            mod.log_metadata(key, value)
            open_tag = mod.config_vars["metadata_tag_open"]
            close_tag = mod.config_vars["metadata_tag_close"]
            expected_log = f"{open_tag}{key}:{value}{close_tag}"
            expected_log_entries.append(expected_log)
            logging.info(f"Add some other info (after adding {key=})")
            logging.debug("Add some other info")
            logging.warning("Add some other info")
    
        # Check that relevant lines show up in the log file somewhere
    
        with open(filename, "r") as f:
    
    Hans Fangohr's avatar
    Hans Fangohr committed
            logfile_content = f.read()
            for expected_log in expected_log_entries:
                assert expected_log in logfile_content
    
        # Test that the metadata is read correctly using our parser
    
        read_dict = mod.read_metadata_from_logfile(tmp_path / "test-metadata.log")
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
        # check all entries are in the file
        for key, value in zip(keys, values):
            read_dict[key] == value
    
        # check no additional entries are there
        assert len(read_dict) == len(keys)
    
    def test_get_available_package_sets():
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        """
    
        Test that available package_sets are reported correctly.
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    
        Needs internet access to succeed.
        """
    
        package_sets = mod.get_available_package_sets("dev-23a")
        assert sorted(package_sets) == sorted(
    
    Hans Fangohr's avatar
    Hans Fangohr committed
            [
                "foss2021a-cuda-mpi",
                "foss2021a-mpi",
                "foss2021a-serial",
                "foss2022a-cuda-mpi",
                "foss2022a-mpi",
                "foss2022a-serial",
                "global",
                "global_generic",
            ]
        )
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    def test_create_log_file_name():
    
        """Test that the log file names are created correctly."""
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        create_log_file_name = mod.create_log_file_name
    
        mpsd_release = "dev-23a"
    
        microarch = mod.get_native_microarchitecture()
    
        date = datetime.datetime.now().replace(microsecond=0).isoformat()
        action = "install"
    
        package_set = "foss2021a"
    
        # test build_log_file_name  generation
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        build_log_file_name = create_log_file_name(
    
            mpsd_release=mpsd_release,
            date=date,
            action=action,
    
            package_set=package_set,
    
            build_log_file_name
    
            == f"{mpsd_release}_{microarch}_{date}_BUILD_{package_set}_{action}.log"
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        installer_log_file_name = create_log_file_name(
    
            mpsd_release=mpsd_release,
            date=date,
    
            action=action,
    
        assert (
            installer_log_file_name
    
            == f"{mpsd_release}_{microarch}_{date}_APEX_{action}.log"
    
        )
        # test no build log file for incorrect action
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        build_log_file_name = create_log_file_name(
    
            mpsd_release=mpsd_release,
            date=date,
    
            action="status",
    
            package_set=package_set,
    
        assert build_log_file_name is None
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
    def create_fake_environment(tmp_path, mpsd_release, expected_toolchain_map=None):
        """Create a fake environment with toolchains for testing."""
        if not expected_toolchain_map:
            test_microarch = mod.get_native_microarchitecture()
            expected_toolchain_map = {test_microarch: ["foss2021a", "intel2021a"]}
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        for microarch in expected_toolchain_map.keys():
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            toolchain_lmod_folder = (
                tmp_path / mpsd_release / microarch / "lmod" / "Core" / "toolchains"
    
            toolchain_lmod_folder.mkdir(parents=True, exist_ok=True)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            spack_folder = tmp_path / mpsd_release / microarch / "spack"
    
            spack_folder.mkdir(parents=True, exist_ok=True)
    
            logs_folder = tmp_path / mpsd_release / "logs"
    
            logs_folder.mkdir(parents=True, exist_ok=True)
    
            # Simulate the creation of APEX.log
            # (which is created by the main function)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            (logs_folder / "APEX.log").touch()
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            for toolchain in expected_toolchain_map[microarch]:
    
                toolchain_lua_file = toolchain_lmod_folder / f"{toolchain}.lua"
                toolchain_lua_file.touch()
    
        return expected_toolchain_map
    
    def test_environment_status(tmp_path):
        """Test that the environment status is correct."""
        toolchain_map = mod.environment_status("fake-release", tmp_path)
        assert toolchain_map is None
        mpsd_release = "dev-23a"
        expected_toolchain_map = create_fake_environment(tmp_path, mpsd_release)
        # check that the environment statuxis is correct
        toolchain_map = mod.environment_status(mpsd_release, tmp_path)
        # convert each list to a set to ensure that the order doesn't matter
        for microarch in expected_toolchain_map.keys():
            assert set(toolchain_map[microarch]) == set(expected_toolchain_map[microarch])
    
    
    
    def test_initialise_environment(tmp_path):
    
        """Test that init_file is created as expected."""
        # test that the init file is created as expected
    
        mod.initialise_environment(tmp_path)
    
        init_file = tmp_path / mod.config_vars["init_file"]
    
        assert init_file.exists()
    
        # ensure "Initialising MPSD software ..." is in the log file
        log_file = tmp_path / mod.config_vars["cmd_log_file"]
        with open(log_file, "r") as f:
            assert (f"Initialising MPSD software instance at {tmp_path}") in f.read()
    
        # test that calling again results in warning and exit code 30
    
        with pytest.raises(SystemExit) as pytest_wrapped_e:
    
            mod.initialise_environment(tmp_path)
    
        assert pytest_wrapped_e.type == SystemExit
    
        assert pytest_wrapped_e.value.code == 30
    
    def test_get_root_dir(tmp_path):
        """Test that the root directory is correct."""
        with mod.os_chdir(tmp_path):
    
            # test that  function exists with error 40 if root dir doesn't exist
    
            with pytest.raises(SystemExit) as pytest_wrapped_e:
                mod.get_root_dir()
            assert pytest_wrapped_e.type == SystemExit
    
            assert pytest_wrapped_e.value.code == 40
    
    
            # test that initialize_environment creates the root dir
    
            mod.initialise_environment(tmp_path)
    
            root_dir = mod.get_root_dir()
            assert root_dir == tmp_path
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            # test that root_dir from parent is detected correctly
    
            sub_dir = tmp_path / "sub_dir"
            sub_dir.mkdir()
            with mod.os_chdir(sub_dir):
                root_dir = mod.get_root_dir()
                assert root_dir == tmp_path
    
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
            # test that initialising in a subdirectory makes it the root dir
            with mod.os_chdir(sub_dir):
    
                mod.initialise_environment(sub_dir)
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
                root_dir = mod.get_root_dir()
                assert root_dir == sub_dir
    
    
    def test_get_available_releases():
        res = mod.get_available_releases()
        assert "dev-23a" in res
        assert len(res) >= 1
        for release in res:
            assert isinstance(release, str)
    
    
    
    Hans Fangohr's avatar
    Hans Fangohr committed
    def test_argument_parsing_logic(mocker):
    
        """Test to find errors in argparse logic.
    
        Strategy:
    
        In each of the tests below, we are setting the sys.argv to simulate the
        input from the command line, and in each instance, we ensure that the
        mocked function get the arguments as expected. The function is mocked not
        to carry out any activity.
    
        """
    
        # pretend we have a rootdir defined
        mock = mocker.patch(
            "mpsd_software_manager.mpsd_software.get_root_dir", return_value=Path(".")
        )
    
    
        sys.argv = ["mpsd-software-tests", "init"]
        mock = mocker.patch(
            "mpsd_software_manager.mpsd_software.initialise_environment", return_value=None
        )
        with pytest.raises(SystemExit):
            mod.main()
        call_argument = mock.call_args[0][0]
        assert isinstance(call_argument, Path)
    
    
        ### available
        sys.argv = ["mpsd-software-tests", "available"]
        mock = mocker.patch(
            "mpsd_software_manager.mpsd_software.get_available_releases", return_value=None
        )
        with pytest.raises(SystemExit):
            mod.main()
    
        sys.argv = ["mpsd-software-tests", "available", "dev-23a"]
        mock = mocker.patch(
            "mpsd_software_manager.mpsd_software.get_available_package_sets",
            return_value=None,
        )
        mod.main()
        call_argument = mock.call_args[0][0]
        assert call_argument == "dev-23a"
    
        ### prepare
        sys.argv = ["mpsd-software-tests", "prepare", "dev-23a"]
        mock = mocker.patch(
            "mpsd_software_manager.mpsd_software.prepare_environment", return_value=None
        )
        mod.main()
        call_argument = mock.call_args[0][0]
        assert call_argument == "dev-23a"
    
        ### install
        mock = mocker.patch(
            "mpsd_software_manager.mpsd_software.install_environment", return_value=None
        )
        sys.argv = ["mpsd-software-tests", "install", "dev-23a", "foss2022a-mpi"]
        mod.main()
        assert mock.call_args[0][0] == "dev-23a"
        assert mock.call_args[0][1] == ["foss2022a-mpi"]
    
        sys.argv = [
            "mpsd-software-tests",
            "install",
            "23b",
            "foss2022a-mpi",
            "foss2022a-serial",
        ]
        mod.main()
        assert mock.call_args[0][0] == "23b"
        assert mock.call_args[0][1] == ["foss2022a-mpi", "foss2022a-serial"]
    
        ### status
        mock = mocker.patch(
            "mpsd_software_manager.mpsd_software.environment_status", return_value=None
        )
        sys.argv = ["mpsd-software-tests", "status", "dev-23a"]
        mod.main()
        assert mock.call_args[0][0] == "dev-23a"
    
        ### remove (argparse doesn't allow this yet.
        ### Copy from 'install' when the time has come.)
    
    
    
    def test_remove_environment(tmp_path, mocker):
        """Test that the remove_environment function works as expected."""
    
        release_to_test = "dev-23a"
    
        # check exit 50 when no package_sets are provided
        with pytest.raises(SystemExit) as pytest_wrapped_e:
            mod.remove_environment("dev-23a", tmp_path)
        assert pytest_wrapped_e.type == SystemExit
        assert pytest_wrapped_e.value.code == 50
    
        # Test case2  - remove entire release
        ## exit with 60 when force is not set
        ### patch the input function to return 'n'
        mocker.patch("builtins.input", return_value="n")
        with pytest.raises(SystemExit) as pytest_wrapped_e:
            mod.remove_environment(release_to_test, tmp_path, "ALL")
        assert pytest_wrapped_e.type == SystemExit
        assert pytest_wrapped_e.value.code == 60
        ### patch the input function to return 'y'
        mocker.patch("builtins.input", return_value="y")
        # check that the release directory is removed and logs are kept
        # create a release directory
        create_fake_environment(tmp_path, release_to_test)
        release_dir = tmp_path / release_to_test / mod.get_native_microarchitecture()
        logs_dir = tmp_path / release_to_test / "logs"
    
        toolchain_map = mod.environment_status(release_to_test, tmp_path)
        assert toolchain_map is not None
        mod.remove_environment(release_to_test, tmp_path, "ALL")
    
        toolchain_map = mod.environment_status(release_to_test, tmp_path)
        # check that no toolchain remains
        assert toolchain_map is None
        # check that the release directory is empty
        assert len(list(release_dir.iterdir())) == 0
        # check that the logs directory is non-empty
    
        list_of_logs = list(logs_dir.iterdir())
        assert len(list_of_logs) == 2  # APEX + remove_build_log
        # check that one of the log has 'remove.log' as part of the name
        assert "BUILD_ALL_remove.log" in ",".join([str(x) for x in list_of_logs])
    
    
        # Test case3  - remove specific package_sets
        # defined in test_remove_package_sets
    
    
    @pytest.fixture
    def simple_toolchain():
        """returns a dict for a simple toolchain"""
        dict = {
            "spack": {
                "specs": ["zlib@1.2.13"],
                "view": True,
                "concretizer": {"reuse": False, "unify": True},
            }
        }
        return dict
    
    
    def install_test_release(tmp_path, simple_toolchain):
        """Install a test release
    
        with toolchain1 and toolchain2 to use for testing.
        """
    
        release_to_test = "dev-23a"
        # prepare a release directory
        mod.prepare_environment(mpsd_release=release_to_test, root_dir=tmp_path)
    
        tmp_path / release_to_test / mod.get_native_microarchitecture()
        spe_dir = tmp_path / release_to_test / "spack-environments"
    
        # create sample toolchains
        simple_toolchain_string = yaml.dump(simple_toolchain)
        simple_toolchain_2 = copy.deepcopy(simple_toolchain)
        simple_toolchain_2["spack"]["specs"] = ["zlib@1.2.13", "zstd@1.5.2"]
        simple_toolchain2_string = yaml.dump(simple_toolchain_2)
        toolchain1_dir = spe_dir / "toolchains" / "toolchain1"
        toolchain2_dir = spe_dir / "toolchains" / "toolchain2"
        toolchain1_dir.mkdir(parents=True)
        toolchain2_dir.mkdir(parents=True)
        (toolchain1_dir / "spack.yaml").write_text(simple_toolchain_string)
        (toolchain2_dir / "spack.yaml").write_text(simple_toolchain2_string)
    
        # install the release
        mod.install_environment(
            mpsd_release=release_to_test,
            package_sets=["toolchain1", "toolchain2"],
            root_dir=tmp_path,
        )
    
    
    def test_remove_package_sets(tmp_path, simple_toolchain):
        """Test removal of package_sets via spack."""
    
        release_to_test = "dev-23a"
    
        # Case1 - remove global / global_generic package_sets
    
        # Case2 - remove specific package_sets (toolchains)
        # Create a test install
        install_test_release(tmp_path, simple_toolchain)
        # check that the installation went through
        release_dir = tmp_path / release_to_test / mod.get_native_microarchitecture()
        assert len(list(release_dir.iterdir())) == 2  # spack and lmod
        # check that the two toolchains are installed
        assert (
            len(list((release_dir / "spack" / "var" / "spack" / "environments").iterdir()))
            == 2
        )
    
        # remove toolchain2
        # toolchain1 contains - zlib@1.2
        # toolchain2 contains - zlib@1.2 and zstd@1.5
        # we check that removing toolchain2 removes zstd@1.5 but NOT zlib@1.2
        # and the environment toolchain2 is also removed
    
        mod.remove_environment(
            mpsd_release=release_to_test,
            root_dir=tmp_path,
            package_sets=["toolchain2"],
            force_remove=True,
        )
        # now check that only toolchain1 is installed
        assert (
            len(list((release_dir / "spack" / "var" / "spack" / "environments").iterdir()))
            == 1
        )
        # check that zlib@1.2 is still installed
        # spack location -i <package> exit 0 if installed and 1 if not installed
        source_spack = (
    
            f"export SPACK_ROOT={release_dir} &&"
    
            f'. {release_dir / "spack" / "share" / "spack" / "setup-env.sh"}'
    
        )
        mod.run(f"{source_spack} && spack location -i zlib", shell=True, check=True)
        # check that zstd@1.5 is not installed
        # we are here flipping the exit code to check that it is not installed
        mod.run(
            f"{source_spack} && (spack location -i zstd && exit 1 || exit 0 )",
            shell=True,
            check=True,
        )
    
        # check that the logs directory contains a build log for remove cmd
        # dev-23a_zen3_2023-08-11T15-55-54_BUILD_toolchain2_remove.log
        logs_dir = tmp_path / release_to_test / "logs"
        # remove_build_log is the last log file in the list
        remove_build_log = sorted(list(logs_dir.iterdir()))[-1]
        assert "toolchain2_remove.log" in remove_build_log.name
        with open(remove_build_log, "r") as f:
            logs = f.read()
            assert "==> Will not uninstall zlib@" in logs
            assert "==> Successfully removed environment 'toolchain2'" in logs
    
    def test_remove_global_package_sets():
        """Test removal of global package_sets via spack."""
        root_dir = Path("/tmp/test_global_generic")
    
        if not root_dir.exists():
            # we need the sample spack instance with global_generic
            # this is already done in test_install_environment_zlib
            # so we just need to call it
            test_install_environment_zlib()
        # check that zlib and zstd are installed
    
        spack_dir = (
            root_dir / release_to_test / mod.get_native_microarchitecture() / "spack"
        )
    
        source_spack = (
    
            f"export SPACK_ROOT={spack_dir} &&"
    
            f'. {spack_dir / "share" / "spack" / "setup-env.sh"}'
        )
        # check that zlib is installed
        # location commands exits with non zero if not installed thus
        # breaking failing test
        mod.run(f"{source_spack} && spack location -i zlib", shell=True, check=True)
        # check that zstd is installed
        mod.run(f"{source_spack} && spack location -i zstd", shell=True, check=True)
    
        # remove global_generic
        mod.remove_environment(
    
            mpsd_release=release_to_test,
    
            root_dir=root_dir,
            package_sets=["global_generic"],
            force_remove=True,
        )
        # check that zstd@1.5 is not installed
        # we are here flipping the exit code to check that it is not installed
        mod.run(
            f"{source_spack} && (spack location -i zstd && exit 1 || exit 0 )",
            shell=True,
            check=True,
        )
        # check that zlib is not installed
        mod.run(
            f"{source_spack} && (spack location -i zlib && exit 1 || exit 0 )",
            shell=True,
            check=True,
        )
    
        # check that the logs directory contains a build log for remove cmd
        # dev-23a_zen3_2023-08-11T15-55-54_BUILD_toolchain2_remove.log
        logs_dir = root_dir / release_to_test / "logs"
        # remove_build_log is the last log file in the list
        remove_build_log = sorted(list(logs_dir.iterdir()))[-1]
        assert "global_generic_remove.log" in remove_build_log.name
        with open(remove_build_log, "r") as f:
            logs = f.read()
            assert "==> Successfully uninstalled zstd" in logs
            assert "==> Successfully uninstalled zlib" in logs
    
    def test_interface(tmp_path):
    
    Hans Fangohr's avatar
    Hans Fangohr committed
        """Test other things (not implemented yet)."""
    
        # ensure that installing without package_sets only passes the available package_sets
    
        # check that the script branch and hash are correct when running the script
        # check that the help message is printed when no arguments are provided
        # check that the help message is printed when -h is provided
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # check that the error messages are also logged to the log file
    
        # check that `/` in release is handled correctly
    
    Ashwin Kumar Karnad's avatar
    Ashwin Kumar Karnad committed
        # check that the cmd_log file contains sys arguments
        # check that the cmd_log file contains the script version for init
        # check that the cmd_log file contains the location of APEX log
    
    
    
    # other tests to add (ideally)
    # - get_native_microarchitecture()