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