Skip to content

Commit 9d0c40c

Browse files
authored
🗑️ Implement clean up for test outputs (#960)
- Implement clean up of test outputs at the end of each run for efficiency.
1 parent c9f026b commit 9d0c40c

22 files changed

+734
-636
lines changed

tests/conftest.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,3 +658,39 @@ def timed(fn: Callable, *args: object) -> (Callable, float):
658658
end = time.time()
659659
compile_time = end - start
660660
return result, compile_time
661+
662+
663+
_tmp_paths: list[Path] = []
664+
665+
666+
@pytest.fixture
667+
def track_tmp_path(tmp_path: Path) -> Path:
668+
"""This fixture tracks `tmp_path` for clean up.
669+
670+
Fixture that wraps pytest's built-in `tmp_path` and tracks each temporary path
671+
for later cleanup at the module level.
672+
673+
Returns:
674+
Path: The temporary directory path for the current test function.
675+
676+
"""
677+
_tmp_paths.append(tmp_path)
678+
return tmp_path
679+
680+
681+
@pytest.fixture(scope="module", autouse=True)
682+
def module_teardown() -> None:
683+
"""This module tears down temporary data directories.
684+
685+
Module-scoped fixture that automatically runs after all tests in a module.
686+
It cleans up all temporary paths tracked during the module's execution.
687+
688+
Yields:
689+
None: Allows pytest to run tests before executing the teardown logic.
690+
691+
"""
692+
yield
693+
for path in _tmp_paths:
694+
if path.exists():
695+
shutil.rmtree(path)
696+
print(f"Cleaned up: {path}")

tests/models/test_arch_micronet.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_value_error() -> None:
5454
toolbox_env.running_on_ci() or not ON_GPU,
5555
reason="Local test on machine with GPU.",
5656
)
57-
def test_micronet_output(remote_sample: Callable, tmp_path: Path) -> None:
57+
def test_micronet_output(remote_sample: Callable, track_tmp_path: Path) -> None:
5858
"""Test the output of MicroNet."""
5959
svs_1_small = Path(remote_sample("svs-1-small"))
6060
micronet_output = Path(remote_sample("micronet-output"))
@@ -74,7 +74,7 @@ def test_micronet_output(remote_sample: Callable, tmp_path: Path) -> None:
7474
imgs=[
7575
svs_1_small,
7676
],
77-
save_dir=tmp_path / "output",
77+
save_dir=track_tmp_path / "output",
7878
)
7979

8080
output = np.load(output[0][1] + ".raw.0.npy")

tests/models/test_dataset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ def test_kather_nonexisting_dir() -> None:
8686
_ = KatherPatchDataset(save_dir_path="non-existing-path")
8787

8888

89-
def test_kather_dataset(tmp_path: Path) -> None:
89+
def test_kather_dataset(track_tmp_path: Path) -> None:
9090
"""Test for kather patch dataset."""
91-
save_dir_path = tmp_path
91+
save_dir_path = track_tmp_path
9292

9393
# save to temporary location
9494
# remove previously generated data

tests/models/test_feature_extractor.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
# -------------------------------------------------------------------------------------
2525

2626

27-
def test_engine(remote_sample: Callable, tmp_path: Path) -> None:
27+
def test_engine(remote_sample: Callable, track_tmp_path: Path) -> None:
2828
"""Test feature extraction with DeepFeatureExtractor engine."""
29-
save_dir = tmp_path / "output"
29+
save_dir = track_tmp_path / "output"
3030
# # convert to pathlib Path to prevent wsireader complaint
3131
mini_wsi_svs = Path(remote_sample("wsi4_1k_1k_svs"))
3232

@@ -55,10 +55,10 @@ def test_engine(remote_sample: Callable, tmp_path: Path) -> None:
5555
"model", [CNNBackbone("resnet50"), TimmBackbone("efficientnet_b0", pretrained=True)]
5656
)
5757
def test_full_inference(
58-
remote_sample: Callable, tmp_path: Path, model: Callable
58+
remote_sample: Callable, track_tmp_path: Path, model: Callable
5959
) -> None:
6060
"""Test full inference with CNNBackbone and TimmBackbone models."""
61-
save_dir = tmp_path / "output"
61+
save_dir = track_tmp_path / "output"
6262
# pre-emptive clean up
6363
shutil.rmtree(save_dir, ignore_errors=True) # default output dir test
6464

@@ -121,9 +121,11 @@ def test_full_inference(
121121
toolbox_env.running_on_ci() or not ON_GPU,
122122
reason="Local test on machine with GPU.",
123123
)
124-
def test_multi_gpu_feature_extraction(remote_sample: Callable, tmp_path: Path) -> None:
124+
def test_multi_gpu_feature_extraction(
125+
remote_sample: Callable, track_tmp_path: Path
126+
) -> None:
125127
"""Local functionality test for feature extraction using multiple GPUs."""
126-
save_dir = tmp_path / "output"
128+
save_dir = track_tmp_path / "output"
127129
mini_wsi_svs = Path(remote_sample("wsi4_1k_1k_svs"))
128130
shutil.rmtree(save_dir, ignore_errors=True)
129131

tests/models/test_multi_task_segmentor.py

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ def semantic_postproc_func(raw_output: np.ndarray) -> np.ndarray:
4848
toolbox_env.running_on_ci() or not ON_GPU,
4949
reason="Local test on machine with GPU.",
5050
)
51-
def test_functionality_local(remote_sample: Callable, tmp_path: Path) -> None:
51+
def test_functionality_local(remote_sample: Callable, track_tmp_path: Path) -> None:
5252
"""Local functionality test for multi task segmentor."""
5353
gc.collect()
54-
root_save_dir = Path(tmp_path)
54+
root_save_dir = Path(track_tmp_path)
5555
mini_wsi_svs = Path(remote_sample("svs-1-small"))
5656
save_dir = root_save_dir / "multitask"
5757
shutil.rmtree(save_dir, ignore_errors=True)
@@ -100,9 +100,11 @@ def test_functionality_local(remote_sample: Callable, tmp_path: Path) -> None:
100100
assert score > 0.95, "Heavy loss of precision!"
101101

102102

103-
def test_functionality_hovernetplus(remote_sample: Callable, tmp_path: Path) -> None:
103+
def test_functionality_hovernetplus(
104+
remote_sample: Callable, track_tmp_path: Path
105+
) -> None:
104106
"""Functionality test for multitask segmentor."""
105-
root_save_dir = Path(tmp_path)
107+
root_save_dir = Path(track_tmp_path)
106108
mini_wsi_svs = Path(remote_sample("wsi4_512_512_svs"))
107109
required_dims = (258, 258)
108110
# above image is 512 x 512 at 0.252 mpp resolution. This is 258 x 258 at 0.500 mpp.
@@ -133,9 +135,9 @@ def test_functionality_hovernetplus(remote_sample: Callable, tmp_path: Path) ->
133135
)
134136

135137

136-
def test_functionality_hovernet(remote_sample: Callable, tmp_path: Path) -> None:
138+
def test_functionality_hovernet(remote_sample: Callable, track_tmp_path: Path) -> None:
137139
"""Functionality test for multitask segmentor."""
138-
root_save_dir = Path(tmp_path)
140+
root_save_dir = Path(track_tmp_path)
139141
mini_wsi_svs = Path(remote_sample("wsi4_512_512_svs"))
140142

141143
save_dir = root_save_dir / "multi"
@@ -159,14 +161,14 @@ def test_functionality_hovernet(remote_sample: Callable, tmp_path: Path) -> None
159161
assert len(inst_dict) > 0, "Must have some nuclei."
160162

161163

162-
def test_masked_segmentor(remote_sample: Callable, tmp_path: Path) -> None:
164+
def test_masked_segmentor(remote_sample: Callable, track_tmp_path: Path) -> None:
163165
"""Test segmentor when image is masked."""
164-
root_save_dir = Path(tmp_path)
166+
root_save_dir = Path(track_tmp_path)
165167
sample_wsi_svs = Path(remote_sample("svs-1-small"))
166168
sample_wsi_msk = remote_sample("small_svs_tissue_mask")
167169
sample_wsi_msk = np.load(sample_wsi_msk).astype(np.uint8)
168-
imwrite(f"{tmp_path}/small_svs_tissue_mask.jpg", sample_wsi_msk)
169-
sample_wsi_msk = tmp_path.joinpath("small_svs_tissue_mask.jpg")
170+
imwrite(f"{track_tmp_path}/small_svs_tissue_mask.jpg", sample_wsi_msk)
171+
sample_wsi_msk = track_tmp_path.joinpath("small_svs_tissue_mask.jpg")
170172

171173
save_dir = root_save_dir / "instance"
172174

@@ -208,10 +210,10 @@ def test_masked_segmentor(remote_sample: Callable, tmp_path: Path) -> None:
208210

209211
def test_functionality_process_instance_predictions(
210212
remote_sample: Callable,
211-
tmp_path: Path,
213+
track_tmp_path: Path,
212214
) -> None:
213215
"""Test the functionality of instance predictions processing."""
214-
root_save_dir = Path(tmp_path)
216+
root_save_dir = Path(track_tmp_path)
215217
mini_wsi_svs = Path(remote_sample("wsi4_512_512_svs"))
216218

217219
save_dir = root_save_dir / "semantic"
@@ -251,9 +253,9 @@ def test_functionality_process_instance_predictions(
251253
assert len(multi_segmentor._wsi_inst_info[0]) == 0
252254

253255

254-
def test_empty_image(tmp_path: Path) -> None:
256+
def test_empty_image(track_tmp_path: Path) -> None:
255257
"""Test MultiTaskSegmentor for an empty image."""
256-
root_save_dir = Path(tmp_path)
258+
root_save_dir = Path(track_tmp_path)
257259
sample_patch = np.ones((256, 256, 3), dtype="uint8") * 255
258260
sample_patch_path = root_save_dir / "sample_tile.png"
259261
imwrite(sample_patch_path, sample_patch)
@@ -320,9 +322,9 @@ def test_empty_image(tmp_path: Path) -> None:
320322
)
321323

322324

323-
def test_functionality_semantic(remote_sample: Callable, tmp_path: Path) -> None:
325+
def test_functionality_semantic(remote_sample: Callable, track_tmp_path: Path) -> None:
324326
"""Functionality test for multitask segmentor."""
325-
root_save_dir = Path(tmp_path)
327+
root_save_dir = Path(track_tmp_path)
326328

327329
save_dir = root_save_dir / "multi"
328330
shutil.rmtree(save_dir, ignore_errors=True)
@@ -373,14 +375,14 @@ def test_functionality_semantic(remote_sample: Callable, tmp_path: Path) -> None
373375
assert layer_map is not None, "Must have some segmentations."
374376

375377

376-
def test_crash_segmentor(remote_sample: Callable, tmp_path: Path) -> None:
378+
def test_crash_segmentor(remote_sample: Callable, track_tmp_path: Path) -> None:
377379
"""Test engine crash when given malformed input."""
378-
root_save_dir = Path(tmp_path)
380+
root_save_dir = Path(track_tmp_path)
379381
sample_wsi_svs = Path(remote_sample("svs-1-small"))
380382
sample_wsi_msk = remote_sample("small_svs_tissue_mask")
381383
sample_wsi_msk = np.load(sample_wsi_msk).astype(np.uint8)
382-
imwrite(f"{tmp_path}/small_svs_tissue_mask.jpg", sample_wsi_msk)
383-
sample_wsi_msk = tmp_path.joinpath("small_svs_tissue_mask.jpg")
384+
imwrite(f"{track_tmp_path}/small_svs_tissue_mask.jpg", sample_wsi_msk)
385+
sample_wsi_msk = track_tmp_path.joinpath("small_svs_tissue_mask.jpg")
384386

385387
save_dir = f"{root_save_dir}/multi/"
386388

tests/models/test_nucleus_instance_segmentor.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,14 @@ def test_cross_section_boundary_boxes() -> None:
237237
assert np.sum(flag - _flag) == 0, "Fail Cross Section Flag"
238238

239239

240-
def test_crash_segmentor(remote_sample: Callable, tmp_path: Path) -> None:
240+
def test_crash_segmentor(remote_sample: Callable, track_tmp_path: Path) -> None:
241241
"""Test engine crash when given malformed input."""
242-
root_save_dir = Path(tmp_path)
242+
root_save_dir = Path(track_tmp_path)
243243
sample_wsi_svs = Path(remote_sample("svs-1-small"))
244244
sample_wsi_msk = remote_sample("small_svs_tissue_mask")
245245
sample_wsi_msk = np.load(sample_wsi_msk).astype(np.uint8)
246-
imwrite(f"{tmp_path}/small_svs_tissue_mask.jpg", sample_wsi_msk)
247-
sample_wsi_msk = tmp_path.joinpath("small_svs_tissue_mask.jpg")
246+
imwrite(f"{track_tmp_path}/small_svs_tissue_mask.jpg", sample_wsi_msk)
247+
sample_wsi_msk = track_tmp_path.joinpath("small_svs_tissue_mask.jpg")
248248

249249
save_dir = f"{root_save_dir}/instance/"
250250

@@ -285,17 +285,17 @@ def test_crash_segmentor(remote_sample: Callable, tmp_path: Path) -> None:
285285
)
286286

287287

288-
def test_functionality_ci(remote_sample: Callable, tmp_path: Path) -> None:
288+
def test_functionality_ci(remote_sample: Callable, track_tmp_path: Path) -> None:
289289
"""Functionality test for nuclei instance segmentor."""
290290
gc.collect()
291-
root_save_dir = Path(tmp_path)
291+
root_save_dir = Path(track_tmp_path)
292292
mini_wsi_svs = Path(remote_sample("wsi4_512_512_svs"))
293293

294294
resolution = 2.0
295295

296296
reader = WSIReader.open(mini_wsi_svs)
297297
thumb = reader.slide_thumbnail(resolution=resolution, units="mpp")
298-
mini_wsi_jpg = f"{tmp_path}/mini_svs.jpg"
298+
mini_wsi_jpg = f"{track_tmp_path}/mini_svs.jpg"
299299
imwrite(mini_wsi_jpg, thumb)
300300

301301
save_dir = f"{root_save_dir}/instance/"
@@ -335,11 +335,11 @@ def test_functionality_ci(remote_sample: Callable, tmp_path: Path) -> None:
335335

336336
def test_functionality_merge_tile_predictions_ci(
337337
remote_sample: Callable,
338-
tmp_path: Path,
338+
track_tmp_path: Path,
339339
) -> None:
340340
"""Functional tests for merging tile predictions."""
341341
gc.collect() # Force clean up everything on hold
342-
save_dir = Path(f"{tmp_path}/output")
342+
save_dir = Path(f"{track_tmp_path}/output")
343343
mini_wsi_svs = Path(remote_sample("wsi4_512_512_svs"))
344344

345345
resolution = 0.5
@@ -438,10 +438,10 @@ def test_functionality_merge_tile_predictions_ci(
438438
toolbox_env.running_on_ci() or not ON_GPU,
439439
reason="Local test on machine with GPU.",
440440
)
441-
def test_functionality_local(remote_sample: Callable, tmp_path: Path) -> None:
441+
def test_functionality_local(remote_sample: Callable, track_tmp_path: Path) -> None:
442442
"""Local functionality test for nuclei instance segmentor."""
443-
root_save_dir = Path(tmp_path)
444-
save_dir = Path(f"{tmp_path}/output")
443+
root_save_dir = Path(track_tmp_path)
444+
save_dir = Path(f"{track_tmp_path}/output")
445445
mini_wsi_svs = Path(remote_sample("wsi4_1k_1k_svs"))
446446

447447
# * generate full output w/o parallel post-processing worker first
@@ -512,17 +512,17 @@ def test_functionality_local(remote_sample: Callable, tmp_path: Path) -> None:
512512

513513
def test_cli_nucleus_instance_segment_ioconfig(
514514
remote_sample: Callable,
515-
tmp_path: Path,
515+
track_tmp_path: Path,
516516
) -> None:
517517
"""Test for nucleus segmentation with IOConfig."""
518518
mini_wsi_svs = Path(remote_sample("wsi4_512_512_svs"))
519-
output_path = tmp_path / "output"
519+
output_path = track_tmp_path / "output"
520520

521521
resolution = 2.0
522522

523523
reader = WSIReader.open(mini_wsi_svs)
524524
thumb = reader.slide_thumbnail(resolution=resolution, units="mpp")
525-
mini_wsi_jpg = f"{tmp_path}/mini_svs.jpg"
525+
mini_wsi_jpg = f"{track_tmp_path}/mini_svs.jpg"
526526
imwrite(mini_wsi_jpg, thumb)
527527

528528
pretrained_weights = fetch_pretrained_weights("hovernet_fast-pannuke")
@@ -543,7 +543,7 @@ def test_cli_nucleus_instance_segment_ioconfig(
543543
"save_resolution": {"units": "mpp", "resolution": 8.0},
544544
}
545545

546-
with Path.open(tmp_path / "config.yaml", "w") as fptr:
546+
with Path.open(track_tmp_path / "config.yaml", "w") as fptr:
547547
yaml.dump(config, fptr)
548548

549549
runner = CliRunner()
@@ -564,7 +564,7 @@ def test_cli_nucleus_instance_segment_ioconfig(
564564
"--output-path",
565565
str(output_path),
566566
"--yaml-config-path",
567-
str(tmp_path.joinpath("config.yaml")),
567+
str(track_tmp_path.joinpath("config.yaml")),
568568
],
569569
)
570570

@@ -574,10 +574,12 @@ def test_cli_nucleus_instance_segment_ioconfig(
574574
assert output_path.joinpath("results.json").exists()
575575

576576

577-
def test_cli_nucleus_instance_segment(remote_sample: Callable, tmp_path: Path) -> None:
577+
def test_cli_nucleus_instance_segment(
578+
remote_sample: Callable, track_tmp_path: Path
579+
) -> None:
578580
"""Test for nucleus segmentation."""
579581
mini_wsi_svs = Path(remote_sample("wsi4_512_512_svs"))
580-
output_path = tmp_path / "output"
582+
output_path = track_tmp_path / "output"
581583

582584
runner = CliRunner()
583585
nucleus_instance_segment_result = runner.invoke(

0 commit comments

Comments
 (0)