1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6'''Unit tests for grit.clique'''
7
8import os
9import sys
10if __name__ == '__main__':
11  sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
12
13import re
14import StringIO
15import unittest
16
17from grit import clique
18from grit import exception
19from grit import pseudo
20from grit import tclib
21from grit import grd_reader
22from grit import util
23
24class MessageCliqueUnittest(unittest.TestCase):
25  def testClique(self):
26    factory = clique.UberClique()
27    msg = tclib.Message(text='Hello USERNAME, how are you?',
28                        placeholders=[
29                          tclib.Placeholder('USERNAME', '%s', 'Joi')])
30    c = factory.MakeClique(msg)
31
32    self.failUnless(c.GetMessage() == msg)
33    self.failUnless(c.GetId() == msg.GetId())
34
35    msg_fr = tclib.Translation(text='Bonjour USERNAME, comment ca va?',
36                               id=msg.GetId(), placeholders=[
37                                tclib.Placeholder('USERNAME', '%s', 'Joi')])
38    msg_de = tclib.Translation(text='Guten tag USERNAME, wie geht es dir?',
39                               id=msg.GetId(), placeholders=[
40                                tclib.Placeholder('USERNAME', '%s', 'Joi')])
41
42    c.AddTranslation(msg_fr, 'fr')
43    factory.FindCliqueAndAddTranslation(msg_de, 'de')
44
45    # sort() sorts lists in-place and does not return them
46    for lang in ('en', 'fr', 'de'):
47      self.failUnless(lang in c.clique)
48
49    self.failUnless(c.MessageForLanguage('fr').GetRealContent() ==
50                    msg_fr.GetRealContent())
51
52    try:
53      c.MessageForLanguage('zh-CN', False)
54      self.fail('Should have gotten exception')
55    except:
56      pass
57
58    self.failUnless(c.MessageForLanguage('zh-CN', True) != None)
59
60    rex = re.compile('fr|de|bingo')
61    self.failUnless(len(c.AllMessagesThatMatch(rex, False)) == 2)
62    self.failUnless(c.AllMessagesThatMatch(rex, True)[pseudo.PSEUDO_LANG] != None)
63
64  def testBestClique(self):
65    factory = clique.UberClique()
66    factory.MakeClique(tclib.Message(text='Alfur', description='alfaholl'))
67    factory.MakeClique(tclib.Message(text='Alfur', description=''))
68    factory.MakeClique(tclib.Message(text='Vaettur', description=''))
69    factory.MakeClique(tclib.Message(text='Vaettur', description=''))
70    factory.MakeClique(tclib.Message(text='Troll', description=''))
71    factory.MakeClique(tclib.Message(text='Gryla', description='ID: IDS_GRYLA'))
72    factory.MakeClique(tclib.Message(text='Gryla', description='vondakerling'))
73    factory.MakeClique(tclib.Message(text='Leppaludi', description='ID: IDS_LL'))
74    factory.MakeClique(tclib.Message(text='Leppaludi', description=''))
75
76    count_best_cliques = 0
77    for c in factory.BestCliquePerId():
78      count_best_cliques += 1
79      msg = c.GetMessage()
80      text = msg.GetRealContent()
81      description = msg.GetDescription()
82      if text == 'Alfur':
83        self.failUnless(description == 'alfaholl')
84      elif text == 'Gryla':
85        self.failUnless(description == 'vondakerling')
86      elif text == 'Leppaludi':
87        self.failUnless(description == 'ID: IDS_LL')
88    self.failUnless(count_best_cliques == 5)
89
90  def testAllInUberClique(self):
91    resources = grd_reader.Parse(
92        StringIO.StringIO(u'''<?xml version="1.0" encoding="UTF-8"?>
93<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
94  <release seq="3">
95    <messages>
96      <message name="IDS_GREETING" desc="Printed to greet the currently logged in user">
97        Hello <ph name="USERNAME">%s<ex>Joi</ex></ph>, how are you doing today?
98      </message>
99    </messages>
100    <structures>
101      <structure type="dialog" name="IDD_ABOUTBOX" encoding="utf-16" file="grit/testdata/klonk.rc" />
102      <structure type="tr_html" name="ID_HTML" file="grit/testdata/simple.html" />
103    </structures>
104  </release>
105</grit>'''), util.PathFromRoot('.'))
106    resources.SetOutputLanguage('en')
107    resources.RunGatherers()
108    content_list = []
109    for clique_list in resources.UberClique().cliques_.values():
110      for clique in clique_list:
111        content_list.append(clique.GetMessage().GetRealContent())
112    self.failUnless('Hello %s, how are you doing today?' in content_list)
113    self.failUnless('Jack "Black" Daniels' in content_list)
114    self.failUnless('Hello!' in content_list)
115
116  def testCorrectExceptionIfWrongEncodingOnResourceFile(self):
117    '''This doesn't really belong in this unittest file, but what the heck.'''
118    resources = grd_reader.Parse(
119        StringIO.StringIO(u'''<?xml version="1.0" encoding="UTF-8"?>
120<grit latest_public_release="2" source_lang_id="en-US" current_release="3" base_dir=".">
121  <release seq="3">
122    <structures>
123      <structure type="dialog" name="IDD_ABOUTBOX" file="grit/testdata/klonk.rc" />
124    </structures>
125  </release>
126</grit>'''), util.PathFromRoot('.'))
127    self.assertRaises(exception.SectionNotFound, resources.RunGatherers)
128
129  def testSemiIdenticalCliques(self):
130    messages = [
131      tclib.Message(text='Hello USERNAME',
132                    placeholders=[tclib.Placeholder('USERNAME', '$1', 'Joi')]),
133      tclib.Message(text='Hello USERNAME',
134                    placeholders=[tclib.Placeholder('USERNAME', '%s', 'Joi')]),
135    ]
136    self.failUnless(messages[0].GetId() == messages[1].GetId())
137
138    # Both of the above would share a translation.
139    translation = tclib.Translation(id=messages[0].GetId(),
140                                    text='Bonjour USERNAME',
141                                    placeholders=[tclib.Placeholder(
142                                      'USERNAME', '$1', 'Joi')])
143
144    factory = clique.UberClique()
145    cliques = [factory.MakeClique(msg) for msg in messages]
146
147    for clq in cliques:
148      clq.AddTranslation(translation, 'fr')
149
150    self.failUnless(cliques[0].MessageForLanguage('fr').GetRealContent() ==
151                    'Bonjour $1')
152    self.failUnless(cliques[1].MessageForLanguage('fr').GetRealContent() ==
153                    'Bonjour %s')
154
155  def testMissingTranslations(self):
156    messages = [ tclib.Message(text='Hello'), tclib.Message(text='Goodbye') ]
157    factory = clique.UberClique()
158    cliques = [factory.MakeClique(msg) for msg in messages]
159
160    cliques[1].MessageForLanguage('fr', False, True)
161
162    self.failUnless(not factory.HasMissingTranslations())
163
164    cliques[0].MessageForLanguage('de', False, False)
165
166    self.failUnless(factory.HasMissingTranslations())
167
168    report = factory.MissingTranslationsReport()
169    self.failUnless(report.count('WARNING') == 1)
170    self.failUnless(report.count('8053599568341804890 "Goodbye" fr') == 1)
171    self.failUnless(report.count('ERROR') == 1)
172    self.failUnless(report.count('800120468867715734 "Hello" de') == 1)
173
174  def testCustomTypes(self):
175    factory = clique.UberClique()
176    message = tclib.Message(text='Bingo bongo')
177    c = factory.MakeClique(message)
178    try:
179      c.SetCustomType(DummyCustomType())
180      self.fail()
181    except:
182      pass  # expected case - 'Bingo bongo' does not start with 'jjj'
183
184    message = tclib.Message(text='jjjBingo bongo')
185    c = factory.MakeClique(message)
186    c.SetCustomType(util.NewClassInstance(
187      'grit.clique_unittest.DummyCustomType', clique.CustomType))
188    translation = tclib.Translation(id=message.GetId(), text='Bilingo bolongo')
189    c.AddTranslation(translation, 'fr')
190    self.failUnless(c.MessageForLanguage('fr').GetRealContent().startswith('jjj'))
191
192  def testWhitespaceMessagesAreNontranslateable(self):
193    factory = clique.UberClique()
194
195    message = tclib.Message(text=' \t')
196    c = factory.MakeClique(message, translateable=True)
197    self.failIf(c.IsTranslateable())
198
199    message = tclib.Message(text='\n \n ')
200    c = factory.MakeClique(message, translateable=True)
201    self.failIf(c.IsTranslateable())
202
203    message = tclib.Message(text='\n hello')
204    c = factory.MakeClique(message, translateable=True)
205    self.failUnless(c.IsTranslateable())
206
207  def testEachCliqueKeptSorted(self):
208    factory = clique.UberClique()
209    msg_a = tclib.Message(text='hello', description='a')
210    msg_b = tclib.Message(text='hello', description='b')
211    msg_c = tclib.Message(text='hello', description='c')
212    # Insert out of order
213    clique_b = factory.MakeClique(msg_b, translateable=True)
214    clique_a = factory.MakeClique(msg_a, translateable=True)
215    clique_c = factory.MakeClique(msg_c, translateable=True)
216    clique_list = factory.cliques_[clique_a.GetId()]
217    self.failUnless(len(clique_list) == 3)
218    self.failUnless(clique_list[0] == clique_a)
219    self.failUnless(clique_list[1] == clique_b)
220    self.failUnless(clique_list[2] == clique_c)
221
222  def testBestCliqueSortIsStable(self):
223    factory = clique.UberClique()
224    text = 'hello'
225    msg_no_description = tclib.Message(text=text)
226    msg_id_description_a = tclib.Message(text=text, description='ID: a')
227    msg_id_description_b = tclib.Message(text=text, description='ID: b')
228    msg_description_x = tclib.Message(text=text, description='x')
229    msg_description_y = tclib.Message(text=text, description='y')
230    clique_id = msg_no_description.GetId()
231
232    # Insert in an order that tests all outcomes.
233    clique_no_description = factory.MakeClique(msg_no_description,
234                                               translateable=True)
235    self.failUnless(factory.BestClique(clique_id) == clique_no_description)
236    clique_id_description_b = factory.MakeClique(msg_id_description_b,
237                                                 translateable=True)
238    self.failUnless(factory.BestClique(clique_id) == clique_id_description_b)
239    clique_id_description_a = factory.MakeClique(msg_id_description_a,
240                                                 translateable=True)
241    self.failUnless(factory.BestClique(clique_id) == clique_id_description_a)
242    clique_description_y = factory.MakeClique(msg_description_y,
243                                              translateable=True)
244    self.failUnless(factory.BestClique(clique_id) == clique_description_y)
245    clique_description_x = factory.MakeClique(msg_description_x,
246                                              translateable=True)
247    self.failUnless(factory.BestClique(clique_id) == clique_description_x)
248
249
250class DummyCustomType(clique.CustomType):
251  def Validate(self, message):
252    return message.GetRealContent().startswith('jjj')
253  def ValidateAndModify(self, lang, translation):
254    is_ok = self.Validate(translation)
255    self.ModifyEachTextPart(lang, translation)
256  def ModifyTextPart(self, lang, text):
257    return 'jjj%s' % text
258
259
260if __name__ == '__main__':
261  unittest.main()
262