/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2015 Aaron Madlon-Kay
Home page: http://www.omegat.org/
Support center: http://groups.yahoo.com/group/OmegaT/
This file is part of OmegaT.
OmegaT is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmegaT 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
package svn;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.Test;
import org.omegat.Main;
import org.omegat.util.EncodingDetector;
import org.omegat.util.Language;
import org.omegat.util.OStrings;
/**
*
* @author Aaron Madlon-Kay
*/
public class BundleTest {
/**
* Ensure that all UI string bundles have either US-ASCII encoding or ISO-8859-1 encoding. The spec requires the
* latter, but ISO-8859-1 is a superset of ASCII so ASCII is also acceptable (and is widely used in practice).
*
* @see <a href=
* "https://docs.oracle.com/javase/8/docs/api/java/util/PropertyResourceBundle.html">PropertyResourceBundle</a>
*/
@Test
public void testBundleEncodings() throws Exception {
// Test English bundle separately as its name corresponds to the
// empty locale, and will not be resolved otherwise.
assertEncoding("Bundle.properties");
for (Language lang : Language.getLanguages()) {
String bundle = "Bundle_" + lang.getLocaleCode() + ".properties";
assertEncoding(bundle);
}
}
private void assertEncoding(String bundle) throws IOException {
try (InputStream stream = Main.class.getResourceAsStream(bundle)) {
if (stream == null) {
return;
}
String encoding = EncodingDetector.detectEncoding(stream);
System.out.println(bundle + ": " + encoding);
// The detector will give null for ASCII and Windows-1252 for ISO-8859-1;
// yes, this is not technically correct, but it's close enough. See:
// http://www.i18nqa.com/debug/table-iso8859-1-vs-windows-1252.html
assertTrue(encoding == null || "WINDOWS-1252".equals(encoding));
}
}
@Test
public void testBundleLoading() {
// We must set the default locale to English first because we provide our
// English bundle as the empty-locale default. If we don't do so, the
// English bundle will never be tested in the case that the "default default"
// is a language we provide a bundle for.
Locale.setDefault(Locale.ENGLISH);
for (Language lang : Language.getLanguages()) {
ResourceBundle bundle = ResourceBundle.getBundle("org/omegat/Bundle", lang.getLocale());
assertTrue(bundle.getKeys().hasMoreElements());
}
}
@Test
public void testVersionPropsLoading() {
ResourceBundle bundle = ResourceBundle.getBundle("org/omegat/Version");
bundle.getString("version");
bundle.getString("update");
bundle.getString("revision");
}
@Test
public void testLoggerPropsLoading() {
ResourceBundle bundle = ResourceBundle.getBundle("org/omegat/logger");
assertTrue(bundle.getKeys().hasMoreElements());
}
@Test
public void testShortcutPropsLoading() throws Exception {
ResourceBundle bundle = ResourceBundle.getBundle("org/omegat/gui/main/MainMenuShortcuts");
assertTrue(bundle.getKeys().hasMoreElements());
// ResourceBundle.getBundle won't resolve the Mac-specific file's name
// so we have to load it manually.
Properties props = new Properties();
props.load(getClass().getResourceAsStream("/org/omegat/gui/main/MainMenuShortcuts.mac.properties"));
assertFalse(props.isEmpty());
}
/**
* Search for UI strings used via OStrings.getString() but not defined in
* Bundle.properties.
* <p>
* This is a brute-force text search over all .java source files and will
* not catch dynamically computed keys or exotic invocations (such as via
* method references, e.g. OStrings::getString, etc.).
* <p>
* More thorough checks would be possible by actually running the relevant
* code, but a lot of it requires a GUI environment (our CI is headless, so
* such tests will rarely get run), or a substantial testing harness, or
* requires people to manually add tests (which they just won't do).
*
* @throws Exception
*/
@Test
public void testUndefinedStrings() throws Exception {
Locale.setDefault(Locale.ENGLISH);
Pattern pattern = Pattern.compile("OStrings\\.getString\\(\\s*\"([^\"]+)\"\\s*[,\\)]");
processSourceContent((path, chars) -> {
Matcher m = pattern.matcher(chars);
while (m.find()) {
OStrings.getString(m.group(1));
}
});
}
/**
* Test the behavior when a resource key is missing. Various code assumes
* that we throw a MissingResourceException, not e.g. return null or the
* empty string.
*/
@Test(expected = MissingResourceException.class)
public void testUndefinedString() {
Locale.setDefault(Locale.ENGLISH);
// Not sure why we'd ever have a UUID key, but just in case, keep trying
// new UUIDs until we hit one that's missing (or we give up at
// Integer.MAX_VALUE and fail).
for (int i = 0; i < Integer.MAX_VALUE; i++) {
OStrings.getString(UUID.randomUUID().toString());
}
}
/**
* Process the text content of all .java files under /src. Will blow up if
* any are not US-ASCII.
*
* @param consumer
* A function that accepts the file path and content
* @throws IOException
* from Files.find()
*/
public static void processSourceContent(BiConsumer<Path, CharSequence> consumer) throws IOException {
CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);
decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
for (String root : new String[] { "src", "test", "test-integration" }) {
Path rootPath = Paths.get(".", root);
assertTrue(rootPath.toFile().isDirectory());
Files.find(rootPath, 100,
(path, attrs) -> attrs.isRegularFile() && path.toString().endsWith(".java")).forEach(p -> {
try {
byte[] bytes = Files.readAllBytes(p);
CharBuffer chars = decoder.decode(ByteBuffer.wrap(bytes));
consumer.accept(p, chars);
} catch (MalformedInputException ex) {
throw new RuntimeException("File contains non-ASCII characters: " + p, ex);
} catch (IOException ex) {
throw new RuntimeException(p.toString(), ex);
}
});
}
}
}