View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotEquals;
25  import static org.junit.Assert.assertNotNull;
26  import static org.junit.Assert.assertNull;
27  import static org.junit.Assert.assertTrue;
28  import static org.junit.Assert.fail;
29  import static org.mockito.Matchers.any;
30  import static org.mockito.Mockito.times;
31  import static org.powermock.api.mockito.PowerMockito.doNothing;
32  import static org.powermock.api.mockito.PowerMockito.mockStatic;
33  import static org.powermock.api.mockito.PowerMockito.verifyStatic;
34  import static org.powermock.api.mockito.PowerMockito.when;
35  
36  import java.io.BufferedInputStream;
37  import java.io.ByteArrayOutputStream;
38  import java.io.File;
39  import java.io.FileInputStream;
40  import java.io.FileOutputStream;
41  import java.io.IOException;
42  import java.io.ObjectOutputStream;
43  import java.io.Serializable;
44  import java.lang.reflect.InvocationTargetException;
45  import java.lang.reflect.Method;
46  import java.net.URI;
47  import java.nio.file.Files;
48  import java.nio.file.Paths;
49  import java.security.MessageDigest;
50  import java.security.NoSuchAlgorithmException;
51  import java.util.HashSet;
52  import java.util.Locale;
53  import java.util.Properties;
54  import java.util.Set;
55  
56  import org.junit.Rule;
57  import org.junit.Test;
58  import org.junit.rules.TemporaryFolder;
59  import org.junit.runner.RunWith;
60  import org.mockito.Matchers;
61  import org.powermock.core.classloader.annotations.PrepareForTest;
62  import org.powermock.modules.junit4.PowerMockRunner;
63  
64  import com.google.common.io.BaseEncoding;
65  import com.google.common.io.ByteStreams;
66  import com.google.common.io.Closeables;
67  import com.google.common.io.Flushables;
68  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
69  import com.puppycrawl.tools.checkstyle.api.Configuration;
70  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
71  
72  @RunWith(PowerMockRunner.class)
73  @PrepareForTest({ PropertyCacheFile.class, ByteStreams.class,
74          CommonUtils.class, Closeables.class, Flushables.class})
75  public class PropertyCacheFileTest extends AbstractPathTestSupport {
76  
77      @Rule
78      public final TemporaryFolder temporaryFolder = new TemporaryFolder();
79  
80      @Override
81      protected String getPackageLocation() {
82          return "com/puppycrawl/tools/checkstyle/propertycachefile";
83      }
84  
85      @Test
86      public void testCtor() {
87          try {
88              final Object test = new PropertyCacheFile(null, "");
89              fail("exception expected but got " + test);
90          }
91          catch (IllegalArgumentException ex) {
92              assertEquals("Invalid exception message",
93                      "config can not be null", ex.getMessage());
94          }
95          try {
96              final Configuration config = new DefaultConfiguration("myName");
97              final Object test = new PropertyCacheFile(config, null);
98              fail("exception expected but got " + test);
99          }
100         catch (IllegalArgumentException ex) {
101             assertEquals("Invalid exception message",
102                     "fileName can not be null", ex.getMessage());
103         }
104     }
105 
106     @Test
107     public void testInCache() throws IOException {
108         final Configuration config = new DefaultConfiguration("myName");
109         final String filePath = temporaryFolder.newFile().getPath();
110         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
111         cache.put("myFile", 1);
112         assertTrue("Should return true when file is in cache",
113                 cache.isInCache("myFile", 1));
114         assertFalse("Should return false when file is not in cache",
115                 cache.isInCache("myFile", 2));
116         assertFalse("Should return false when file is not in cache",
117                 cache.isInCache("myFile1", 1));
118     }
119 
120     @Test
121     public void testResetIfFileDoesNotExist() throws IOException {
122         final Configuration config = new DefaultConfiguration("myName");
123         final PropertyCacheFile cache = new PropertyCacheFile(config, "fileDoesNotExist.txt");
124 
125         cache.load();
126 
127         assertNotNull("Config hash key should not be null",
128                 cache.get(PropertyCacheFile.CONFIG_HASH_KEY));
129     }
130 
131     @Test
132     public void testCloseAndFlushOutputStreamAfterCreatingHashCode() throws IOException {
133         mockStatic(Closeables.class);
134         doNothing().when(Closeables.class);
135         Closeables.close(any(ObjectOutputStream.class), Matchers.eq(false));
136         mockStatic(Flushables.class);
137         doNothing().when(Flushables.class);
138         Flushables.flush(any(ObjectOutputStream.class), Matchers.eq(false));
139 
140         final Configuration config = new DefaultConfiguration("myName");
141         final PropertyCacheFile cache = new PropertyCacheFile(config, "fileDoesNotExist.txt");
142         cache.load();
143 
144         verifyStatic(times(1));
145 
146         Closeables.close(any(ObjectOutputStream.class), Matchers.eq(false));
147         verifyStatic(times(1));
148         Flushables.flush(any(ObjectOutputStream.class), Matchers.eq(false));
149     }
150 
151     @Test
152     public void testPopulateDetails() throws IOException {
153         mockStatic(Closeables.class);
154         doNothing().when(Closeables.class);
155         Closeables.closeQuietly(any(FileInputStream.class));
156 
157         final Configuration config = new DefaultConfiguration("myName");
158         final PropertyCacheFile cache = new PropertyCacheFile(config,
159                 getPath("InputPropertyCacheFile"));
160         cache.load();
161 
162         final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
163         assertNotNull("Config hash key should not be null", hash);
164         assertNull("Should return null if key is not in cache", cache.get("key"));
165 
166         cache.load();
167 
168         assertEquals("Invalid config hash key", hash, cache.get(PropertyCacheFile.CONFIG_HASH_KEY));
169         assertEquals("Invalid cache value", "value", cache.get("key"));
170         assertNotNull("Config hash key should not be null",
171                 cache.get(PropertyCacheFile.CONFIG_HASH_KEY));
172 
173         verifyStatic(times(2));
174         Closeables.closeQuietly(any(FileInputStream.class));
175     }
176 
177     @Test
178     public void testConfigHashOnReset() throws IOException {
179         final Configuration config = new DefaultConfiguration("myName");
180         final String filePath = temporaryFolder.newFile().getPath();
181         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
182 
183         cache.load();
184 
185         final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
186         assertNotNull("Config hash key should not be null", hash);
187 
188         cache.reset();
189 
190         assertEquals("Invalid config hash key",
191                 hash, cache.get(PropertyCacheFile.CONFIG_HASH_KEY));
192     }
193 
194     @Test
195     public void testConfigHashRemainsOnResetExternalResources() throws IOException {
196         final Configuration config = new DefaultConfiguration("myName");
197         final String filePath = temporaryFolder.newFile().getPath();
198         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
199 
200         // create cache with one file
201         cache.load();
202         cache.put("myFile", 1);
203 
204         final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
205         assertNotNull("Config hash key should not be null", hash);
206 
207         // apply new external resource to clear cache
208         final Set<String> resources = new HashSet<>();
209         resources.add("dummy");
210         cache.putExternalResources(resources);
211 
212         assertEquals("Invalid config hash key",
213                 hash, cache.get(PropertyCacheFile.CONFIG_HASH_KEY));
214         assertFalse("Should return false in file is not in cache",
215                 cache.isInCache("myFile", 1));
216     }
217 
218     @Test
219     public void testExternalResourceIsSavedInCache() throws Exception {
220         final Configuration config = new DefaultConfiguration("myName");
221         final String filePath = temporaryFolder.newFile().getPath();
222         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
223 
224         cache.load();
225 
226         final Set<String> resources = new HashSet<>();
227         final String pathToResource = getPath("InputPropertyCacheFileExternal.properties");
228         resources.add(pathToResource);
229         cache.putExternalResources(resources);
230 
231         final MessageDigest digest = MessageDigest.getInstance("SHA-1");
232         final URI uri = CommonUtils.getUriByFilename(pathToResource);
233         final byte[] input =
234                 ByteStreams.toByteArray(new BufferedInputStream(uri.toURL().openStream()));
235         final ByteArrayOutputStream out = new ByteArrayOutputStream();
236         try (ObjectOutputStream oos = new ObjectOutputStream(out)) {
237             oos.writeObject(input);
238         }
239         digest.update(out.toByteArray());
240         final String expected = BaseEncoding.base16().upperCase().encode(digest.digest());
241 
242         assertEquals("Hashes are not equal", expected,
243                 cache.get("module-resource*?:" + pathToResource));
244     }
245 
246     /**
247      * This SuppressWarning("unchecked") required to suppress
248      * "Unchecked generics array creation for varargs parameter" during mock.
249      * @throws IOException when smth wrong with file creation or cache.load
250      */
251     @SuppressWarnings("unchecked")
252     @Test
253     public void testNonExistentResource() throws IOException {
254         final Configuration config = new DefaultConfiguration("myName");
255         final String filePath = temporaryFolder.newFile().getPath();
256         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
257 
258         // create cache with one file
259         cache.load();
260         final String myFile = "myFile";
261         cache.put(myFile, 1);
262 
263         final String hash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
264         assertNotNull("Config hash key should not be null", hash);
265 
266         mockStatic(ByteStreams.class);
267 
268         when(ByteStreams.toByteArray(any(BufferedInputStream.class)))
269                 .thenThrow(IOException.class);
270 
271         // apply new external resource to clear cache
272         final Set<String> resources = new HashSet<>();
273         final String resource = getPath("InputPropertyCacheFile.header");
274         resources.add(resource);
275         cache.putExternalResources(resources);
276 
277         assertFalse("Should return false in file is not in cache",
278                 cache.isInCache(myFile, 1));
279         assertFalse("Should return false in file is not in cache",
280                 cache.isInCache(resource, 1));
281     }
282 
283     @Test
284     public void testFlushAndCloseCacheFileOutputStream() throws IOException {
285         mockStatic(Closeables.class);
286         doNothing().when(Closeables.class);
287         Closeables.close(any(FileOutputStream.class), Matchers.eq(false));
288         mockStatic(Flushables.class);
289         doNothing().when(Flushables.class);
290         Flushables.flush(any(FileOutputStream.class), Matchers.eq(false));
291 
292         final Configuration config = new DefaultConfiguration("myName");
293         final PropertyCacheFile cache = new PropertyCacheFile(config,
294             temporaryFolder.newFile().getPath());
295 
296         cache.put("CheckedFileName.java", System.currentTimeMillis());
297         cache.persist();
298 
299         verifyStatic(times(1));
300         Closeables.close(any(FileOutputStream.class), Matchers.eq(false));
301         verifyStatic(times(1));
302         Flushables.flush(any(FileOutputStream.class), Matchers.eq(false));
303     }
304 
305     @Test
306     public void testCacheDirectoryDoesNotExistAndShouldBeCreated() throws IOException {
307         final Configuration config = new DefaultConfiguration("myName");
308         final String filePath = String.format(Locale.getDefault(), "%s%2$stemp%2$scache.temp",
309             temporaryFolder.getRoot(), File.separator);
310         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
311 
312         // no exception expected, cache directory should be created
313         cache.persist();
314 
315         assertTrue("cache exists in directory", new File(filePath).exists());
316     }
317 
318     @Test
319     public void testPathToCacheContainsOnlyFileName() throws IOException {
320         final Configuration config = new DefaultConfiguration("myName");
321         final String filePath = "temp.cache";
322         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
323 
324         // no exception expected
325         cache.persist();
326         assertTrue("Cache file does not exist", Files.exists(Paths.get(filePath)));
327         Files.delete(Paths.get(filePath));
328     }
329 
330     @Test
331     @SuppressWarnings("unchecked")
332     public void testExceptionNoSuchAlgorithmException() throws Exception {
333 
334         final Configuration config = new DefaultConfiguration("myName");
335         final String filePath = temporaryFolder.newFile().getPath();
336         final PropertyCacheFile cache = new PropertyCacheFile(config, filePath);
337         cache.put("myFile", 1);
338         mockStatic(MessageDigest.class);
339 
340         when(MessageDigest.getInstance("SHA-1"))
341                 .thenThrow(NoSuchAlgorithmException.class);
342 
343         final Class<?>[] param = new Class<?>[1];
344         param[0] = Serializable.class;
345         final Method method =
346             PropertyCacheFile.class.getDeclaredMethod("getHashCodeBasedOnObjectContent", param);
347         method.setAccessible(true);
348         try {
349             method.invoke(cache, config);
350             fail("InvocationTargetException is expected");
351         }
352         catch (InvocationTargetException ex) {
353             assertTrue("Invalid exception cause",
354                     ex.getCause().getCause() instanceof NoSuchAlgorithmException);
355             assertEquals("Invalid exception message",
356                     "Unable to calculate hashcode.", ex.getCause().getMessage());
357         }
358     }
359 
360     @Test
361     public void testPutNonExistentExternalResourceSameExceptionBetweenRuns() throws Exception {
362         final File cacheFile = temporaryFolder.newFile();
363 
364         // We mock getUriByFilename method of CommonUtils to guarantee that it will
365         // throw CheckstyleException with the specific content.
366         mockStatic(CommonUtils.class);
367         final CheckstyleException mockException =
368             new CheckstyleException("Cannot get URL for cache file " + cacheFile.getAbsolutePath());
369         when(CommonUtils.getUriByFilename(any(String.class)))
370             .thenThrow(mockException);
371 
372         // We invoke 'putExternalResources' twice to invalidate cache
373         // and have two identical exceptions which the equal content
374         final int numberOfRuns = 2;
375         final String[] configHashes = new String[numberOfRuns];
376         final String[] externalResourceHashes = new String[numberOfRuns];
377         for (int i = 0; i < numberOfRuns; i++) {
378             final Configuration config = new DefaultConfiguration("myConfig");
379             final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFile.getPath());
380             cache.load();
381 
382             configHashes[i] = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
383             assertNotNull("Config hash key should not be null", configHashes[i]);
384 
385             final Set<String> nonExistentExternalResources = new HashSet<>();
386             final String externalResourceFileName = "non_existent_file.xml";
387             nonExistentExternalResources.add(externalResourceFileName);
388             cache.putExternalResources(nonExistentExternalResources);
389 
390             externalResourceHashes[i] = cache.get(PropertyCacheFile.EXTERNAL_RESOURCE_KEY_PREFIX
391                     + externalResourceFileName);
392             assertNotNull("External resource hashes should not be null",
393                     externalResourceHashes[i]);
394 
395             cache.persist();
396 
397             final Properties cacheDetails = new Properties();
398             cacheDetails.load(Files.newBufferedReader(cacheFile.toPath()));
399 
400             final int expectedNumberOfObjectsInCacheFile = 2;
401             assertEquals("Unexpected number of objects in cache",
402                     expectedNumberOfObjectsInCacheFile, cacheDetails.size());
403         }
404 
405         assertEquals("Invalid config hash", configHashes[0], configHashes[1]);
406         assertEquals("Invalid external resource hashes",
407                 externalResourceHashes[0], externalResourceHashes[1]);
408     }
409 
410     /**
411      * It is OK to have long test method name here as it describes the test purpose.
412      * @noinspection InstanceMethodNamingConvention
413      */
414     @Test
415     public void testPutNonExistentExternalResourceDifferentExceptionsBetweenRuns()
416             throws Exception {
417 
418         final File cacheFile = temporaryFolder.newFile();
419 
420         // We invoke 'putExternalResources' twice to invalidate cache
421         // and have two different exceptions with different content.
422         final int numberOfRuns = 2;
423         final String[] configHashes = new String[numberOfRuns];
424         final String[] externalResourceHashes = new String[numberOfRuns];
425         for (int i = 0; i < numberOfRuns; i++) {
426             final Configuration config = new DefaultConfiguration("myConfig");
427             final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFile.getPath());
428 
429             // We mock getUriByFilename method of CommonUtils to guarantee that it will
430             // throw CheckstyleException with the specific content.
431             mockStatic(CommonUtils.class);
432             final CheckstyleException mockException = new CheckstyleException("Exception #" + i);
433             when(CommonUtils.getUriByFilename(any(String.class)))
434                 .thenThrow(mockException);
435 
436             cache.load();
437 
438             configHashes[i] = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
439             assertNotNull("Config hash key should not be null", configHashes[i]);
440 
441             final Set<String> nonExistentExternalResources = new HashSet<>();
442             final String externalResourceFileName = "non_existent_file.xml";
443             nonExistentExternalResources.add(externalResourceFileName);
444             cache.putExternalResources(nonExistentExternalResources);
445 
446             externalResourceHashes[i] = cache.get(PropertyCacheFile.EXTERNAL_RESOURCE_KEY_PREFIX
447                     + externalResourceFileName);
448             assertNotNull("External resource hashes should not be null",
449                     externalResourceHashes[i]);
450 
451             cache.persist();
452 
453             final Properties cacheDetails = new Properties();
454             cacheDetails.load(Files.newBufferedReader(cacheFile.toPath()));
455 
456             final int expectedNumberOfObjectsInCacheFile = 2;
457             assertEquals("Unexpected number of objects in cache",
458                     expectedNumberOfObjectsInCacheFile, cacheDetails.size());
459         }
460 
461         assertEquals("Invalid config hash", configHashes[0], configHashes[1]);
462         assertNotEquals("Invalid external resource hashes",
463                 externalResourceHashes[0], externalResourceHashes[1]);
464     }
465 
466     @Test
467     public void testChangeInConfig() throws Exception {
468         final DefaultConfiguration config = new DefaultConfiguration("myConfig");
469         config.addAttribute("attr", "value");
470 
471         final File cacheFile = temporaryFolder.newFile();
472         final PropertyCacheFile cache = new PropertyCacheFile(config, cacheFile.getPath());
473         cache.load();
474 
475         final String expectedInitialConfigHash = "91753B970AFDF9F5F3DFA0D258064841949D3C6B";
476         final String actualInitialConfigHash = cache.get(PropertyCacheFile.CONFIG_HASH_KEY);
477         assertEquals("Invalid config hash", expectedInitialConfigHash, actualInitialConfigHash);
478 
479         cache.persist();
480 
481         final Properties details = new Properties();
482         details.load(Files.newBufferedReader(cacheFile.toPath()));
483         assertEquals("Invalid details size", 1, details.size());
484 
485         // change in config
486         config.addAttribute("newAttr", "newValue");
487 
488         final PropertyCacheFile cacheAfterChangeInConfig =
489             new PropertyCacheFile(config, cacheFile.getPath());
490         cacheAfterChangeInConfig.load();
491 
492         final String expectedConfigHashAfterChange = "4CF5EC78955B81D76153ACC2CA6D60CB77FDCB2A";
493         final String actualConfigHashAfterChange =
494             cacheAfterChangeInConfig.get(PropertyCacheFile.CONFIG_HASH_KEY);
495         assertEquals("Invalid config hash",
496                 expectedConfigHashAfterChange, actualConfigHashAfterChange);
497 
498         cacheAfterChangeInConfig.persist();
499 
500         final Properties detailsAfterChangeInConfig = new Properties();
501         detailsAfterChangeInConfig.load(Files.newBufferedReader(cacheFile.toPath()));
502         assertEquals("Invalid cache size", 1, detailsAfterChangeInConfig.size());
503     }
504 }