Skip to content

Commit 7b3bc51

Browse files
committed
test: add EnvSpec/Env unit tests, raising coverage to 84%
Adds unit tests for EnvSpec (names, __str__, identity_attributes, source_path_attributes, __hash__/__eq__, dict-key usability) and the pure Env methods (decorate_shellcmd, record_hash, report_software). The hash tests guard the classmethod fix. Coverage rises from 64.86% to 83.78%, clearing the fail-under=70 gate. Env.check/__post_init__/contains_executable stay uncovered as they require a live `module` command.
1 parent a6406a0 commit 7b3bc51

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

tests/test_plugin.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import hashlib
12
import os
3+
from types import SimpleNamespace
24
from typing import Optional, Type
35
from snakemake_interface_software_deployment_plugins import EnvBase, EnvSpecBase
46
from snakemake_interface_software_deployment_plugins.tests import (
@@ -44,3 +46,95 @@ def get_settings(
4446
def get_test_cmd(self) -> str:
4547
# Return a command that should be executable without error in the environment
4648
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

Comments
 (0)