blob: 416c99729c007429721575013d06102540941e09 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2017 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import glob
import logging
import os
import re
import shutil
import string
import tempfile
import unittest
import factory_common # pylint: disable=unused-import
from cros.factory.test.i18n import translation
from cros.factory.utils import file_utils
from cros.factory.utils import process_utils
SCRIPT_DIR = os.path.dirname(__file__)
class _MockValue(object):
"""A mock value that accepts all format_spec for __format__."""
def __format__(self, format_spec):
del format_spec # Unused.
return ''
class PoBuildTest(unittest.TestCase):
"""Basic sanity check for po files."""
@classmethod
def setUpClass(cls):
cls.temp_dir = tempfile.mkdtemp(prefix='po_check_test.')
cls.po_dir = os.path.join(cls.temp_dir, 'po')
cls.build_dir = os.path.join(cls.temp_dir, 'build')
cls.locale_dir = os.path.join(cls.build_dir, 'locale')
po_files = glob.glob(os.path.join(SCRIPT_DIR, '*.po'))
os.makedirs(cls.po_dir)
for po_file in po_files:
shutil.copy(po_file, cls.po_dir)
env = {'PO_DIR': cls.po_dir, 'BUILD_DIR': cls.build_dir}
process_utils.Spawn(['make', '-C', SCRIPT_DIR, 'build'],
ignore_stdout=True, ignore_stderr=True,
env=env, check_call=True)
translation.LOCALES = [translation.DEFAULT_LOCALE] + [
os.path.splitext(os.path.basename(po_file))[0] for po_file in po_files]
translation.LOCALE_DIR = cls.locale_dir
@classmethod
def tearDownClass(cls):
if os.path.exists(cls.temp_dir):
shutil.rmtree(cls.temp_dir)
def setUp(self):
self.formatter = string.Formatter()
self.errors = []
def tearDown(self):
if self.errors:
raise AssertionError('\n'.join(self.errors).encode('UTF-8'))
def AddError(self, err):
self.errors.append(err)
def testFormatStringVariablesMatch(self):
all_translations = translation.GetAllTranslations()
for text in all_translations:
default_text = text[translation.DEFAULT_LOCALE]
default_vars = self._ExtractVariablesFromFormatString(
default_text, translation.DEFAULT_LOCALE)
for locale in translation.LOCALES:
if locale == translation.DEFAULT_LOCALE:
continue
used_vars = self._ExtractVariablesFromFormatString(text[locale], locale)
unknown_vars = used_vars - default_vars
if unknown_vars:
self.AddError(u'[%s] "%s": Unknown vars %r' %
(locale, text[locale], list(unknown_vars)))
unused_vars = default_vars - used_vars
if unused_vars:
logging.warn(u'[%s] "%s": Unused vars %r', locale, text[locale],
list(unused_vars))
def testFormatStringFormat(self):
all_translations = translation.GetAllTranslations()
kwargs = collections.defaultdict(_MockValue)
for text in all_translations:
for locale in translation.LOCALES:
try:
self.formatter.vformat(text[locale], [], kwargs)
except Exception as e:
self.AddError('[%s] "%s": %s' % (locale, text[locale], e))
def _ExtractVariablesFromFormatString(self, format_str, locale):
ret = set()
for unused_text, field_name, unused_format_spec, unused_conversion in (
self.formatter.parse(format_str)):
if field_name is None:
continue
var_name = re.match('[a-zA-Z0-9_]*', field_name).group(0)
if not var_name or re.match('[0-9]+$', var_name):
self.AddError(u'[%s] "%s": Positional argument {%s} found' %
(locale, format_str, var_name))
else:
ret.add(var_name)
return ret
class PoCheckTest(unittest.TestCase):
"""Check some formatting issue for po files."""
def setUp(self):
self.po_files = glob.glob(os.path.join(SCRIPT_DIR, '*.po'))
def testNoFuzzy(self):
err_files = []
for po_file in self.po_files:
po_lines = file_utils.ReadLines(po_file)
if any(line.startswith('#, fuzzy') for line in po_lines):
err_files.append(os.path.basename(po_file))
self.assertFalse(
err_files,
"'#, fuzzy' lines found in files %r, please check the translation is "
'correct and remove those lines.' % err_files)
def testNoUnused(self):
err_files = []
for po_file in self.po_files:
po_lines = file_utils.ReadLines(po_file)
if any(line.startswith('#~') for line in po_lines):
err_files.append(os.path.basename(po_file))
self.assertFalse(
err_files,
"Lines started with '#~' found in files %r, please check if those lines"
' are unused and remove those lines.' % err_files)
class PoUpdateTest(unittest.TestCase):
"""Check that po update have been run."""
def runTest(self):
try:
temp_dir = tempfile.mkdtemp(prefix='po_update_test.')
po_dir = os.path.join(temp_dir, 'po')
po_files = glob.glob(os.path.join(SCRIPT_DIR, '*.po'))
os.makedirs(po_dir)
for po_file in po_files:
shutil.copy(po_file, po_dir)
env = {'PO_DIR': po_dir}
process_utils.Spawn(['make', '-C', SCRIPT_DIR, 'update'],
ignore_stdout=True, ignore_stderr=True,
env=env, check_call=True)
err_files = []
for po_file in po_files:
new_po_file = os.path.join(po_dir, os.path.basename(po_file))
if file_utils.ReadLines(po_file) != file_utils.ReadLines(new_po_file):
err_files.append(os.path.basename(po_file))
self.assertFalse(
err_files,
"Files %r are not updated, please run 'make -C po update' inside "
'chroot and check the translations.' % err_files)
finally:
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
if __name__ == '__main__':
unittest.main()