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