| # -*- coding: utf-8 -*- |
| # This Source Code Form is subject to the terms of the Mozilla Public |
| # License, v. 2.0. If a copy of the MPL was not distributed with this |
| # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| |
| import unittest |
| |
| from compare_locales.checks import getChecker |
| from compare_locales.parser import getParser, Entity |
| from compare_locales.paths import File |
| |
| |
| class BaseHelper(unittest.TestCase): |
| file = None |
| refContent = None |
| |
| def setUp(self): |
| p = getParser(self.file.file) |
| p.readContents(self.refContent) |
| self.refList, self.refMap = p.parse() |
| |
| def _test(self, content, refWarnOrErrors): |
| p = getParser(self.file.file) |
| p.readContents(content) |
| l10n = [e for e in p] |
| assert len(l10n) == 1 |
| l10n = l10n[0] |
| checker = getChecker(self.file) |
| ref = self.refList[self.refMap[l10n.key]] |
| found = tuple(checker.check(ref, l10n)) |
| self.assertEqual(found, refWarnOrErrors) |
| |
| |
| class TestProperties(BaseHelper): |
| file = File('foo.properties', 'foo.properties') |
| refContent = '''some = value |
| ''' |
| |
| def testGood(self): |
| self._test('''some = localized''', |
| tuple()) |
| |
| def testMissedEscape(self): |
| self._test(r'''some = \u67ood escape, bad \escape''', |
| (('warning', 20, r'unknown escape sequence, \e', |
| 'escape'),)) |
| |
| |
| class TestPlurals(BaseHelper): |
| file = File('foo.properties', 'foo.properties') |
| refContent = '''\ |
| # LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms. |
| # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals |
| # #1 number of files |
| # example: 111 files - Downloads |
| downloadsTitleFiles=#1 file - Downloads;#1 files - #2 |
| ''' |
| |
| def testGood(self): |
| self._test('''\ |
| # LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms. |
| # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals |
| # #1 number of files |
| # example: 111 files - Downloads |
| downloadsTitleFiles=#1 file - Downloads;#1 files - #2;#1 filers |
| ''', |
| tuple()) |
| |
| def testNotUsed(self): |
| self._test('''\ |
| # LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms. |
| # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals |
| # #1 number of files |
| # example: 111 files - Downloads |
| downloadsTitleFiles=#1 file - Downloads;#1 files - Downloads;#1 filers |
| ''', |
| (('warning', 0, 'not all variables used in l10n', |
| 'plural'),)) |
| |
| def testNotDefined(self): |
| self._test('''\ |
| # LOCALIZATION NOTE (downloadsTitleFiles): Semi-colon list of plural forms. |
| # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals |
| # #1 number of files |
| # example: 111 files - Downloads |
| downloadsTitleFiles=#1 file - Downloads;#1 files - #2;#1 #3 |
| ''', |
| (('error', 0, 'unreplaced variables in l10n', 'plural'),)) |
| |
| |
| class TestDTDs(BaseHelper): |
| file = File('foo.dtd', 'foo.dtd') |
| refContent = '''<!ENTITY foo "This is 'good'"> |
| <!ENTITY width "10ch"> |
| <!ENTITY style "width: 20ch; height: 280px;"> |
| <!ENTITY minStyle "min-height: 50em;"> |
| <!ENTITY ftd "0"> |
| <!ENTITY formatPercent "This is 100% correct"> |
| <!ENTITY some.key "K"> |
| ''' |
| |
| def testWarning(self): |
| self._test('''<!ENTITY foo "This is ¬ good"> |
| ''', |
| (('warning', (0, 0), 'Referencing unknown entity `not`', |
| 'xmlparse'),)) |
| # make sure we only handle translated entity references |
| self._test(u'''<!ENTITY foo "This is &ƞǿŧ; good"> |
| '''.encode('utf-8'), |
| (('warning', (0, 0), u'Referencing unknown entity `ƞǿŧ`', |
| 'xmlparse'),)) |
| |
| def testErrorFirstLine(self): |
| self._test('''<!ENTITY foo "This is </bad> stuff"> |
| ''', |
| (('error', (1, 10), 'mismatched tag', 'xmlparse'),)) |
| |
| def testErrorSecondLine(self): |
| self._test('''<!ENTITY foo "This is |
| </bad> |
| stuff"> |
| ''', |
| (('error', (2, 4), 'mismatched tag', 'xmlparse'),)) |
| |
| def testKeyErrorSingleAmpersand(self): |
| self._test('''<!ENTITY some.key "&"> |
| ''', |
| (('error', (1, 1), 'not well-formed (invalid token)', |
| 'xmlparse'),)) |
| |
| def testXMLEntity(self): |
| self._test('''<!ENTITY foo "This is "good""> |
| ''', |
| tuple()) |
| |
| def testPercentEntity(self): |
| self._test('''<!ENTITY formatPercent "Another 100%"> |
| ''', |
| tuple()) |
| self._test('''<!ENTITY formatPercent "Bad 100% should fail"> |
| ''', |
| (('error', (0, 32), 'not well-formed (invalid token)', |
| 'xmlparse'),)) |
| |
| def testNoNumber(self): |
| self._test('''<!ENTITY ftd "foo">''', |
| (('warning', 0, 'reference is a number', 'number'),)) |
| |
| def testNoLength(self): |
| self._test('''<!ENTITY width "15miles">''', |
| (('error', 0, 'reference is a CSS length', 'css'),)) |
| |
| def testNoStyle(self): |
| self._test('''<!ENTITY style "15ch">''', |
| (('error', 0, 'reference is a CSS spec', 'css'),)) |
| self._test('''<!ENTITY style "junk">''', |
| (('error', 0, 'reference is a CSS spec', 'css'),)) |
| |
| def testStyleWarnings(self): |
| self._test('''<!ENTITY style "width:15ch">''', |
| (('warning', 0, 'height only in reference', 'css'),)) |
| self._test('''<!ENTITY style "width:15em;height:200px;">''', |
| (('warning', 0, "units for width don't match (em != ch)", |
| 'css'),)) |
| |
| def testNoWarning(self): |
| self._test('''<!ENTITY width "12em">''', tuple()) |
| self._test('''<!ENTITY style "width:12ch;height:200px;">''', tuple()) |
| self._test('''<!ENTITY ftd "0">''', tuple()) |
| |
| |
| class TestAndroid(unittest.TestCase): |
| """Test Android checker |
| |
| Make sure we're hitting our extra rules only if |
| we're passing in a DTD file in the embedding/android module. |
| """ |
| apos_msg = u"Apostrophes in Android DTDs need escaping with \\' or " + \ |
| u"\\u0027, or use \u2019, or put string in quotes." |
| quot_msg = u"Quotes in Android DTDs need escaping with \\\" or " + \ |
| u"\\u0022, or put string in apostrophes." |
| |
| def getEntity(self, v): |
| return Entity(v, lambda s: s, (0, len(v)), (), (0, 0), (), (), |
| (0, len(v)), ()) |
| |
| def getDTDEntity(self, v): |
| v = v.replace('"', '"') |
| return Entity('<!ENTITY foo "%s">' % v, |
| lambda s: s, |
| (0, len(v) + 16), (), (0, 0), (), (9, 12), |
| (14, len(v) + 14), ()) |
| |
| def test_android_dtd(self): |
| """Testing the actual android checks. The logic is involved, |
| so this is a lot of nitty gritty detail tests. |
| """ |
| f = File("embedding/android/strings.dtd", "strings.dtd", |
| "embedding/android") |
| checker = getChecker(f) |
| # good string |
| ref = self.getDTDEntity("plain string") |
| l10n = self.getDTDEntity("plain localized string") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # dtd warning |
| l10n = self.getDTDEntity("plain localized string &ref;") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('warning', (0, 0), |
| 'Referencing unknown entity `ref`', 'xmlparse'),)) |
| # no report on stray ampersand or quote, if not completely quoted |
| for i in xrange(3): |
| # make sure we're catching unescaped apostrophes, |
| # try 0..5 backticks |
| l10n = self.getDTDEntity("\\"*(2*i) + "'") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 2*i, self.apos_msg, 'android'),)) |
| l10n = self.getDTDEntity("\\"*(2*i + 1) + "'") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # make sure we don't report if apos string is quoted |
| l10n = self.getDTDEntity('"' + "\\"*(2*i) + "'\"") |
| tpl = tuple(checker.check(ref, l10n)) |
| self.assertEqual(tpl, (), |
| "`%s` shouldn't fail but got %s" |
| % (l10n.val, str(tpl))) |
| l10n = self.getDTDEntity('"' + "\\"*(2*i+1) + "'\"") |
| tpl = tuple(checker.check(ref, l10n)) |
| self.assertEqual(tpl, (), |
| "`%s` shouldn't fail but got %s" |
| % (l10n.val, str(tpl))) |
| # make sure we're catching unescaped quotes, try 0..5 backticks |
| l10n = self.getDTDEntity("\\"*(2*i) + "\"") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 2*i, self.quot_msg, 'android'),)) |
| l10n = self.getDTDEntity("\\"*(2*i + 1) + "'") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # make sure we don't report if quote string is single quoted |
| l10n = self.getDTDEntity("'" + "\\"*(2*i) + "\"'") |
| tpl = tuple(checker.check(ref, l10n)) |
| self.assertEqual(tpl, (), |
| "`%s` shouldn't fail but got %s" % |
| (l10n.val, str(tpl))) |
| l10n = self.getDTDEntity('"' + "\\"*(2*i+1) + "'\"") |
| tpl = tuple(checker.check(ref, l10n)) |
| self.assertEqual(tpl, (), |
| "`%s` shouldn't fail but got %s" % |
| (l10n.val, str(tpl))) |
| # check for mixed quotes and ampersands |
| l10n = self.getDTDEntity("'\"") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 0, self.apos_msg, 'android'), |
| ('error', 1, self.quot_msg, 'android'))) |
| l10n = self.getDTDEntity("''\"'") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 1, self.apos_msg, 'android'),)) |
| l10n = self.getDTDEntity('"\'""') |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 2, self.quot_msg, 'android'),)) |
| |
| # broken unicode escape |
| l10n = self.getDTDEntity("Some broken \u098 unicode") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 12, 'truncated \\uXXXX escape', |
| 'android'),)) |
| # broken unicode escape, try to set the error off |
| l10n = self.getDTDEntity(u"\u9690"*14+"\u006"+" "+"\u0064") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 14, 'truncated \\uXXXX escape', |
| 'android'),)) |
| |
| def test_android_prop(self): |
| f = File("embedding/android/strings.properties", "strings.properties", |
| "embedding/android") |
| checker = getChecker(f) |
| # good plain string |
| ref = self.getEntity("plain string") |
| l10n = self.getEntity("plain localized string") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # no dtd warning |
| ref = self.getEntity("plain string") |
| l10n = self.getEntity("plain localized string &ref;") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # no report on stray ampersand |
| ref = self.getEntity("plain string") |
| l10n = self.getEntity("plain localized string with apos: '") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # report on bad printf |
| ref = self.getEntity("string with %s") |
| l10n = self.getEntity("string with %S") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('error', 0, 'argument 1 `S` should be `s`', |
| 'printf'),)) |
| |
| def test_non_android_dtd(self): |
| f = File("browser/strings.dtd", "strings.dtd", "browser") |
| checker = getChecker(f) |
| # good string |
| ref = self.getDTDEntity("plain string") |
| l10n = self.getDTDEntity("plain localized string") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # dtd warning |
| ref = self.getDTDEntity("plain string") |
| l10n = self.getDTDEntity("plain localized string &ref;") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('warning', (0, 0), |
| 'Referencing unknown entity `ref`', 'xmlparse'),)) |
| # no report on stray ampersand |
| ref = self.getDTDEntity("plain string") |
| l10n = self.getDTDEntity("plain localized string with apos: '") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| |
| def test_entities_across_dtd(self): |
| f = File("browser/strings.dtd", "strings.dtd", "browser") |
| p = getParser(f.file) |
| p.readContents('<!ENTITY other "some &good.ref;">') |
| ref = p.parse() |
| checker = getChecker(f, reference=ref[0]) |
| # good string |
| ref = self.getDTDEntity("plain string") |
| l10n = self.getDTDEntity("plain localized string") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| # dtd warning |
| ref = self.getDTDEntity("plain string") |
| l10n = self.getDTDEntity("plain localized string &ref;") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| (('warning', (0, 0), |
| 'Referencing unknown entity `ref` (good.ref known)', |
| 'xmlparse'),)) |
| # no report on stray ampersand |
| ref = self.getDTDEntity("plain string") |
| l10n = self.getDTDEntity("plain localized string with &good.ref;") |
| self.assertEqual(tuple(checker.check(ref, l10n)), |
| ()) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |