Coverage for tests/test_dataset_examples.py: 0%

94 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-16 16:14 -0700

1#!/usr/bin/env python 

2""" 

3Test script to ensure that class names match filenames in the dataset examples. 

4""" 

5 

6import os 

7import random 

8import re 

9import shutil 

10import sys 

11import tempfile 

12import unittest 

13from pathlib import Path 

14from unittest.mock import MagicMock, patch 

15 

16import numpy as np 

17import zarr 

18 

19# Add the script directory to the path to import generate_dataset_examples 

20sys.path.append(os.path.join(os.path.dirname(__file__), "..", "scripts")) 

21 

22 

23class TestDatasetExamples(unittest.TestCase): 

24 """ 

25 Test class to verify the correct relationship between class names and file names 

26 in the dataset_examples documentation. 

27 """ 

28 

29 @classmethod 

30 def setUpClass(cls): 

31 """Set up a temporary directory for testing.""" 

32 cls.temp_dir = tempfile.mkdtemp() 

33 cls.docs_dir = os.path.join(cls.temp_dir, "docs") 

34 os.makedirs(cls.docs_dir, exist_ok=True) 

35 

36 # Copy the original script output path to a backup 

37 cls.original_docs_dir = Path("docs/dataset_examples") 

38 if cls.original_docs_dir.exists(): 

39 cls.backup_dir = Path(tempfile.mkdtemp()) 

40 shutil.copytree(cls.original_docs_dir, cls.backup_dir / "dataset_examples") 

41 print(f"Backed up original docs to {cls.backup_dir}") 

42 else: 

43 cls.backup_dir = None 

44 

45 @classmethod 

46 def tearDownClass(cls): 

47 """Clean up the temporary directory.""" 

48 shutil.rmtree(cls.temp_dir) 

49 

50 # Restore the original docs if they were backed up 

51 if cls.backup_dir and cls.original_docs_dir.exists(): 

52 shutil.rmtree(cls.original_docs_dir) 

53 shutil.copytree(cls.backup_dir / "dataset_examples", cls.original_docs_dir) 

54 shutil.rmtree(cls.backup_dir) 

55 print("Restored original docs") 

56 

57 @patch("zarr.open") 

58 @patch("copick.from_czcdp_datasets") 

59 def test_class_name_to_filename_consistency(self, mock_from_czcdp, mock_zarr_open): 

60 """ 

61 Test that each class name in the markdown file correctly corresponds to a matching 

62 filename in the directory. 

63 

64 The test patches the output directory and then run the generate_docs function 

65 with mocked dependencies to avoid actual copick operations. 

66 """ 

67 # Set up mock for copick.from_czcdp_datasets 

68 mock_copick_root = MagicMock() 

69 mock_run = MagicMock() 

70 mock_vs = MagicMock() 

71 mock_tomogram = MagicMock() 

72 

73 # Configure mocks 

74 mock_from_czcdp.return_value = mock_copick_root 

75 mock_copick_root.runs = [mock_run] 

76 mock_copick_root.pickable_objects = [ 

77 MagicMock(name="ferritin-complex"), 

78 MagicMock(name="virus-like-capsid"), 

79 MagicMock(name="cytosolic-ribosome"), 

80 MagicMock(name="membrane"), 

81 MagicMock(name="beta-galactosidase"), 

82 MagicMock(name="beta-amylase"), 

83 MagicMock(name="thyroglobulin"), 

84 ] 

85 for po in mock_copick_root.pickable_objects: 

86 po.name = po.name 

87 

88 mock_run.name = "test_run" 

89 mock_run.get_voxel_spacing.return_value = mock_vs 

90 mock_vs.tomograms = [mock_tomogram] 

91 mock_tomogram.tomo_type = "wbp-denoised" 

92 

93 # Mock picks for each object type 

94 mock_picks_list = [] 

95 for po in mock_copick_root.pickable_objects: 

96 mock_pick = MagicMock() 

97 mock_pick.from_tool = True 

98 mock_pick.pickable_object_name = po.name 

99 mock_pick.numpy.return_value = (np.array([[100, 100, 100]]), None) 

100 mock_picks_list.append(mock_pick) 

101 

102 mock_run.get_picks.return_value = mock_picks_list 

103 

104 # Mock zarr.open to return a dummy array 

105 mock_zarr_root = MagicMock() 

106 mock_zarr_open.return_value = mock_zarr_root 

107 mock_zarr_root.__getitem__.return_value = np.random.randn(100, 100, 100) 

108 mock_tomogram.zarr.return_value = "dummy_zarr_path" 

109 

110 # We'll patch the output directory and then run the generate_docs function 

111 original_output_dir = Path("docs/dataset_examples") 

112 temp_output_dir = Path(self.docs_dir) / "dataset_examples" 

113 

114 # Monkey patch the Path class to redirect the output 

115 original_init = Path.__init__ 

116 

117 def patched_init(self_path, *args, **kwargs): 

118 # Replace the output directory path with our temp directory 

119 arg_str = str(args[0]) if args else "" 

120 if arg_str == str(original_output_dir): 

121 original_init(self_path, temp_output_dir, **kwargs) 

122 else: 

123 original_init(self_path, *args, **kwargs) 

124 

125 # Apply monkey patch 

126 Path.__init__ = patched_init 

127 

128 try: 

129 # Run the document generation script 

130 from generate_dataset_examples import main as generate_docs 

131 

132 generate_docs() 

133 

134 # Verify the markdown file exists 

135 md_file = temp_output_dir / "README.md" 

136 self.assertTrue(md_file.exists(), f"README.md file not found at {md_file}") 

137 

138 # Read the markdown file 

139 with open(md_file, "r") as f: 

140 content = f.read() 

141 

142 # Extract all class names and filenames 

143 class_pattern = re.compile(r"## Class: ([^\n]+)") 

144 image_pattern = re.compile(r"!\[(.*?)\]\(\./([^)]+)\)") 

145 

146 class_names = class_pattern.findall(content) 

147 image_refs = image_pattern.findall(content) 

148 

149 # Check that all class sections have a corresponding image 

150 self.assertEqual( 

151 len(class_names), 

152 len(image_refs), 

153 f"Number of class sections ({len(class_names)}) does not match number of images ({len(image_refs)})", 

154 ) 

155 

156 # Check the directory for actual image files 

157 image_files = [f for f in os.listdir(temp_output_dir) if f.endswith(".png")] 

158 

159 # Check that all referenced images exist in the directory 

160 for _, filename in image_refs: 

161 self.assertIn(filename, image_files, f"Referenced image file {filename} not found in the directory") 

162 

163 # Verify that all class names match their corresponding images 

164 for i, class_name in enumerate(class_names): 

165 # Get the image reference for this class 

166 image_alt, image_file = image_refs[i] 

167 

168 # The alt text should match the class name 

169 self.assertEqual( 

170 class_name, 

171 image_alt, 

172 f"Alt text '{image_alt}' does not match class name '{class_name}'", 

173 ) 

174 

175 # Generate the expected filename from the class name 

176 expected_filename = f"{class_name.lower().replace(' ', '_').replace('-', '_')}.png" 

177 

178 # Check that the actual filename matches the expected filename 

179 self.assertEqual( 

180 expected_filename, 

181 image_file, 

182 f"Image filename '{image_file}' does not match expected filename '{expected_filename}' for class '{class_name}'", 

183 ) 

184 

185 # Verify the file exists with the expected name 

186 self.assertTrue((temp_output_dir / image_file).exists(), f"Image file {image_file} does not exist") 

187 

188 # Check that we have examples for all the expected pickable objects 

189 expected_class_names = [po.name for po in mock_copick_root.pickable_objects] + ["background"] 

190 for expected_class in expected_class_names: 

191 self.assertIn(expected_class, class_names, f"Missing example for expected class '{expected_class}'") 

192 

193 finally: 

194 # Restore the original Path.__init__ 

195 Path.__init__ = original_init 

196 

197 

198if __name__ == "__main__": 

199 unittest.main()