]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-2693 Hamcrest matcher to compare a pair of translation bundles
authorFabrice Bellingard <bellingard@gmail.com>
Fri, 2 Sep 2011 15:12:41 +0000 (17:12 +0200)
committerFabrice Bellingard <bellingard@gmail.com>
Fri, 2 Sep 2011 15:13:23 +0000 (17:13 +0200)
First commit and push to GitHub before activating other tests that
require this code to be pushed.

sonar-testing-harness/src/main/java/org/sonar/test/i18n/BundleSynchronizedMatcher.java [new file with mode: 0644]
sonar-testing-harness/src/main/java/org/sonar/test/i18n/I18nMatchers.java [new file with mode: 0644]
sonar-testing-harness/src/test/java/org/sonar/test/i18n/BundleSynchronizedTest.java [new file with mode: 0644]
sonar-testing-harness/src/test/resources/org/sonar/l10n/core.properties [new file with mode: 0644]
sonar-testing-harness/src/test/resources/org/sonar/l10n/core_fr.properties [new file with mode: 0644]
sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin.properties [new file with mode: 0644]
sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr.properties [new file with mode: 0644]
sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr_CA.properties [new file with mode: 0644]
sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr_QB.properties [new file with mode: 0644]

diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/i18n/BundleSynchronizedMatcher.java b/sonar-testing-harness/src/main/java/org/sonar/test/i18n/BundleSynchronizedMatcher.java
new file mode 100644 (file)
index 0000000..1fd44ec
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.test.i18n;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Properties;
+import java.util.Set;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.sonar.test.TestUtils;
+
+import com.google.common.collect.Lists;
+
+public class BundleSynchronizedMatcher extends BaseMatcher<String> {
+
+  public static final String L10N_PATH = "/org/sonar/l10n/";
+  private static final String GITHUB_RAW_FILE_PATH = "https://raw.github.com/SonarSource/sonar/master/plugins/sonar-l10n-en-plugin/src/main/resources/org/sonar/l10n/";
+  private static final Collection<String> CORE_BUNDLES = Lists.newArrayList("checkstyle.properties", "core.properties",
+      "findbugs.properties", "gwt.properties", "pmd.properties", "squidjava.properties");
+
+  // we use this variable to be able to unit test this class without looking at the real Github core bundles that change all the time
+  private String remote_file_path;
+  private String bundleName;
+  private Collection<String> missingKeys;
+  private Collection<String> nonExistingKeys;
+
+  public BundleSynchronizedMatcher() {
+    this(GITHUB_RAW_FILE_PATH);
+  }
+
+  public BundleSynchronizedMatcher(String remote_file_path) {
+    this.remote_file_path = remote_file_path;
+  }
+
+  public boolean matches(Object arg0) {
+    if ( !(arg0 instanceof String)) {
+      return false;
+    }
+    bundleName = (String) arg0;
+
+    // Get the bundle
+    File bundle = getBundleFileFromClasspath(bundleName);
+
+    // Find the default bundle name which should be compared to
+    String defaultBundleName = extractDefaultBundleName(bundleName);
+    File defaultBundle = null;
+    if (isCoreBundle(defaultBundleName)) {
+      defaultBundle = getBundleFileFromGithub(defaultBundleName);
+    } else {
+      defaultBundle = getBundleFileFromClasspath(defaultBundleName);
+    }
+
+    // and now let's compare
+    try {
+      missingKeys = retrieveMissingKeys(bundle, defaultBundle);
+      nonExistingKeys = retrieveMissingKeys(defaultBundle, bundle);
+      return missingKeys.isEmpty() && nonExistingKeys.isEmpty();
+    } catch (IOException e) {
+      fail("An error occured while reading the bundles: " + e.getMessage());
+      return false;
+    }
+  }
+
+  public void describeTo(Description description) {
+    StringBuilder details = new StringBuilder("\n=======================\n'");
+    details.append(bundleName);
+    details.append("' is not synchronized.");
+    if ( !missingKeys.isEmpty()) {
+      details.append("\n\n Missing keys are:");
+      for (String key : missingKeys) {
+        details.append("\n\t- " + key);
+      }
+    }
+    if ( !nonExistingKeys.isEmpty()) {
+      details.append("\n\nAlso, the following keys do not exist in the default bundle:");
+      for (String key : nonExistingKeys) {
+        details.append("\n\t- " + key);
+      }
+    }
+    details.append("\n\n=======================");
+
+    description.appendText(details.toString());
+  }
+
+  protected Collection<String> retrieveMissingKeys(File bundle, File defaultBundle) throws IOException {
+    Collection<String> missingKeys = Lists.newArrayList();
+
+    Properties bundleProps = new Properties();
+    bundleProps.load(new FileInputStream(bundle));
+    Set<Object> bundleKeys = bundleProps.keySet();
+
+    Properties defaultBundleProps = new Properties();
+    defaultBundleProps.load(new FileInputStream(defaultBundle));
+    Set<Object> defaultBundleKeys = defaultBundleProps.keySet();
+
+    for (Object key : defaultBundleKeys) {
+      if ( !bundleKeys.contains(key)) {
+        missingKeys.add(key.toString());
+      }
+    }
+
+    return missingKeys;
+  }
+
+  protected File getBundleFileFromGithub(String defaultBundleName) {
+    File bundle = new File("target/l10n/download/" + defaultBundleName);
+    try {
+      saveUrl(remote_file_path + defaultBundleName, bundle);
+    } catch (MalformedURLException e) {
+      fail("Could not download the original core bundle at: " + remote_file_path + defaultBundleName);
+    } catch (IOException e) {
+      fail("Could not download the original core bundle at: " + remote_file_path + defaultBundleName);
+    }
+    assertThat("File 'target/tmp/" + defaultBundleName + "' has been downloaded but does not exist.", bundle, notNullValue());
+    assertThat("File 'target/tmp/" + defaultBundleName + "' has been downloaded but does not exist.", bundle.exists(), is(true));
+    return bundle;
+  }
+
+  protected File getBundleFileFromClasspath(String bundleName) {
+    File bundle = TestUtils.getResource(L10N_PATH + bundleName);
+    assertThat("File '" + bundleName + "' does not exist in '/org/sonar/l10n/'.", bundle, notNullValue());
+    assertThat("File '" + bundleName + "' does not exist in '/org/sonar/l10n/'.", bundle.exists(), is(true));
+    return bundle;
+  }
+
+  protected String extractDefaultBundleName(String bundleName) {
+    int firstUnderScoreIndex = bundleName.indexOf('_');
+    assertThat("The bundle '" + bundleName + "' is a default bundle (without locale), so it can't be compared.", firstUnderScoreIndex > 0,
+        is(true));
+    return bundleName.substring(0, firstUnderScoreIndex) + ".properties";
+  }
+
+  protected boolean isCoreBundle(String defaultBundleName) {
+    return CORE_BUNDLES.contains(defaultBundleName);
+  }
+
+  private void saveUrl(String url, File localFile) throws MalformedURLException, IOException {
+    if (localFile.exists()) {
+      localFile.delete();
+    }
+    localFile.getParentFile().mkdirs();
+
+    BufferedInputStream in = null;
+    FileOutputStream fout = null;
+    try {
+      in = new BufferedInputStream(new URL(url).openStream());
+      fout = new FileOutputStream(localFile);
+
+      byte data[] = new byte[1024];
+      int count;
+      while ((count = in.read(data, 0, 1024)) != -1) {
+        fout.write(data, 0, count);
+      }
+    } finally {
+      if (in != null)
+        in.close();
+      if (fout != null)
+        fout.close();
+    }
+  }
+
+}
diff --git a/sonar-testing-harness/src/main/java/org/sonar/test/i18n/I18nMatchers.java b/sonar-testing-harness/src/main/java/org/sonar/test/i18n/I18nMatchers.java
new file mode 100644 (file)
index 0000000..6d24a63
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.test.i18n;
+
+public final class I18nMatchers {
+
+  private I18nMatchers() {
+  }
+
+  public static BundleSynchronizedMatcher isBundleSynchronized() {
+    return new BundleSynchronizedMatcher();
+  }
+}
diff --git a/sonar-testing-harness/src/test/java/org/sonar/test/i18n/BundleSynchronizedTest.java b/sonar-testing-harness/src/test/java/org/sonar/test/i18n/BundleSynchronizedTest.java
new file mode 100644 (file)
index 0000000..0bac5a9
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.test.i18n;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.sonar.test.i18n.I18nMatchers.isBundleSynchronized;
+
+import java.io.File;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.sonar.test.TestUtils;
+
+public class BundleSynchronizedTest {
+
+  private BundleSynchronizedMatcher matcher;
+
+  @Before
+  public void test() throws Exception {
+    matcher = new BundleSynchronizedMatcher("https://raw.github.com/SonarSource/sonar/master/sonar-testing-harness/src/test/resources/org/sonar/l10n/");
+  }
+
+  @Test
+  // The case of a Sonar plugin that embeds all the bundles for every language
+  public void testBundlesInsideSonarPlugin() {
+    // synchronized bundle
+    assertThat("myPlugin_fr_CA.properties", isBundleSynchronized());
+    // missing keys
+    try {
+      assertThat("myPlugin_fr.properties", isBundleSynchronized());
+    } catch (AssertionError e) {
+      assertThat(e.getMessage(), containsString("Missing keys are:\n\t- second.prop"));
+    }
+    // unnecessary many keys
+    try {
+      assertThat("myPlugin_fr_QB.properties", isBundleSynchronized());
+    } catch (AssertionError e) {
+      assertThat(e.getMessage(), containsString("Also, the following keys do not exist in the default bundle:\n\t- fourth.prop"));
+    }
+  }
+
+  @Test
+  @Ignore
+  // The case of a Sonar Language Pack that translates the Core bundles
+  public void testBundlesOfLanguagePack() {
+    assertThat("core_fr.properties", isBundleSynchronized());
+  }
+
+  @Test
+  public void testGetBundleFileFromClasspath() {
+    matcher.getBundleFileFromClasspath("core_fr.properties");
+    try {
+      matcher.getBundleFileFromClasspath("unexistingBundle.properties");
+    } catch (AssertionError e) {
+      assertThat(e.getMessage(), containsString("File 'unexistingBundle.properties' does not exist in '/org/sonar/l10n/'."));
+    }
+  }
+
+  @Test
+  @Ignore
+  public void testGetBundleFileFromGithub() throws Exception {
+    matcher.getBundleFileFromGithub("core.properties");
+    assertTrue(new File("target/l10n/download/core.properties").exists());
+  }
+
+  @Test
+  public void testExtractDefaultBundleName() throws Exception {
+    assertThat(matcher.extractDefaultBundleName("myPlugin_fr.properties"), is("myPlugin.properties"));
+    assertThat(matcher.extractDefaultBundleName("myPlugin_fr_QB.properties"), is("myPlugin.properties"));
+    try {
+      matcher.extractDefaultBundleName("myPlugin.properties");
+    } catch (AssertionError e) {
+      assertThat(e.getMessage(),
+          containsString("The bundle 'myPlugin.properties' is a default bundle (without locale), so it can't be compared."));
+    }
+  }
+
+  @Test
+  public void testIsCoreBundle() throws Exception {
+    assertTrue(matcher.isCoreBundle("core.properties"));
+    assertFalse(matcher.isCoreBundle("myPlugin.properties"));
+  }
+
+  @Test
+  public void testRetrieveMissingKeys() throws Exception {
+    File defaultBundle = TestUtils.getResource(BundleSynchronizedMatcher.L10N_PATH + "myPlugin.properties");
+    File frBundle = TestUtils.getResource(BundleSynchronizedMatcher.L10N_PATH + "myPlugin_fr.properties");
+    File qbBundle = TestUtils.getResource(BundleSynchronizedMatcher.L10N_PATH + "myPlugin_fr_QB.properties");
+
+    Collection<String> diffs = matcher.retrieveMissingKeys(frBundle, defaultBundle);
+    assertThat(diffs.size(), is(1));
+    assertThat(diffs, hasItem("second.prop"));
+
+    diffs = matcher.retrieveMissingKeys(qbBundle, defaultBundle);
+    assertThat(diffs.size(), is(0));
+  }
+}
diff --git a/sonar-testing-harness/src/test/resources/org/sonar/l10n/core.properties b/sonar-testing-harness/src/test/resources/org/sonar/l10n/core.properties
new file mode 100644 (file)
index 0000000..d182159
--- /dev/null
@@ -0,0 +1,4 @@
+## -------- Test file for the BundleSynchronizedMatcher -------- ##
+first.prop = This is my first property
+second.prop = This is my second property
+third.prop = This is my third property
\ No newline at end of file
diff --git a/sonar-testing-harness/src/test/resources/org/sonar/l10n/core_fr.properties b/sonar-testing-harness/src/test/resources/org/sonar/l10n/core_fr.properties
new file mode 100644 (file)
index 0000000..aed2acc
--- /dev/null
@@ -0,0 +1,4 @@
+## -------- Test file for the BundleSynchronizedMatcher -------- ##
+first.prop = This is my first property
+#second.prop = This is my second property
+third.prop = This is my third property
\ No newline at end of file
diff --git a/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin.properties b/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin.properties
new file mode 100644 (file)
index 0000000..d182159
--- /dev/null
@@ -0,0 +1,4 @@
+## -------- Test file for the BundleSynchronizedMatcher -------- ##
+first.prop = This is my first property
+second.prop = This is my second property
+third.prop = This is my third property
\ No newline at end of file
diff --git a/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr.properties b/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr.properties
new file mode 100644 (file)
index 0000000..79c32ed
--- /dev/null
@@ -0,0 +1,4 @@
+## -------- Test file for the BundleSynchronizedMatcher -------- ##
+first.prop = C'est ma première propriété
+#second.prop = C'est ma deuxième propriété
+third.prop = C'est ma troisième propriété
\ No newline at end of file
diff --git a/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr_CA.properties b/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr_CA.properties
new file mode 100644 (file)
index 0000000..462cc8b
--- /dev/null
@@ -0,0 +1,4 @@
+## -------- Test file for the BundleSynchronizedMatcher -------- ##
+first.prop = C'est ma première propriété
+second.prop = C'est ma deuxième propriété
+third.prop = C'est ma troisième propriété
\ No newline at end of file
diff --git a/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr_QB.properties b/sonar-testing-harness/src/test/resources/org/sonar/l10n/myPlugin_fr_QB.properties
new file mode 100644 (file)
index 0000000..5076dd8
--- /dev/null
@@ -0,0 +1,5 @@
+## -------- Test file for the BundleSynchronizedMatcher -------- ##
+first.prop = C'est ma première propriété
+second.prop = C'est ma deuxième propriété
+third.prop = C'est ma troisième propriété
+fourth.prop = C'est ma quatrième propriété
\ No newline at end of file