blob: 8ea934e9456ddd440548857dbb18be400c1ab8e8 [file] [log] [blame]
# -*- 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 &apos;good&apos;">
<!ENTITY width "10ch">
<!ENTITY style "width: 20ch; height: 280px;">
<!ENTITY minStyle "min-height: 50em;">
<!ENTITY ftd "0">
<!ENTITY formatPercent "This is 100&#037; correct">
<!ENTITY some.key "K">
'''
def testWarning(self):
self._test('''<!ENTITY foo "This is &not; 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 &quot;good&quot;">
''',
tuple())
def testPercentEntity(self):
self._test('''<!ENTITY formatPercent "Another 100&#037;">
''',
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('"', '&quot;')
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()