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.checks;
21  
22  import static com.puppycrawl.tools.checkstyle.checks.TranslationCheck.MSG_KEY;
23  import static com.puppycrawl.tools.checkstyle.checks.TranslationCheck.MSG_KEY_MISSING_TRANSLATION_FILE;
24  import static org.hamcrest.CoreMatchers.containsString;
25  import static org.hamcrest.CoreMatchers.endsWith;
26  import static org.junit.Assert.assertEquals;
27  import static org.junit.Assert.assertThat;
28  import static org.junit.Assert.assertTrue;
29  import static org.junit.Assert.fail;
30  import static org.mockito.Matchers.any;
31  import static org.mockito.Mockito.mock;
32  import static org.mockito.Mockito.times;
33  import static org.powermock.api.mockito.PowerMockito.doNothing;
34  import static org.powermock.api.mockito.PowerMockito.mockStatic;
35  import static org.powermock.api.mockito.PowerMockito.verifyStatic;
36  
37  import java.io.ByteArrayOutputStream;
38  import java.io.File;
39  import java.io.FileInputStream;
40  import java.io.IOException;
41  import java.io.InputStream;
42  import java.lang.reflect.Field;
43  import java.lang.reflect.Method;
44  import java.nio.charset.StandardCharsets;
45  import java.util.Collection;
46  import java.util.Collections;
47  import java.util.Set;
48  import java.util.SortedSet;
49  
50  import org.junit.Test;
51  import org.junit.runner.RunWith;
52  import org.mockito.ArgumentCaptor;
53  import org.mockito.Captor;
54  import org.mockito.Mockito;
55  import org.powermock.core.classloader.annotations.PrepareForTest;
56  import org.powermock.modules.junit4.PowerMockRunner;
57  
58  import com.google.common.collect.ImmutableMap;
59  import com.google.common.io.Closeables;
60  import com.puppycrawl.tools.checkstyle.AbstractXmlTestSupport;
61  import com.puppycrawl.tools.checkstyle.Checker;
62  import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
63  import com.puppycrawl.tools.checkstyle.XMLLogger;
64  import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
65  import com.puppycrawl.tools.checkstyle.api.Configuration;
66  import com.puppycrawl.tools.checkstyle.api.FileText;
67  import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
68  import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
69  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
70  import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter;
71  import com.puppycrawl.tools.checkstyle.internal.utils.XmlUtil;
72  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
73  
74  @RunWith(PowerMockRunner.class)
75  @PrepareForTest(Closeables.class)
76  public class TranslationCheckTest extends AbstractXmlTestSupport {
77      @Captor
78      private ArgumentCaptor<SortedSet<LocalizedMessage>> captor;
79  
80      @Override
81      protected String getPackageLocation() {
82          return "com/puppycrawl/tools/checkstyle/checks/translation";
83      }
84  
85      @Test
86      public void testTranslation() throws Exception {
87          final Configuration checkConfig = createModuleConfig(TranslationCheck.class);
88          final String[] expected = {
89              "0: " + getCheckMessage(MSG_KEY, "only.english"),
90          };
91          final File[] propertyFiles = {
92              new File(getPath("messages_test_de.properties")),
93              new File(getPath("messages_test.properties")),
94          };
95          verify(
96              createChecker(checkConfig),
97              propertyFiles,
98              getPath("messages_test_de.properties"),
99              expected);
100     }
101 
102     /**
103      * Even when we pass several files to AbstractModuleTestSupport#verify,
104      * the check processes it during one run, so we cannot reproduce situation
105      * when TranslationCheck#beginProcessing called several times during single run.
106      * So, we have to use reflection to check this particular case.
107      *
108      * @throws Exception when code tested throws exception
109      */
110     @Test
111     @SuppressWarnings("unchecked")
112     public void testStateIsCleared() throws Exception {
113         final File fileToProcess = new File(
114                 getPath("InputTranslationCheckFireErrors_de.properties")
115         );
116         final String charset = StandardCharsets.UTF_8.name();
117         final TranslationCheck check = new TranslationCheck();
118         check.beginProcessing(charset);
119         check.processFiltered(fileToProcess, new FileText(fileToProcess, charset));
120         check.beginProcessing(charset);
121         final Field field = check.getClass().getDeclaredField("filesToProcess");
122         field.setAccessible(true);
123 
124         assertTrue("Stateful field is not cleared on beginProcessing",
125             ((Collection<File>) field.get(check)).isEmpty());
126     }
127 
128     @Test
129     public void testFileExtension() throws Exception {
130         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
131         checkConfig.addAttribute("baseName", "^InputTranslation.*$");
132         final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
133         final File[] propertyFiles = {
134             new File(getPath("InputTranslation_de.txt")),
135         };
136         verify(createChecker(checkConfig),
137             propertyFiles,
138             getPath("InputTranslation_de.txt"),
139             expected);
140     }
141 
142     @Test
143     public void testLogOutput() throws Exception {
144         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
145         checkConfig.addAttribute("requiredTranslations", "ja,de");
146         checkConfig.addAttribute("baseName", "^InputTranslation.*$");
147         final Checker checker = createChecker(checkConfig);
148         checker.setBasedir(getPath(""));
149         final ByteArrayOutputStream out = new ByteArrayOutputStream();
150         final XMLLogger logger = new XMLLogger(out, AutomaticBean.OutputStreamOptions.NONE);
151         checker.addListener(logger);
152 
153         final String defaultProps = getPath("InputTranslationCheckFireErrors.properties");
154         final String translationProps = getPath("InputTranslationCheckFireErrors_de.properties");
155 
156         final File[] propertyFiles = {
157             new File(defaultProps),
158             new File(translationProps),
159         };
160 
161         final String line = "0: ";
162         final String firstErrorMessage = getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
163                 "InputTranslationCheckFireErrors_ja.properties");
164         final String secondErrorMessage = getCheckMessage(MSG_KEY, "anotherKey");
165 
166         verify(checker, propertyFiles, ImmutableMap.of(
167             ":0", Collections.singletonList(" " + firstErrorMessage),
168             "InputTranslationCheckFireErrors_de.properties",
169                 Collections.singletonList(line + secondErrorMessage)));
170 
171         verifyXml(getPath("ExpectedTranslationLog.xml"), out, (expected, actual) -> {
172             // order is not always maintained here for an unknown reason.
173             // File names can appear in different orders depending on the OS and VM.
174             // This ensures we pick up the correct file based on its name and the
175             // number of children it has.
176             return !"file".equals(expected.getNodeName())
177                     || expected.getAttributes().getNamedItem("name").getNodeValue()
178                             .equals(actual.getAttributes().getNamedItem("name").getNodeValue())
179                     && XmlUtil.getChildrenElements(expected).size() == XmlUtil
180                             .getChildrenElements(actual).size();
181         }, firstErrorMessage, secondErrorMessage);
182     }
183 
184     @Test
185     public void testOnePropertyFileSet() throws Exception {
186         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
187         final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
188         final File[] propertyFiles = {
189             new File(getPath("app-dev.properties")),
190         };
191         verify(
192             createChecker(checkConfig),
193             propertyFiles,
194             getPath("app-dev.properties"),
195             expected);
196     }
197 
198     @Test
199     @SuppressWarnings("unchecked")
200     public void testLogIoExceptionFileNotFound() throws Exception {
201         //I can't put wrong file here. Checkstyle fails before check started.
202         //I saw some usage of file or handling of wrong file in Checker, or somewhere
203         //in checks running part. So I had to do it with reflection to improve coverage.
204         final TranslationCheck check = new TranslationCheck();
205         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
206         check.configure(checkConfig);
207         final Checker checker = createChecker(checkConfig);
208         final SeverityLevelCounter counter = new SeverityLevelCounter(SeverityLevel.ERROR);
209         checker.addListener(counter);
210         check.setMessageDispatcher(checker);
211 
212         final Method loadKeys =
213             check.getClass().getDeclaredMethod("getTranslationKeys", File.class);
214         loadKeys.setAccessible(true);
215         final Set<String> keys = (Set<String>) loadKeys.invoke(check, new File(""));
216         assertTrue("Translation keys should be empty when File is not found", keys.isEmpty());
217         assertEquals("Invalid error count", 1, counter.getCount());
218     }
219 
220     @Test
221     public void testLogIoException() throws Exception {
222         //I can't put wrong file here. Checkstyle fails before check started.
223         //I saw some usage of file or handling of wrong file in Checker, or somewhere
224         //in checks running part. So I had to do it with reflection to improve coverage.
225         final TranslationCheck check = new TranslationCheck();
226         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
227         final MessageDispatcher dispatcher = mock(MessageDispatcher.class);
228         check.configure(checkConfig);
229         check.setMessageDispatcher(dispatcher);
230 
231         final Method logIoException = check.getClass().getDeclaredMethod("logIoException",
232                 IOException.class,
233                 File.class);
234         logIoException.setAccessible(true);
235         final File file = new File("");
236         logIoException.invoke(check, new IOException("test exception"), file);
237 
238         Mockito.verify(dispatcher, times(1)).fireErrors(any(String.class), captor.capture());
239         final String actual = captor.getValue().first().getMessage();
240         assertThat("Invalid message: " + actual, actual, endsWith("test exception"));
241     }
242 
243     @Test
244     public void testDefaultTranslationFileIsMissing() throws Exception {
245         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
246         checkConfig.addAttribute("requiredTranslations", "ja,,, de, ja");
247 
248         final File[] propertyFiles = {
249             new File(getPath("messages_translation_de.properties")),
250             new File(getPath("messages_translation_ja.properties")),
251         };
252 
253         final String[] expected = {
254             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
255                     "messages_translation.properties"),
256         };
257         verify(
258             createChecker(checkConfig),
259             propertyFiles,
260             getPath(""),
261             expected);
262     }
263 
264     @Test
265     public void testTranslationFilesAreMissing() throws Exception {
266         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
267         checkConfig.addAttribute("requiredTranslations", "ja, de");
268 
269         final File[] propertyFiles = {
270             new File(getPath("messages_translation.properties")),
271             new File(getPath("messages_translation_ja.properties")),
272         };
273 
274         final String[] expected = {
275             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
276                     "messages_translation_de.properties"),
277         };
278         verify(
279             createChecker(checkConfig),
280             propertyFiles,
281             getPath(""),
282             expected);
283     }
284 
285     @Test
286     public void testBaseNameWithSeparatorDefaultTranslationIsMissing() throws Exception {
287         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
288         checkConfig.addAttribute("requiredTranslations", "fr");
289 
290         final File[] propertyFiles = {
291             new File(getPath("messages-translation_fr.properties")),
292         };
293 
294         final String[] expected = {
295             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
296                     "messages-translation.properties"),
297         };
298         verify(
299             createChecker(checkConfig),
300             propertyFiles,
301             getPath(""),
302             expected);
303     }
304 
305     @Test
306     public void testBaseNameWithSeparatorTranslationsAreMissing() throws Exception {
307         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
308         checkConfig.addAttribute("requiredTranslations", "fr, tr");
309 
310         final File[] propertyFiles = {
311             new File(getPath("messages-translation.properties")),
312             new File(getPath("messages-translation_fr.properties")),
313         };
314 
315         final String[] expected = {
316             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
317                     "messages-translation_tr.properties"),
318         };
319         verify(
320             createChecker(checkConfig),
321             propertyFiles,
322             getPath(""),
323             expected);
324     }
325 
326     @Test
327     public void testIsNotMessagesBundle() throws Exception {
328         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
329         checkConfig.addAttribute("requiredTranslations", "de");
330 
331         final File[] propertyFiles = {
332             new File(getPath("app-dev.properties")),
333             new File(getPath("app-stage.properties")),
334         };
335 
336         final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
337         verify(
338             createChecker(checkConfig),
339             propertyFiles,
340             getPath("app-dev.properties"),
341             expected);
342     }
343 
344     @Test
345     public void testTranslationFileWithLanguageCountryVariantIsMissing() throws Exception {
346         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
347         checkConfig.addAttribute("requiredTranslations", "es, de");
348 
349         final File[] propertyFiles = {
350             new File(getPath("messages_home.properties")),
351             new File(getPath("messages_home_es_US.properties")),
352             new File(getPath("messages_home_fr_CA_UNIX.properties")),
353             };
354 
355         final String[] expected = {
356             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
357                     "messages_home_de.properties"),
358         };
359         verify(
360             createChecker(checkConfig),
361             propertyFiles,
362             getPath(""),
363             expected);
364     }
365 
366     @Test
367     public void testTranslationFileWithLanguageCountryVariantArePresent() throws Exception {
368         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
369         checkConfig.addAttribute("requiredTranslations", "es, fr");
370 
371         final File[] propertyFiles = {
372             new File(getPath("messages_home.properties")),
373             new File(getPath("messages_home_es_US.properties")),
374             new File(getPath("messages_home_fr_CA_UNIX.properties")),
375             };
376 
377         final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
378         verify(
379             createChecker(checkConfig),
380             propertyFiles,
381             getPath(""),
382             expected);
383     }
384 
385     /**
386      * Pitest requires all closes of streams and readers to be verified. Using PowerMock
387      * is almost only possibility to check it without rewriting production code.
388      *
389      * @throws Exception when code tested throws some exception
390      */
391     @Test
392     public void testResourcesAreClosed() throws Exception {
393         mockStatic(Closeables.class);
394         doNothing().when(Closeables.class);
395         Closeables.closeQuietly(any(InputStream.class));
396 
397         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
398         checkConfig.addAttribute("requiredTranslations", "es");
399 
400         final File[] propertyFiles = {
401             new File(getPath("messages_home.properties")),
402             new File(getPath("messages_home_es_US.properties")),
403             };
404 
405         final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
406         verify(
407             createChecker(checkConfig),
408             propertyFiles,
409             getPath(""),
410             expected);
411         verifyStatic(times(2));
412         Closeables.closeQuietly(any(FileInputStream.class));
413     }
414 
415     @Test
416     public void testBaseNameOption() throws Exception {
417         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
418         checkConfig.addAttribute("requiredTranslations", "de, es, fr, ja");
419         checkConfig.addAttribute("baseName", "^.*Labels$");
420 
421         final File[] propertyFiles = {
422             new File(getPath("ButtonLabels.properties")),
423             new File(getPath("ButtonLabels_de.properties")),
424             new File(getPath("ButtonLabels_es.properties")),
425             new File(getPath("ButtonLabels_fr_CA_UNIX.properties")),
426             new File(getPath("messages_home.properties")),
427             new File(getPath("messages_home_es_US.properties")),
428             new File(getPath("messages_home_fr_CA_UNIX.properties")),
429         };
430 
431         final String[] expected = {
432             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
433                     "ButtonLabels_ja.properties"),
434         };
435         verify(
436             createChecker(checkConfig),
437             propertyFiles,
438             getPath(""),
439             expected);
440     }
441 
442     @Test
443     public void testFileExtensions() throws Exception {
444         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
445         checkConfig.addAttribute("requiredTranslations", "de, es, fr, ja");
446         checkConfig.addAttribute("fileExtensions", "properties,translation");
447         checkConfig.addAttribute("baseName", "^.*(Titles|Labels)$");
448 
449         final File[] propertyFiles = {
450             new File(getPath("ButtonLabels.properties")),
451             new File(getPath("ButtonLabels_de.properties")),
452             new File(getPath("ButtonLabels_es.properties")),
453             new File(getPath("ButtonLabels_fr_CA_UNIX.properties")),
454             new File(getPath("PageTitles.translation")),
455             new File(getPath("PageTitles_de.translation")),
456             new File(getPath("PageTitles_es.translation")),
457             new File(getPath("PageTitles_fr.translation")),
458             new File(getPath("PageTitles_ja.translation")),
459         };
460 
461         final String[] expected = {
462             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
463                     "ButtonLabels_ja.properties"),
464         };
465 
466         verify(
467             createChecker(checkConfig),
468             propertyFiles,
469             getPath(""),
470             expected);
471     }
472 
473     @Test
474     public void testEqualBaseNamesButDifferentExtensions() throws Exception {
475         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
476         checkConfig.addAttribute("requiredTranslations", "de, es, fr, ja");
477         checkConfig.addAttribute("fileExtensions", "properties,translations");
478         checkConfig.addAttribute("baseName", "^.*Labels$");
479 
480         final File[] propertyFiles = {
481             new File(getPath("ButtonLabels.properties")),
482             new File(getPath("ButtonLabels_de.properties")),
483             new File(getPath("ButtonLabels_es.properties")),
484             new File(getPath("ButtonLabels_fr_CA_UNIX.properties")),
485             new File(getPath("ButtonLabels.translations")),
486             new File(getPath("ButtonLabels_ja.translations")),
487             new File(getPath("ButtonLabels_es.translations")),
488             new File(getPath("ButtonLabels_fr_CA_UNIX.translations")),
489             new File(getPath("ButtonLabels_de.translations")),
490         };
491 
492         final String[] expected = {
493             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE,
494                     "ButtonLabels_ja.properties"),
495         };
496 
497         verify(
498             createChecker(checkConfig),
499             propertyFiles,
500             getPath(""),
501             expected);
502     }
503 
504     @Test
505     public void testRegexpToMatchPartOfBaseName() throws Exception {
506         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
507         checkConfig.addAttribute("requiredTranslations", "de, es, fr, ja");
508         checkConfig.addAttribute("fileExtensions", "properties,translations");
509         checkConfig.addAttribute("baseName", "^.*Labels.*");
510 
511         final File[] propertyFiles = {
512             new File(getPath("MyLabelsI18.properties")),
513             new File(getPath("MyLabelsI18_de.properties")),
514             new File(getPath("MyLabelsI18_es.properties")),
515         };
516 
517         final String[] expected = {
518             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE, "MyLabelsI18_fr.properties"),
519             "0: " + getCheckMessage(MSG_KEY_MISSING_TRANSLATION_FILE, "MyLabelsI18_ja.properties"),
520         };
521 
522         verify(
523             createChecker(checkConfig),
524             propertyFiles,
525             getPath(""),
526             expected);
527     }
528 
529     @Test
530     public void testBundlesWithSameNameButDifferentPaths() throws Exception {
531         final DefaultConfiguration checkConfig = createModuleConfig(TranslationCheck.class);
532         checkConfig.addAttribute("requiredTranslations", "de");
533         checkConfig.addAttribute("fileExtensions", "properties");
534         checkConfig.addAttribute("baseName", "^.*Labels.*");
535 
536         final File[] propertyFiles = {
537             new File(getPath("MyLabelsI18.properties")),
538             new File(getPath("MyLabelsI18_de.properties")),
539             new File(getNonCompilablePath("MyLabelsI18.properties")),
540             new File(getNonCompilablePath("MyLabelsI18_de.properties")),
541         };
542 
543         final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
544 
545         verify(
546             createChecker(checkConfig),
547             propertyFiles,
548             getPath(""),
549             expected);
550     }
551 
552     @Test
553     public void testWrongUserSpecifiedLanguageCodes() {
554         final TranslationCheck check = new TranslationCheck();
555         try {
556             check.setRequiredTranslations("11");
557             fail("IllegalArgumentException is expected. Specified language code is incorrect.");
558         }
559         catch (IllegalArgumentException ex) {
560             final String exceptionMessage = ex.getMessage();
561             assertThat("Error message is unexpected",
562                     exceptionMessage, containsString("11"));
563             assertThat("Error message is unexpected",
564                     exceptionMessage, endsWith("[TranslationCheck]"));
565         }
566     }
567 }