~bzr-pqm/bzr/bzr.dev

5830.2.18 by INADA Naoki
Add some tests for export_pot module.
1
# Copyright (C) 2011 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
17
from cStringIO import StringIO
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
18
import textwrap
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
19
20
from bzrlib import (
5875.3.20 by INADA Naoki
Add test for exporting command help.
21
    commands,
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
22
    export_pot,
6282.2.3 by Martin Packman
Export registry help to pot for unhidden option value switches
23
    option,
24
    registry,
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
25
    tests,
26
    )
27
5875.3.20 by INADA Naoki
Add test for exporting command help.
28
import re
29
30
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
31
class TestEscape(tests.TestCase):
32
33
    def test_simple_escape(self):
34
        self.assertEqual(
35
                export_pot._escape('foobar'),
36
                'foobar')
37
38
        s = '''foo\nbar\r\tbaz\\"spam"'''
39
        e = '''foo\\nbar\\r\\tbaz\\\\\\"spam\\"'''
40
        self.assertEqual(export_pot._escape(s), e)
41
42
    def test_complex_escape(self):
43
        s = '''\\r \\\n'''
44
        e = '''\\\\r \\\\\\n'''
45
        self.assertEqual(export_pot._escape(s), e)
46
47
48
class TestNormalize(tests.TestCase):
49
50
    def test_single_line(self):
51
        s = 'foobar'
52
        e = '"foobar"'
53
        self.assertEqual(export_pot._normalize(s), e)
54
55
        s = 'foo"bar'
56
        e = '"foo\\"bar"'
57
        self.assertEqual(export_pot._normalize(s), e)
58
59
    def test_multi_lines(self):
60
        s = 'foo\nbar\n'
61
        e = '""\n"foo\\n"\n"bar\\n"'
62
        self.assertEqual(export_pot._normalize(s), e)
63
64
        s = '\nfoo\nbar\n'
65
        e = ('""\n'
66
             '"\\n"\n'
67
             '"foo\\n"\n'
68
             '"bar\\n"')
69
        self.assertEqual(export_pot._normalize(s), e)
70
71
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
72
class TestParseSource(tests.TestCase):
73
    """Check mappings to line numbers generated from python source"""
74
75
    def test_classes(self):
76
        src = '''
77
class Ancient:
78
    """Old style class"""
79
80
class Modern(object):
81
    """New style class"""
82
'''
83
        cls_lines, _ = export_pot._parse_source(src)
84
        self.assertEqual(cls_lines,
85
            {"Ancient": 2, "Modern": 5})
86
87
    def test_classes_nested(self):
88
        src = '''
89
class Matroska(object):
90
    class Smaller(object):
91
        class Smallest(object):
92
            pass
93
'''
94
        cls_lines, _ = export_pot._parse_source(src)
95
        self.assertEqual(cls_lines,
96
            {"Matroska": 2, "Smaller": 3, "Smallest":4})
97
98
    def test_strings_docstrings(self):
99
        src = '''\
100
"""Module"""
101
102
def function():
103
    """Function"""
104
105
class Class(object):
106
    """Class"""
107
108
    def method(self):
109
        """Method"""
110
'''
111
        _, str_lines = export_pot._parse_source(src)
112
        self.assertEqual(str_lines,
113
            {"Module": 1, "Function": 4, "Class": 7, "Method": 10})
114
115
    def test_strings_literals(self):
116
        src = '''\
117
s = "One"
118
t = (2, "Two")
119
f = dict(key="Three")
120
'''
121
        _, str_lines = export_pot._parse_source(src)
122
        self.assertEqual(str_lines,
123
            {"One": 1, "Two": 2, "Three": 3})
124
125
    def test_strings_multiline(self):
126
        src = '''\
127
"""Start
128
129
End
130
"""
131
t = (
132
    "A"
133
    "B"
134
    "C"
135
    )
136
'''
137
        _, str_lines = export_pot._parse_source(src)
138
        self.assertEqual(str_lines,
139
            {"Start\n\nEnd\n": 1, "ABC": 6})
140
141
    def test_strings_multiline_escapes(self):
142
        src = '''\
143
s = "Escaped\\n"
144
r = r"Raw\\n"
145
t = (
146
    "A\\n\\n"
147
    "B\\n\\n"
148
    "C\\n\\n"
149
    )
150
'''
151
        _, str_lines = export_pot._parse_source(src)
152
        self.expectFailure("Escaped newlines confuses the multiline handling",
153
            self.assertNotEqual, str_lines,
154
            {"Escaped\n": 0, "Raw\\n": 2, "A\n\nB\n\nC\n\n": -2})
155
        self.assertEqual(str_lines,
156
            {"Escaped\n": 1, "Raw\\n": 2, "A\n\nB\n\nC\n\n": 4})
157
158
159
class TestModuleContext(tests.TestCase):
160
    """Checks for source context tracking objects"""
161
162
    def check_context(self, context, path, lineno):
163
        self.assertEquals((context.path, context.lineno), (path, lineno))
164
165
    def test___init__(self):
166
        context = export_pot._ModuleContext("one.py")
167
        self.check_context(context, "one.py", 1)
168
        context = export_pot._ModuleContext("two.py", 5)
169
        self.check_context(context, "two.py", 5)
170
171
    def test_from_class(self):
172
        """New context returned with lineno updated from class"""
173
        path = "cls.py"
174
        class A(object): pass
175
        class B(object): pass
176
        cls_lines = {"A": 5, "B": 7}
177
        context = export_pot._ModuleContext(path, _source_info=(cls_lines, {}))
178
        contextA = context.from_class(A)
179
        self.check_context(contextA, path, 5)
180
        contextB1 = context.from_class(B)
181
        self.check_context(contextB1, path, 7)
182
        contextB2 = contextA.from_class(B)
183
        self.check_context(contextB2, path, 7)
184
        self.check_context(context, path, 1)
185
        self.assertEquals("", self.get_log())
186
187
    def test_from_class_missing(self):
188
        """When class has no lineno the old context details are returned"""
189
        path = "cls_missing.py"
190
        class A(object): pass
191
        class M(object): pass
192
        context = export_pot._ModuleContext(path, 3, ({"A": 15}, {}))
193
        contextA = context.from_class(A)
194
        contextM1 = context.from_class(M)
195
        self.check_context(contextM1, path, 3)
196
        contextM2 = contextA.from_class(M)
197
        self.check_context(contextM2, path, 15)
198
        self.assertContainsRe(self.get_log(), "Definition of <.*M'> not found")
199
200
    def test_from_string(self):
201
        """New context returned with lineno updated from string"""
202
        path = "str.py"
203
        str_lines = {"one": 14, "two": 42}
204
        context = export_pot._ModuleContext(path, _source_info=({}, str_lines))
205
        context1 = context.from_string("one")
206
        self.check_context(context1, path, 14)
207
        context2A = context.from_string("two")
208
        self.check_context(context2A, path, 42)
209
        context2B = context1.from_string("two")
210
        self.check_context(context2B, path, 42)
211
        self.check_context(context, path, 1)
212
        self.assertEquals("", self.get_log())
213
214
    def test_from_string_missing(self):
215
        """When string has no lineno the old context details are returned"""
216
        path = "str_missing.py"
217
        context = export_pot._ModuleContext(path, 4, ({}, {"line\n": 21}))
218
        context1 = context.from_string("line\n")
219
        context2A = context.from_string("not there")
220
        self.check_context(context2A, path, 4)
221
        context2B = context1.from_string("not there")
222
        self.check_context(context2B, path, 21)
223
        self.assertContainsRe(self.get_log(), "String 'not there' not found")
224
225
6282.2.3 by Martin Packman
Export registry help to pot for unhidden option value switches
226
class TestWriteOption(tests.TestCase):
227
    """Tests for writing texts extracted from options in pot format"""
228
229
    def pot_from_option(self, opt, context=None, note="test"):
230
        sio = StringIO()
231
        exporter = export_pot._PotExporter(sio)
232
        if context is None:
233
            context = export_pot._ModuleContext("nowhere", 0)
234
        export_pot._write_option(exporter, context, opt, note)
235
        return sio.getvalue()
236
237
    def test_option_without_help(self):
238
        opt = option.Option("helpless")
239
        self.assertEqual("", self.pot_from_option(opt))
240
241
    def test_option_with_help(self):
242
        opt = option.Option("helpful", help="Info.")
243
        self.assertContainsString(self.pot_from_option(opt), "\n"
244
            "# help of 'helpful' test\n"
245
            "msgid \"Info.\"\n")
246
247
    def test_option_hidden(self):
248
        opt = option.Option("hidden", help="Unseen.", hidden=True)
249
        self.assertEqual("", self.pot_from_option(opt))
250
251
    def test_option_context_missing(self):
252
        context = export_pot._ModuleContext("remote.py", 3)
253
        opt = option.Option("metaphor", help="Not a literal in the source.")
254
        self.assertContainsString(self.pot_from_option(opt, context),
255
            "#: remote.py:3\n"
256
            "# help of 'metaphor' test\n")
257
258
    def test_option_context_string(self):
259
        s = "Literally."
260
        context = export_pot._ModuleContext("local.py", 3, ({}, {s: 17}))
261
        opt = option.Option("example", help=s)
262
        self.assertContainsString(self.pot_from_option(opt, context),
263
            "#: local.py:17\n"
264
            "# help of 'example' test\n")
265
266
    def test_registry_option_title(self):
267
        opt = option.RegistryOption.from_kwargs("group", help="Pick one.",
268
            title="Choose!")
269
        pot = self.pot_from_option(opt)
270
        self.assertContainsString(pot, "\n"
271
            "# title of 'group' test\n"
272
            "msgid \"Choose!\"\n")
273
        self.assertContainsString(pot, "\n"
274
            "# help of 'group' test\n"
275
            "msgid \"Pick one.\"\n")
276
277
    def test_registry_option_title_context_missing(self):
278
        context = export_pot._ModuleContext("theory.py", 3)
279
        opt = option.RegistryOption.from_kwargs("abstract", title="Unfounded!")
280
        self.assertContainsString(self.pot_from_option(opt, context),
281
            "#: theory.py:3\n"
282
            "# title of 'abstract' test\n")
283
284
    def test_registry_option_title_context_string(self):
285
        s = "Grounded!"
286
        context = export_pot._ModuleContext("practice.py", 3, ({}, {s: 144}))
287
        opt = option.RegistryOption.from_kwargs("concrete", title=s)
288
        self.assertContainsString(self.pot_from_option(opt, context),
289
            "#: practice.py:144\n"
290
            "# title of 'concrete' test\n")
291
292
    def test_registry_option_value_switches(self):
293
        opt = option.RegistryOption.from_kwargs("switch", help="Flip one.",
294
            value_switches=True, enum_switch=False,
295
            red="Big.", green="Small.")
296
        pot = self.pot_from_option(opt)
297
        self.assertContainsString(pot, "\n"
298
            "# help of 'switch' test\n"
299
            "msgid \"Flip one.\"\n")
300
        self.assertContainsString(pot, "\n"
301
            "# help of 'switch=red' test\n"
302
            "msgid \"Big.\"\n")
303
        self.assertContainsString(pot, "\n"
304
            "# help of 'switch=green' test\n"
305
            "msgid \"Small.\"\n")
306
307
    def test_registry_option_value_switches_hidden(self):
308
        reg = registry.Registry()
309
        class Hider(object):
310
            hidden = True
311
        reg.register("new", 1, "Current.")
312
        reg.register("old", 0, "Legacy.", info=Hider())
313
        opt = option.RegistryOption("protocol", "Talking.", reg,
314
            value_switches=True, enum_switch=False)
315
        pot = self.pot_from_option(opt)
316
        self.assertContainsString(pot, "\n"
317
            "# help of 'protocol' test\n"
318
            "msgid \"Talking.\"\n")
319
        self.assertContainsString(pot, "\n"
320
            "# help of 'protocol=new' test\n"
321
            "msgid \"Current.\"\n")
322
        self.assertNotContainsString(pot, "'protocol=old'")
323
324
6351.2.1 by Martin Packman
Add export-pot --include-duplicates option for permitting multiple entries with the same msgid
325
class TestPotExporter(tests.TestCase):
326
    """Test for logic specific to the _PotExporter class"""
327
328
    # This test duplicates test_duplicates below
329
    def test_duplicates(self):
330
        exporter = export_pot._PotExporter(StringIO())
331
        context = export_pot._ModuleContext("mod.py", 1)
332
        exporter.poentry_in_context(context, "Common line.")
333
        context.lineno = 3
334
        exporter.poentry_in_context(context, "Common line.")
335
        self.assertEqual(1, exporter.outf.getvalue().count("Common line."))
336
    
337
    def test_duplicates_included(self):
338
        exporter = export_pot._PotExporter(StringIO(), True)
339
        context = export_pot._ModuleContext("mod.py", 1)
340
        exporter.poentry_in_context(context, "Common line.")
341
        context.lineno = 3
342
        exporter.poentry_in_context(context, "Common line.")
343
        self.assertEqual(2, exporter.outf.getvalue().count("Common line."))
344
345
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
346
class PoEntryTestCase(tests.TestCase):
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
347
348
    def setUp(self):
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
349
        super(PoEntryTestCase, self).setUp()
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
350
        self.exporter = export_pot._PotExporter(StringIO())
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
351
352
    def check_output(self, expected):
353
        self.assertEqual(
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
354
                self.exporter.outf.getvalue(),
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
355
                textwrap.dedent(expected)
356
                )
357
6282.2.2 by Martin Packman
Add export_pot._ModuleContext class for more structured source location tracking, and share option writing logic
358
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
359
class TestPoEntry(PoEntryTestCase):
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
360
361
    def test_simple(self):
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
362
        self.exporter.poentry('dummy', 1, "spam")
363
        self.exporter.poentry('dummy', 2, "ham", 'EGG')
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
364
        self.check_output('''\
365
                #: dummy:1
366
                msgid "spam"
367
                msgstr ""
368
369
                #: dummy:2
370
                # EGG
371
                msgid "ham"
372
                msgstr ""
373
374
                ''')
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
375
376
    def test_duplicate(self):
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
377
        self.exporter.poentry('dummy', 1, "spam")
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
378
        # This should be ignored.
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
379
        self.exporter.poentry('dummy', 2, "spam", 'EGG')
5830.2.18 by INADA Naoki
Add some tests for export_pot module.
380
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
381
        self.check_output('''\
382
                #: dummy:1
383
                msgid "spam"
384
                msgstr ""\n
385
                ''')
386
387
388
class TestPoentryPerPergraph(PoEntryTestCase):
389
390
    def test_single(self):
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
391
        self.exporter.poentry_per_paragraph(
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
392
                'dummy',
393
                10,
394
                '''foo\nbar\nbaz\n'''
395
                )
396
        self.check_output('''\
397
                #: dummy:10
398
                msgid ""
399
                "foo\\n"
400
                "bar\\n"
401
                "baz\\n"
402
                msgstr ""\n
403
                ''')
404
405
    def test_multi(self):
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
406
        self.exporter.poentry_per_paragraph(
5830.2.20 by INADA Naoki
Add test for _poentry_per_peragraph()
407
                'dummy',
408
                10,
409
                '''spam\nham\negg\n\nSPAM\nHAM\nEGG\n'''
410
                )
411
        self.check_output('''\
412
                #: dummy:10
413
                msgid ""
414
                "spam\\n"
415
                "ham\\n"
416
                "egg"
417
                msgstr ""
418
419
                #: dummy:14
420
                msgid ""
421
                "SPAM\\n"
422
                "HAM\\n"
423
                "EGG\\n"
424
                msgstr ""\n
425
                ''')
5875.3.20 by INADA Naoki
Add test for exporting command help.
426
427
428
class TestExportCommandHelp(PoEntryTestCase):
429
430
    def test_command_help(self):
431
432
        class cmd_Demo(commands.Command):
433
            __doc__ = """A sample command.
434
435
            :Usage:
436
                bzr demo
437
438
            :Examples:
439
                Example 1::
440
441
                    cmd arg1
442
443
            Blah Blah Blah
444
            """
445
6282.2.1 by Martin Packman
Add export_pot._PotExporter class to avoid module global, and other minor cleanups
446
        export_pot._write_command_help(self.exporter, cmd_Demo())
447
        result = self.exporter.outf.getvalue()
5993.1.2 by Vincent Ladeuil
Fix python2.6 compatibility.
448
        # We don't care about filename and lineno here.
449
        result = re.sub(r'(?m)^#: [^\n]+\n', '', result)
5875.3.20 by INADA Naoki
Add test for exporting command help.
450
451
        self.assertEqualDiff(
452
                'msgid "A sample command."\n'
453
                'msgstr ""\n'
454
                '\n'                # :Usage: should not be translated.
455
                'msgid ""\n'
456
                '":Examples:\\n"\n'
457
                '"    Example 1::"\n'
458
                'msgstr ""\n'
459
                '\n'
460
                'msgid "        cmd arg1"\n'
461
                'msgstr ""\n'
462
                '\n'
463
                'msgid "Blah Blah Blah"\n'
464
                'msgstr ""\n'
465
                '\n',
466
                result
467
                )