1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle;
21
22 import static com.google.common.truth.Truth.assertWithMessage;
23 import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.Mockito.mockStatic;
26
27 import java.io.BufferedInputStream;
28 import java.io.BufferedReader;
29 import java.io.ByteArrayOutputStream;
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.ObjectOutputStream;
33 import java.net.URI;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.security.MessageDigest;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.HashSet;
39 import java.util.Locale;
40 import java.util.Properties;
41 import java.util.Set;
42 import java.util.UUID;
43
44 import org.junit.jupiter.api.Test;
45 import org.junit.jupiter.api.condition.DisabledOnOs;
46 import org.junit.jupiter.api.condition.OS;
47 import org.junit.jupiter.api.io.TempDir;
48 import org.junit.jupiter.params.ParameterizedTest;
49 import org.junit.jupiter.params.provider.ValueSource;
50 import org.mockito.MockedStatic;
51
52 import com.google.common.io.BaseEncoding;
53 import com.google.common.io.ByteStreams;
54 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
55 import com.puppycrawl.tools.checkstyle.api.Configuration;
56 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
57 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
58
59 public class PropertyCacheFileTest extends AbstractPathTestSupport {
60
61 @TempDir
62 public File temporaryFolder;
63
64 @Override
65 protected String getPackageLocation() {
66 return "com/puppycrawl/tools/checkstyle/propertycachefile";
67 }
68
69 @Test
70 public void testCtor() {
71 try {
72 final Object test = new PropertyCacheFile(null, "");
73 assertWithMessage("exception expected but got " + test).fail();
74 }
75 catch (IllegalArgumentException ex) {
76 assertWithMessage("Invalid exception message")
77 .that(ex.getMessage())
78 .isEqualTo("config can not be null");
79 }
80 final Configuration config = new DefaultConfiguration("myName");
81 try {
82 final Object test = new PropertyCacheFile(config, null);
83 assertWithMessage("exception expected but got " + test).fail();
84 }
85 catch (IllegalArgumentException ex) {
86 assertWithMessage("Invalid exception message")
87 .that(ex.getMessage())
88 .isEqualTo("fileName can not be null");
89 }
90 }
91
92 @Test
93 public void testInCache() {
94 final Configuration config = new DefaultConfiguration("myName");
95 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
96 final File filePath = new File(temporaryFolder, uniqueFileName);
97 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath.toString());
98 cache.put("myFile", 1);
99 assertWithMessage("Should return true when file is in cache")
100 .that(cache.isInCache("myFile", 1))
101 .isTrue();
102 assertWithMessage("Should return false when file is not in cache")
103 .that(cache.isInCache("myFile", 2))
104 .isFalse();
105 assertWithMessage("Should return false when file is not in cache")
106 .that(cache.isInCache("myFile1", 1))
107 .isFalse();
108 }
109
110 @Test
111 public void testResetIfFileDoesNotExist() throws IOException {
112 final Configuration config = new DefaultConfiguration("myName");
113 final PropertyCacheFile cache = new PropertyCacheFile(config, "fileDoesNotExist.txt");
114
115 cache.load();
116
117 assertWithMessage("Config hash key should not be null")
118 .that(cache.get(PropertyCacheFile.CONFIG_HASH_KEY))
119 .isNotNull();
120 }
121
122 @Test
123 public void testPopulateDetails() throws IOException {
124 final Configuration config = new DefaultConfiguration("myName");
125 final PropertyCacheFile cache = new PropertyCacheFile(config,
126 getPath("InputPropertyCacheFile"));
127 cache.load();
128
129 final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
130 assertWithMessage("Config hash key should not be null")
131 .that(hash)
132 .isNotNull();
133 assertWithMessage("Should return null if key is not in cache")
134 .that(cache.get("key"))
135 .isNull();
136
137 cache.load();
138
139 assertWithMessage("Invalid config hash key")
140 .that(cache.get(PropertyCacheFile.CONFIG_HASH_KEY))
141 .isEqualTo(hash);
142 assertWithMessage("Invalid cache value")
143 .that(cache.get("key"))
144 .isEqualTo("value");
145 assertWithMessage("Config hash key should not be null")
146 .that(cache.get(PropertyCacheFile.CONFIG_HASH_KEY))
147 .isNotNull();
148 }
149
150 @Test
151 public void testConfigHashOnReset() throws IOException {
152 final Configuration config = new DefaultConfiguration("myName");
153 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
154 final File filePath = new File(temporaryFolder, uniqueFileName);
155 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath.toString());
156
157 cache.load();
158
159 final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
160 assertWithMessage("Config hash key should not be null")
161 .that(hash)
162 .isNotNull();
163
164 cache.reset();
165
166 assertWithMessage("Invalid config hash key")
167 .that(cache.get(PropertyCacheFile.CONFIG_HASH_KEY))
168 .isEqualTo(hash);
169 }
170
171 @Test
172 public void testConfigHashRemainsOnResetExternalResources() throws IOException {
173 final Configuration config = new DefaultConfiguration("myName");
174 final String uniqueFileName = "file_" + UUID.randomUUID() + ".java";
175 final File filePath = new File(temporaryFolder, uniqueFileName);
176 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath.toString());
177
178
179 cache.load();
180 cache.put("myFile", 1);
181
182 final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
183 assertWithMessage("Config hash key should not be null")
184 .that(hash)
185 .isNotNull();
186
187
188 final Set<String> resources = new HashSet<>();
189 resources.add("dummy");
190 cache.putExternalResources(resources);
191
192 assertWithMessage("Invalid config hash key")
193 .that(cache.get(PropertyCacheFile.CONFIG_HASH_KEY))
194 .isEqualTo(hash);
195 assertWithMessage("Should return false in file is not in cache")
196 .that(cache.isInCache("myFile", 1))
197 .isFalse();
198 }
199
200 @Test
201 public void testCacheRemainsWhenExternalResourceTheSame() throws IOException {
202 final Configuration config = new DefaultConfiguration("myName");
203 final String externalFile = "junit_" + UUID.randomUUID() + ".java";
204 final File externalResourcePath = new File(temporaryFolder, externalFile);
205 externalResourcePath.createNewFile();
206 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
207 final File filePath = new File(temporaryFolder, uniqueFileName);
208 filePath.createNewFile();
209 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath.toString());
210
211
212
213 cache.load();
214
215 final Set<String> resources = new HashSet<>();
216 resources.add(externalResourcePath.toString());
217 cache.putExternalResources(resources);
218
219 cache.persist();
220
221
222
223 cache.load();
224 cache.put("myFile", 1);
225 cache.putExternalResources(resources);
226
227 assertWithMessage("Should return true in file is in cache")
228 .that(cache.isInCache("myFile", 1))
229 .isTrue();
230 }
231
232 @Test
233 public void testExternalResourceIsSavedInCache() throws Exception {
234 final Configuration config = new DefaultConfiguration("myName");
235 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
236 final File filePath = new File(temporaryFolder, uniqueFileName);
237 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath.toString());
238
239 cache.load();
240
241 final Set<String> resources = new HashSet<>();
242 final String pathToResource = getPath("InputPropertyCacheFileExternal.properties");
243 resources.add(pathToResource);
244 cache.putExternalResources(resources);
245
246 final MessageDigest digest = MessageDigest.getInstance("SHA-1");
247 final URI uri = CommonUtil.getUriByFilename(pathToResource);
248 final byte[] input =
249 ByteStreams.toByteArray(new BufferedInputStream(uri.toURL().openStream()));
250 final ByteArrayOutputStream out = new ByteArrayOutputStream();
251 try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
252 oos.writeObject(input);
253 }
254 digest.update(out.toByteArray());
255 final String expected = BaseEncoding.base16().upperCase().encode(digest.digest());
256
257 assertWithMessage("Hashes are not equal")
258 .that(cache.get("module-resource*?:" + pathToResource))
259 .isEqualTo(expected);
260 }
261
262 @Test
263 public void testCacheDirectoryDoesNotExistAndShouldBeCreated() throws IOException {
264 final Configuration config = new DefaultConfiguration("myName");
265 final String filePath = String.format(Locale.ENGLISH, "%s%2$stemp%2$scache.temp",
266 temporaryFolder, File.separator);
267 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
268
269
270 cache.persist();
271
272 assertWithMessage("cache exists in directory")
273 .that(new File(filePath).exists())
274 .isTrue();
275 }
276
277 @Test
278 public void testPathToCacheContainsOnlyFileName() throws IOException {
279 final Configuration config = new DefaultConfiguration("myName");
280 final String fileName = "temp.cache";
281 final Path filePath = Path.of(fileName);
282 final PropertyCacheFile cache = new PropertyCacheFile(config, fileName);
283
284
285 cache.persist();
286
287 assertWithMessage("Cache file does not exist")
288 .that(Files.exists(filePath))
289 .isTrue();
290 Files.delete(filePath);
291 }
292
293 @Test
294 @DisabledOnOs(OS.WINDOWS)
295 public void testPersistWithSymbolicLinkToDirectory() throws IOException {
296 final Path tempDirectory = temporaryFolder.toPath();
297 final Path symbolicLinkDirectory = temporaryFolder.toPath()
298 .resolve("symbolicLink");
299 Files.createSymbolicLink(symbolicLinkDirectory, tempDirectory);
300
301 final Configuration config = new DefaultConfiguration("myName");
302 final String cacheFilePath = symbolicLinkDirectory.resolve("cache.temp").toString();
303 final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath);
304
305 cache.persist();
306
307 final Path expectedFilePath = tempDirectory.resolve("cache.temp");
308 assertWithMessage("Cache file should be created in the actual directory")
309 .that(Files.exists(expectedFilePath))
310 .isTrue();
311 }
312
313 @Test
314 @DisabledOnOs(OS.WINDOWS)
315 public void testSymbolicLinkResolution() throws IOException {
316 final Path tempDirectory = temporaryFolder.toPath();
317 final Path symbolicLinkDirectory = temporaryFolder.toPath()
318 .resolve("symbolicLink");
319 Files.createSymbolicLink(symbolicLinkDirectory, tempDirectory);
320
321 final Configuration config = new DefaultConfiguration("myName");
322 final String cacheFilePath = symbolicLinkDirectory.resolve("cache.temp").toString();
323 final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath);
324
325 cache.persist();
326
327 final Path expectedFilePath = tempDirectory.resolve("cache.temp");
328 assertWithMessage(
329 "Cache file should be created in the actual directory.")
330 .that(Files.exists(expectedFilePath))
331 .isTrue();
332 }
333
334 @Test
335 @DisabledOnOs(OS.WINDOWS)
336 public void testSymbolicLinkToNonDirectory() throws IOException {
337 final String uniqueFileName = "tempFile_" + UUID.randomUUID() + ".java";
338 final File tempFile = new File(temporaryFolder, uniqueFileName);
339 tempFile.createNewFile();
340 final Path symbolicLinkDirectory = temporaryFolder.toPath();
341 final Path symbolicLink = symbolicLinkDirectory.resolve("symbolicLink");
342 Files.createSymbolicLink(symbolicLink, tempFile.toPath());
343
344 final Configuration config = new DefaultConfiguration("myName");
345 final String cacheFilePath = symbolicLink.resolve("cache.temp").toString();
346 final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath);
347
348 final IOException thrown = getExpectedThrowable(IOException.class, cache::persist);
349
350 final String expectedMessage = "Resolved symbolic link " + symbolicLink
351 + " is not a directory.";
352
353 assertWithMessage(
354 "Expected IOException when symbolicLink is not a directory")
355 .that(thrown.getMessage())
356 .contains(expectedMessage);
357 }
358
359 @Test
360 @DisabledOnOs(OS.WINDOWS)
361 public void testMultipleSymbolicLinkResolution() throws IOException {
362 final Path actualDirectory = temporaryFolder.toPath();
363 final Path firstSymbolicLink = temporaryFolder.toPath()
364 .resolve("firstLink");
365 Files.createSymbolicLink(firstSymbolicLink, actualDirectory);
366
367 final Path secondSymbolicLink = temporaryFolder.toPath()
368 .resolve("secondLink");
369 Files.createSymbolicLink(secondSymbolicLink, firstSymbolicLink);
370
371 final Configuration config = new DefaultConfiguration("myName");
372 final String cacheFilePath = secondSymbolicLink.resolve("cache.temp").toString();
373 final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFilePath);
374
375 cache.persist();
376
377 final Path expectedFilePath = actualDirectory.resolve("cache.temp");
378 assertWithMessage("Cache file should be created in the final actual directory")
379 .that(Files.exists(expectedFilePath))
380 .isTrue();
381 }
382
383 @Test
384 public void testChangeInConfig() throws Exception {
385 final DefaultConfiguration config = new DefaultConfiguration("myConfig");
386 config.addProperty("attr", "value");
387 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
388 final File cacheFile = new File(temporaryFolder, uniqueFileName);
389 final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFile.getPath());
390 cache.load();
391
392 final String expectedInitialConfigHash = "D5BB1747FC11B2BB839C80A6C28E3E7684AF9940";
393 final String actualInitialConfigHash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
394 assertWithMessage("Invalid config hash")
395 .that(actualInitialConfigHash)
396 .isEqualTo(expectedInitialConfigHash);
397
398 cache.persist();
399
400 final Properties details = new Properties();
401 try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
402 details.load(reader);
403 }
404 assertWithMessage("Invalid details size")
405 .that(details)
406 .hasSize(1);
407
408
409 config.addProperty("newAttr", "newValue");
410
411 final PropertyCacheFile cacheAfterChangeInConfig =
412 new PropertyCacheFile(config, cacheFile.getPath());
413 cacheAfterChangeInConfig.load();
414
415 final String expectedConfigHashAfterChange = "714876AE38C069EC52BF86889F061B3776E526D3";
416 final String actualConfigHashAfterChange =
417 cacheAfterChangeInConfig.get(PropertyCacheFile.CONFIG_HASH_KEY);
418 assertWithMessage("Invalid config hash")
419 .that(actualConfigHashAfterChange)
420 .isEqualTo(expectedConfigHashAfterChange);
421
422 cacheAfterChangeInConfig.persist();
423
424 final Properties detailsAfterChangeInConfig = new Properties();
425 try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
426 detailsAfterChangeInConfig.load(reader);
427 }
428 assertWithMessage("Invalid cache size")
429 .that(detailsAfterChangeInConfig)
430 .hasSize(1);
431 }
432
433 @Test
434 public void testNonExistentResource() throws IOException {
435 final Configuration config = new DefaultConfiguration("myName");
436 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
437 final File filePath = new File(temporaryFolder, uniqueFileName);
438 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath.toString());
439
440
441 cache.load();
442 final String myFile = "myFile";
443 cache.put(myFile, 1);
444
445 final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
446 assertWithMessage("Config hash key should not be null")
447 .that(hash)
448 .isNotNull();
449
450
451 final Set<String> resources = new HashSet<>();
452 final String resource = getPath("InputPropertyCacheFile.header");
453 resources.add(resource);
454 cache.putExternalResources(resources);
455
456 assertWithMessage("Should return false in file is not in cache")
457 .that(cache.isInCache(myFile, 1))
458 .isFalse();
459
460 assertWithMessage("Should return false in file is not in cache")
461 .that(cache.isInCache(resource, 1))
462 .isFalse();
463 }
464
465 @Test
466 public void testExceptionNoSuchAlgorithmException() {
467 final Configuration config = new DefaultConfiguration("myName");
468 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
469 final File filePath = new File(temporaryFolder, uniqueFileName);
470 final PropertyCacheFile cache = new PropertyCacheFile(config, filePath.toString());
471 cache.put("myFile", 1);
472
473 try (MockedStatic<MessageDigest> messageDigest = mockStatic(MessageDigest.class)) {
474 messageDigest.when(() -> MessageDigest.getInstance("SHA-1"))
475 .thenThrow(NoSuchAlgorithmException.class);
476
477 final ReflectiveOperationException ex =
478 getExpectedThrowable(ReflectiveOperationException.class, () -> {
479 TestUtil.invokeStaticMethod(PropertyCacheFile.class,
480 "getHashCodeBasedOnObjectContent", config);
481 });
482 assertWithMessage("Invalid exception cause")
483 .that(ex)
484 .hasCauseThat()
485 .hasCauseThat()
486 .isInstanceOf(NoSuchAlgorithmException.class);
487 assertWithMessage("Invalid exception message")
488 .that(ex)
489 .hasCauseThat()
490 .hasMessageThat()
491 .isEqualTo("Unable to calculate hashcode.");
492 }
493 }
494
495
496
497
498
499
500
501
502 @ParameterizedTest
503 @ValueSource(strings = {"Same;Same", "First;Second"})
504 public void testPutNonExistentExternalResource(String rawMessages) throws Exception {
505 final String uniqueFileName = "junit_" + UUID.randomUUID() + ".java";
506 final File cacheFile = new File(temporaryFolder, uniqueFileName);
507 final String[] messages = rawMessages.split(";");
508
509
510 try (MockedStatic<CommonUtil> commonUtil = mockStatic(CommonUtil.class)) {
511 final int numberOfRuns = messages.length;
512 final String[] configHashes = new String[numberOfRuns];
513 final String[] externalResourceHashes = new String[numberOfRuns];
514 for (int i = 0; i < numberOfRuns; i++) {
515 commonUtil.when(() -> CommonUtil.getUriByFilename(any(String.class)))
516 .thenThrow(new CheckstyleException(messages[i]));
517 final Configuration config = new DefaultConfiguration("myConfig");
518 final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFile.getPath());
519 cache.load();
520
521 configHashes[i] = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
522 assertWithMessage("Config hash key should not be null")
523 .that(configHashes[i])
524 .isNotNull();
525
526 final Set<String> nonExistentExternalResources = new HashSet<>();
527 final String externalResourceFileName = "non_existent_file.xml";
528 nonExistentExternalResources.add(externalResourceFileName);
529 cache.putExternalResources(nonExistentExternalResources);
530
531 externalResourceHashes[i] = cache.get(PropertyCacheFile.EXTERNAL_RESOURCE_KEY_PREFIX
532 + externalResourceFileName);
533 assertWithMessage("External resource hashes should not be null")
534 .that(externalResourceHashes[i])
535 .isNotNull();
536
537 cache.persist();
538
539 final Properties cacheDetails = new Properties();
540 try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath())) {
541 cacheDetails.load(reader);
542 }
543
544 assertWithMessage("Unexpected number of objects in cache")
545 .that(cacheDetails)
546 .hasSize(2);
547 }
548
549 assertWithMessage("Invalid config hash")
550 .that(configHashes[0])
551 .isEqualTo(configHashes[1]);
552 final boolean sameException = messages[0].equals(messages[1]);
553 assertWithMessage("Invalid external resource hashes")
554 .that(externalResourceHashes[0].equals(externalResourceHashes[1]))
555 .isEqualTo(sameException);
556 }
557 }
558
559 }