Coverage for tests / unit / utils / test_file_cache.py: 100%

97 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Tests for lintro.utils.file_cache module.""" 

2 

3from __future__ import annotations 

4 

5import json 

6from pathlib import Path 

7from unittest.mock import patch 

8 

9from assertpy import assert_that 

10 

11from lintro.utils.file_cache import ( 

12 FileFingerprint, 

13 ToolCache, 

14 clear_all_caches, 

15 get_cache_stats, 

16) 

17 

18 

19def test_file_fingerprint_to_dict() -> None: 

20 """Convert fingerprint to dictionary.""" 

21 fp = FileFingerprint(path="/test/file.py", mtime=1234567890.0, size=1024) 

22 result = fp.to_dict() 

23 

24 assert_that(result).is_equal_to( 

25 { 

26 "path": "/test/file.py", 

27 "mtime": 1234567890.0, 

28 "size": 1024, 

29 }, 

30 ) 

31 

32 

33def test_file_fingerprint_from_dict() -> None: 

34 """Create fingerprint from dictionary.""" 

35 data = {"path": "/test/file.py", "mtime": 1234567890.0, "size": 1024} 

36 fp = FileFingerprint.from_dict(data) 

37 

38 assert_that(fp.path).is_equal_to("/test/file.py") 

39 assert_that(fp.mtime).is_equal_to(1234567890.0) 

40 assert_that(fp.size).is_equal_to(1024) 

41 

42 

43def test_file_fingerprint_roundtrip() -> None: 

44 """Roundtrip through to_dict and from_dict.""" 

45 original = FileFingerprint(path="/test/file.py", mtime=1234567890.0, size=1024) 

46 result = FileFingerprint.from_dict(original.to_dict()) 

47 

48 assert_that(result.path).is_equal_to(original.path) 

49 assert_that(result.mtime).is_equal_to(original.mtime) 

50 assert_that(result.size).is_equal_to(original.size) 

51 

52 

53def test_tool_cache_empty_returns_all_files_as_changed(tmp_path: Path) -> None: 

54 """Empty cache returns all files as changed. 

55 

56 Args: 

57 tmp_path: Pytest fixture for temporary directory. 

58 """ 

59 temp_file = tmp_path / "test.py" 

60 temp_file.write_text("test content") 

61 

62 cache = ToolCache(tool_name="test") 

63 changed = cache.get_changed_files([str(temp_file)]) 

64 assert_that(changed).contains(str(temp_file)) 

65 

66 

67def test_tool_cache_unchanged_file_not_returned(tmp_path: Path) -> None: 

68 """File in cache with same mtime/size not returned as changed. 

69 

70 Args: 

71 tmp_path: Pytest fixture for temporary directory. 

72 """ 

73 temp_file = tmp_path / "test.py" 

74 temp_file.write_text("test content") 

75 

76 stat = temp_file.stat() 

77 cache = ToolCache(tool_name="test") 

78 cache.fingerprints[str(temp_file)] = FileFingerprint( 

79 path=str(temp_file), 

80 mtime=stat.st_mtime, 

81 size=stat.st_size, 

82 ) 

83 changed = cache.get_changed_files([str(temp_file)]) 

84 assert_that(changed).does_not_contain(str(temp_file)) 

85 

86 

87def test_tool_cache_modified_file_returned(tmp_path: Path) -> None: 

88 """File in cache with different mtime returned as changed. 

89 

90 Args: 

91 tmp_path: Pytest fixture for temporary directory. 

92 """ 

93 temp_file = tmp_path / "test.py" 

94 temp_file.write_text("test content") 

95 

96 stat = temp_file.stat() 

97 cache = ToolCache(tool_name="test") 

98 cache.fingerprints[str(temp_file)] = FileFingerprint( 

99 path=str(temp_file), 

100 mtime=stat.st_mtime - 100, 

101 size=stat.st_size, 

102 ) 

103 changed = cache.get_changed_files([str(temp_file)]) 

104 assert_that(changed).contains(str(temp_file)) 

105 

106 

107def test_tool_cache_size_changed_file_returned(tmp_path: Path) -> None: 

108 """File in cache with different size returned as changed. 

109 

110 Args: 

111 tmp_path: Pytest fixture for temporary directory. 

112 """ 

113 temp_file = tmp_path / "test.py" 

114 temp_file.write_text("test content") 

115 

116 stat = temp_file.stat() 

117 cache = ToolCache(tool_name="test") 

118 cache.fingerprints[str(temp_file)] = FileFingerprint( 

119 path=str(temp_file), 

120 mtime=stat.st_mtime, 

121 size=stat.st_size + 100, # Different size 

122 ) 

123 changed = cache.get_changed_files([str(temp_file)]) 

124 assert_that(changed).contains(str(temp_file)) 

125 

126 

127def test_tool_cache_nonexistent_file_skipped() -> None: 

128 """Nonexistent file skipped in get_changed_files.""" 

129 cache = ToolCache(tool_name="test") 

130 changed = cache.get_changed_files(["/nonexistent/file.py"]) 

131 assert_that(changed).is_empty() 

132 

133 

134def test_tool_cache_update_adds_fingerprints(tmp_path: Path) -> None: 

135 """Update adds fingerprints for files. 

136 

137 Args: 

138 tmp_path: Pytest fixture for temporary directory. 

139 """ 

140 temp_file = tmp_path / "test.py" 

141 temp_file.write_text("test content") 

142 

143 cache = ToolCache(tool_name="test") 

144 cache.update([str(temp_file)]) 

145 assert_that(cache.fingerprints).contains_key(str(temp_file)) 

146 

147 

148def test_tool_cache_clear_removes_all_fingerprints() -> None: 

149 """Clear removes all fingerprints.""" 

150 cache = ToolCache(tool_name="test") 

151 cache.fingerprints["file1.py"] = FileFingerprint( 

152 path="file1.py", 

153 mtime=1234567890.0, 

154 size=100, 

155 ) 

156 cache.clear() 

157 assert_that(cache.fingerprints).is_empty() 

158 

159 

160def test_tool_cache_save_and_load_roundtrip(tmp_path: Path) -> None: 

161 """Save and load preserves cache data. 

162 

163 Args: 

164 tmp_path: Pytest fixture for temporary directory. 

165 """ 

166 with patch("lintro.utils.file_cache.CACHE_DIR", tmp_path): 

167 cache = ToolCache(tool_name="test_tool") 

168 cache.fingerprints["/test/file.py"] = FileFingerprint( 

169 path="/test/file.py", 

170 mtime=1234567890.0, 

171 size=1024, 

172 ) 

173 cache.save() 

174 

175 loaded = ToolCache.load("test_tool") 

176 assert_that(loaded.tool_name).is_equal_to("test_tool") 

177 assert_that(loaded.fingerprints).contains_key("/test/file.py") 

178 

179 

180def test_tool_cache_load_returns_empty_for_missing_file(tmp_path: Path) -> None: 

181 """Load returns empty cache for missing cache file. 

182 

183 Args: 

184 tmp_path: Pytest fixture for temporary directory. 

185 """ 

186 with patch("lintro.utils.file_cache.CACHE_DIR", tmp_path): 

187 loaded = ToolCache.load("nonexistent_tool") 

188 assert_that(loaded.fingerprints).is_empty() 

189 

190 

191def test_clear_all_caches_deletes_files(tmp_path: Path) -> None: 

192 """Clear all caches deletes all cache files. 

193 

194 Args: 

195 tmp_path: Pytest fixture for temporary directory. 

196 """ 

197 (tmp_path / "tool1.json").write_text('{"tool_name": "tool1"}') 

198 (tmp_path / "tool2.json").write_text('{"tool_name": "tool2"}') 

199 

200 with patch("lintro.utils.file_cache.CACHE_DIR", tmp_path): 

201 clear_all_caches() 

202 assert_that(list(tmp_path.glob("*.json"))).is_empty() 

203 

204 

205def test_get_cache_stats_returns_file_counts(tmp_path: Path) -> None: 

206 """Get cache stats returns file counts. 

207 

208 Args: 

209 tmp_path: Pytest fixture for temporary directory. 

210 """ 

211 cache_data = { 

212 "tool_name": "test_tool", 

213 "fingerprints": { 

214 "/file1.py": {"path": "/file1.py", "mtime": 1.0, "size": 100}, 

215 "/file2.py": {"path": "/file2.py", "mtime": 2.0, "size": 200}, 

216 }, 

217 } 

218 (tmp_path / "test_tool.json").write_text(json.dumps(cache_data)) 

219 

220 with patch("lintro.utils.file_cache.CACHE_DIR", tmp_path): 

221 stats = get_cache_stats() 

222 assert_that(stats).contains_key("test_tool") 

223 assert_that(stats["test_tool"]).is_equal_to(2) 

224 

225 

226def test_get_cache_stats_returns_empty_for_nonexistent_dir(tmp_path: Path) -> None: 

227 """Get cache stats returns empty dict for nonexistent directory. 

228 

229 Args: 

230 tmp_path: Pytest fixture for temporary directory. 

231 """ 

232 nonexistent_dir = tmp_path / "nonexistent" 

233 

234 with patch("lintro.utils.file_cache.CACHE_DIR", nonexistent_dir): 

235 stats = get_cache_stats() 

236 assert_that(stats).is_empty()