|
| 1 | +import hashlib |
1 | 2 | import os |
| 3 | +from types import SimpleNamespace |
2 | 4 | from typing import Optional, Type |
3 | 5 | from snakemake_interface_software_deployment_plugins import EnvBase, EnvSpecBase |
4 | 6 | from snakemake_interface_software_deployment_plugins.tests import ( |
@@ -44,3 +46,95 @@ def get_settings( |
44 | 46 | def get_test_cmd(self) -> str: |
45 | 47 | # Return a command that should be executable without error in the environment |
46 | 48 | return "somecmd" |
| 49 | + |
| 50 | + |
| 51 | +# --------------------------------------------------------------------------- |
| 52 | +# Unit tests for EnvSpec and the pure Env methods. |
| 53 | +# |
| 54 | +# These raise test coverage and guard the fix that made |
| 55 | +# identity_attributes / source_path_attributes classmethods (so that EnvSpec is |
| 56 | +# hashable and usable as a dict key, which the software-deployment manager |
| 57 | +# relies on for its specs_to_envs cache). |
| 58 | +# --------------------------------------------------------------------------- |
| 59 | + |
| 60 | + |
| 61 | +def _env_with_spec(spec): |
| 62 | + """An Env-like object with only `.spec` set. |
| 63 | +
|
| 64 | + Env.__post_init__ runs check(), which needs the `module` command, so we |
| 65 | + bypass construction and exercise only methods that depend on self.spec. |
| 66 | + """ |
| 67 | + return SimpleNamespace(spec=spec) |
| 68 | + |
| 69 | + |
| 70 | +def _spec(*names): |
| 71 | + """Construct an EnvSpec the way the directive factory does (with technical_init). |
| 72 | +
|
| 73 | + technical_init() is required before hashing/equality, because EnvSpecBase.__hash__ |
| 74 | + reads managed attributes (e.g. self.kind) that it sets. |
| 75 | + """ |
| 76 | + spec = EnvSpec(*names) |
| 77 | + spec.technical_init() |
| 78 | + return spec |
| 79 | + |
| 80 | + |
| 81 | +def test_envspec_stores_names_as_tuple(): |
| 82 | + assert _spec("bwa", "samtools").names == ("bwa", "samtools") |
| 83 | + |
| 84 | + |
| 85 | +def test_envspec_str_is_comma_joined(): |
| 86 | + assert str(_spec("bwa", "samtools")) == "bwa,samtools" |
| 87 | + assert str(_spec("bwa")) == "bwa" |
| 88 | + |
| 89 | + |
| 90 | +def test_identity_attributes_yields_names(): |
| 91 | + # classmethod: callable on the class itself |
| 92 | + assert list(EnvSpec.identity_attributes()) == ["names"] |
| 93 | + |
| 94 | + |
| 95 | +def test_source_path_attributes_is_empty(): |
| 96 | + assert list(EnvSpec.source_path_attributes()) == [] |
| 97 | + |
| 98 | + |
| 99 | +def test_envspec_hash_and_equality_use_names(): |
| 100 | + # Regression guard: __hash__ resolves through cls.identity_attributes(), |
| 101 | + # which only works because identity_attributes is now a @classmethod. |
| 102 | + a = _spec("bwa", "samtools") |
| 103 | + b = _spec("bwa", "samtools") |
| 104 | + c = _spec("bwa") |
| 105 | + assert a == b |
| 106 | + assert hash(a) == hash(b) |
| 107 | + assert a != c |
| 108 | + assert hash(a) != hash(c) |
| 109 | + |
| 110 | + |
| 111 | +def test_envspec_is_usable_as_dict_key(): |
| 112 | + # The original crash: hashing an envmodules spec raised TypeError, so it |
| 113 | + # could not be used as a dict key (e.g. in specs_to_envs). |
| 114 | + spec = _spec("bwa") |
| 115 | + lookup = _spec("bwa") |
| 116 | + assert {spec: "env"}[lookup] == "env" |
| 117 | + |
| 118 | + |
| 119 | +def test_env_decorate_shellcmd_loads_modules(): |
| 120 | + env = _env_with_spec(_spec("bwa", "samtools")) |
| 121 | + assert Env.decorate_shellcmd(env, "echo hi") == ( |
| 122 | + "module purge && module load bwa samtools && echo hi" |
| 123 | + ) |
| 124 | + |
| 125 | + |
| 126 | +def test_env_decorate_shellcmd_quotes_unsafe_names(): |
| 127 | + env = _env_with_spec(_spec("name with space")) |
| 128 | + assert "module load 'name with space'" in Env.decorate_shellcmd(env, "cmd") |
| 129 | + |
| 130 | + |
| 131 | +def test_env_record_hash_uses_names(): |
| 132 | + env = _env_with_spec(_spec("bwa", "samtools")) |
| 133 | + h = hashlib.sha256() |
| 134 | + Env.record_hash(env, h) |
| 135 | + assert h.hexdigest() == hashlib.sha256(b"bwa,samtools").hexdigest() |
| 136 | + |
| 137 | + |
| 138 | +def test_env_report_software_is_empty(): |
| 139 | + env = _env_with_spec(_spec("bwa")) |
| 140 | + assert list(Env.report_software(env)) == [] |
0 commit comments