Skip to content

Commit f1fb70a

Browse files
committed
Invalidate layer locks for launch module changes
Closes #89
1 parent 3da1037 commit f1fb70a

File tree

4 files changed

+50
-8
lines changed

4 files changed

+50
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Changed
2+
-------
3+
4+
- Layer locks are now invalidated for launch module changes. This also means
5+
that implicit versioning will update the layer version (resolves :issue:`89`).

src/venvstacks/stacks.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2163,9 +2163,11 @@ def __post_init__(self) -> None:
21632163
launch_module_path = self.env_spec.launch_module_path
21642164
self.launch_module_name = launch_module_path.stem
21652165
if launch_module_path.is_file():
2166-
self._launch_module_hash = _hash_file_name_and_contents(launch_module_path)
2166+
launch_module_hash = _hash_file_name_and_contents(launch_module_path)
21672167
else:
2168-
self._launch_module_hash = _hash_directory(launch_module_path)
2168+
launch_module_hash = _hash_directory(launch_module_path)
2169+
self._launch_module_hash = launch_module_hash
2170+
self.env_lock.append_other_input(launch_module_hash)
21692171

21702172
def _update_existing_environment(self, *, lock_only: bool = False) -> None:
21712173
super()._update_existing_environment(lock_only=lock_only)

tests/expected-output-config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ UV_EXCLUDE_NEWER="2025-04-27 00:00:00+00:00"
3333
# Metadata updates can also be requested when the
3434
# launch module content in the sample project changes
3535

36-
# Last requested update: include names in launch module hashes
36+
# Last requested update: include launch module hash in env lock checks

tests/test_env_locks.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Test cases for environment lock management."""
22

3+
import shutil
34
import tempfile
45
import tomllib
56

@@ -137,8 +138,7 @@ def test_requirements_file_hashing(temp_dir_path: Path) -> None:
137138
##################################
138139

139140
EMPTY_SCRIPT_PATH = Path(__file__).parent / "minimal_project/empty.py"
140-
EMPTY_SCRIPT_PATH_POSIX = EMPTY_SCRIPT_PATH.as_posix()
141-
EXAMPLE_STACK_SPEC = f"""\
141+
EXAMPLE_STACK_SPEC = """\
142142
[[runtimes]]
143143
name = "cpython-to-be-modified"
144144
python_implementation = "[email protected]"
@@ -178,7 +178,7 @@ def test_requirements_file_hashing(temp_dir_path: Path) -> None:
178178
179179
[[applications]]
180180
name = "to-be-modified"
181-
launch_module = "{EMPTY_SCRIPT_PATH_POSIX}"
181+
launch_module = "launch.py"
182182
# "dependent" must be first here to allow linearisation
183183
# when that layer also depends on "other-app-dependency"
184184
frameworks = ["dependent", "other-app-dependency"]
@@ -187,12 +187,12 @@ def test_requirements_file_hashing(temp_dir_path: Path) -> None:
187187
[[applications]]
188188
name = "runtime-only"
189189
runtime = "cpython-to-be-modified"
190-
launch_module = "{EMPTY_SCRIPT_PATH_POSIX}"
190+
launch_module = "launch2.py"
191191
requirements = []
192192
193193
[[applications]]
194194
name = "unaffected"
195-
launch_module = "{EMPTY_SCRIPT_PATH_POSIX}"
195+
launch_module = "launch2.py"
196196
frameworks = ["unaffected"]
197197
requirements = []
198198
"""
@@ -243,6 +243,9 @@ def _modified_file(file_path: Path, contents: str) -> Generator[Any, None, None]
243243
def test_build_env_layer_locks(temp_dir_path: Path, subtests: SubTests) -> None:
244244
# Built as a monolithic tests with subtests for performance reasons
245245
# (initial setup takes ~10 seconds, subsequent checks are fractions of a second)
246+
launch_module_path = temp_dir_path / "launch.py"
247+
shutil.copyfile(EMPTY_SCRIPT_PATH, launch_module_path)
248+
shutil.copyfile(EMPTY_SCRIPT_PATH, temp_dir_path / "launch2.py")
246249
spec_path = temp_dir_path / "venvstacks.toml"
247250
updated_spec_path = temp_dir_path / "venvstacks_updated.toml"
248251
spec_data = tomllib.loads(EXAMPLE_STACK_SPEC)
@@ -505,6 +508,22 @@ def test_build_env_layer_locks(temp_dir_path: Path, subtests: SubTests) -> None:
505508
assert invalid_locks == expected_invalid_locks
506509
assert build_env._needs_lock()
507510
subtests_passed += 1
511+
with subtests.test("Change launch module name at application layer"):
512+
# Even if the locked requirements don't change, the layer needs updating
513+
# This is due to the launch module needing to be invoked differently
514+
subtests_started += 1
515+
spec_data_to_check = tomllib.loads(EXAMPLE_STACK_SPEC)
516+
env_spec_to_modify = spec_data_to_check["applications"][0]
517+
assert env_spec_to_modify["name"] == "to-be-modified"
518+
env_spec_to_modify["launch_module"] = "launch2.py"
519+
build_env = _define_lock_testing_env(updated_spec_path, spec_data_to_check)
520+
expected_invalid_locks = {"app-to-be-modified"}
521+
expected_valid_locks = all_layer_names - expected_invalid_locks
522+
valid_locks, invalid_locks = _partition_envs(build_env)
523+
assert valid_locks == expected_valid_locks
524+
assert invalid_locks == expected_invalid_locks
525+
assert build_env._needs_lock()
526+
subtests_passed += 1
508527
# Remaining subtests need to modify the actual envs, not just the layer specifications
509528
env_to_modify: LayerEnvBase
510529
with subtests.test("Change locked requirements at runtime layer"):
@@ -558,6 +577,22 @@ def test_build_env_layer_locks(temp_dir_path: Path, subtests: SubTests) -> None:
558577
assert invalid_locks == expected_invalid_locks
559578
assert build_env._needs_lock()
560579
subtests_passed += 1
580+
with subtests.test("Change launch module content at application layer"):
581+
# Even if the locked requirements don't change, the layer needs updating
582+
# This is due to the launch module needing to be invoked differently
583+
subtests_started += 1
584+
spec_data_to_check = tomllib.loads(EXAMPLE_STACK_SPEC)
585+
with _modified_file(
586+
launch_module_path, "# Changed launch module contents"
587+
):
588+
build_env = _define_lock_testing_env(updated_spec_path, spec_data_to_check)
589+
expected_invalid_locks = {"app-to-be-modified"}
590+
expected_valid_locks = all_layer_names - expected_invalid_locks
591+
valid_locks, invalid_locks = _partition_envs(build_env)
592+
assert valid_locks == expected_valid_locks
593+
assert invalid_locks == expected_invalid_locks
594+
assert build_env._needs_lock()
595+
subtests_passed += 1
561596

562597
# Work around pytest-subtests not failing the test case when subtests fail
563598
# https://github.com/pytest-dev/pytest-subtests/issues/76

0 commit comments

Comments
 (0)