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