~bzr-pqm/bzr/bzr.dev

2052.3.2 by John Arbash Meinel
Change Copyright .. by Canonical to Copyright ... Canonical
1
# Copyright (C) 2005, 2006 Canonical Ltd
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
16
17
"""Tests for rio serialization
18
19
A simple, reproducible structured IO format.
20
21
rio itself works in Unicode strings.  It is typically encoded to UTF-8,
22
but this depends on the transport.
23
"""
24
1553.5.32 by Martin Pool
rio files are always externalized in utf-8. test this.
25
import cStringIO
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
26
import os
1551.12.1 by Aaron Bentley
Basic RIO patch-compatible format is working
27
import re
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
28
import sys
29
from tempfile import TemporaryFile
30
2030.1.5 by John Arbash Meinel
Create a 'read_stanza_unicode' to handle unicode processing
31
from bzrlib import (
32
    rio,
33
    )
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
34
from bzrlib.tests import TestCaseInTempDir, TestCase
1534.10.2 by Aaron Bentley
Implemented rio_file to produce a light file object from stanzas
35
from bzrlib.rio import (RioWriter, Stanza, read_stanza, read_stanzas, rio_file,
36
                        RioReader)
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
37
38
39
class TestRio(TestCase):
40
41
    def test_stanza(self):
42
        """Construct rio stanza in memory"""
1185.47.2 by Martin Pool
Finish rio format and tests.
43
        s = Stanza(number='42', name="fred")
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
44
        self.assertTrue('number' in s)
45
        self.assertFalse('color' in s)
1185.47.2 by Martin Pool
Finish rio format and tests.
46
        self.assertFalse('42' in s)
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
47
        self.assertEquals(list(s.iter_pairs()),
1185.47.2 by Martin Pool
Finish rio format and tests.
48
                [('name', 'fred'), ('number', '42')])
49
        self.assertEquals(s.get('number'), '42')
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
50
        self.assertEquals(s.get('name'), 'fred')
51
52
    def test_value_checks(self):
53
        """rio checks types on construction"""
54
        # these aren't enforced at construction time
55
        ## self.assertRaises(ValueError,
56
        ##        Stanza, complex=42 + 3j)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
57
        ## self.assertRaises(ValueError,
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
58
        ##        Stanza, several=range(10))
59
60
    def test_empty_value(self):
61
        """Serialize stanza with empty field"""
62
        s = Stanza(empty='')
63
        self.assertEqualDiff(s.to_string(),
64
                "empty: \n")
65
66
    def test_to_lines(self):
67
        """Write simple rio stanza to string"""
1185.47.2 by Martin Pool
Finish rio format and tests.
68
        s = Stanza(number='42', name='fred')
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
69
        self.assertEquals(list(s.to_lines()),
70
                ['name: fred\n',
71
                 'number: 42\n'])
72
1553.5.8 by Martin Pool
New Rio.as_dict method
73
    def test_as_dict(self):
74
        """Convert rio Stanza to dictionary"""
75
        s = Stanza(number='42', name='fred')
76
        sd = s.as_dict()
77
        self.assertEquals(sd, dict(number='42', name='fred'))
78
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
79
    def test_to_file(self):
80
        """Write rio to file"""
81
        tmpf = TemporaryFile()
1185.47.2 by Martin Pool
Finish rio format and tests.
82
        s = Stanza(a_thing='something with "quotes like \\"this\\""', number='42', name='fred')
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
83
        s.write(tmpf)
84
        tmpf.seek(0)
85
        self.assertEqualDiff(tmpf.read(), r'''
86
a_thing: something with "quotes like \"this\""
87
name: fred
88
number: 42
89
'''[1:])
90
91
    def test_multiline_string(self):
92
        tmpf = TemporaryFile()
93
        s = Stanza(motto="war is peace\nfreedom is slavery\nignorance is strength")
94
        s.write(tmpf)
95
        tmpf.seek(0)
96
        self.assertEqualDiff(tmpf.read(), '''\
97
motto: war is peace
98
\tfreedom is slavery
99
\tignorance is strength
100
''')
101
        tmpf.seek(0)
102
        s2 = read_stanza(tmpf)
103
        self.assertEquals(s, s2)
104
105
    def test_read_stanza(self):
106
        """Load stanza from string"""
107
        lines = """\
108
revision: mbp@sourcefrog.net-123-abc
109
timestamp: 1130653962
110
timezone: 36000
111
committer: Martin Pool <mbp@test.sourcefrog.net>
112
""".splitlines(True)
113
        s = read_stanza(lines)
114
        self.assertTrue('revision' in s)
115
        self.assertEqualDiff(s.get('revision'), 'mbp@sourcefrog.net-123-abc')
116
        self.assertEquals(list(s.iter_pairs()),
117
                [('revision', 'mbp@sourcefrog.net-123-abc'),
118
                 ('timestamp', '1130653962'),
119
                 ('timezone', '36000'),
120
                 ('committer', "Martin Pool <mbp@test.sourcefrog.net>")])
121
        self.assertEquals(len(s), 4)
122
123
    def test_repeated_field(self):
124
        """Repeated field in rio"""
125
        s = Stanza()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
126
        for k, v in [('a', '10'), ('b', '20'), ('a', '100'), ('b', '200'),
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
127
                     ('a', '1000'), ('b', '2000')]:
128
            s.add(k, v)
129
        s2 = read_stanza(s.to_lines())
130
        self.assertEquals(s, s2)
131
        self.assertEquals(s.get_all('a'), map(str, [10, 100, 1000]))
132
        self.assertEquals(s.get_all('b'), map(str, [20, 200, 2000]))
133
1185.47.2 by Martin Pool
Finish rio format and tests.
134
    def test_backslash(self):
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
135
        s = Stanza(q='\\')
136
        t = s.to_string()
1185.47.2 by Martin Pool
Finish rio format and tests.
137
        self.assertEqualDiff(t, 'q: \\\n')
138
        s2 = read_stanza(s.to_lines())
139
        self.assertEquals(s, s2)
140
141
    def test_blank_line(self):
142
        s = Stanza(none='', one='\n', two='\n\n')
143
        self.assertEqualDiff(s.to_string(), """\
3943.8.2 by Marius Kruger
fix tests relying on trailing whitespace by replacing it with \x20.
144
none:\x20
145
one:\x20
1185.47.2 by Martin Pool
Finish rio format and tests.
146
\t
3943.8.2 by Marius Kruger
fix tests relying on trailing whitespace by replacing it with \x20.
147
two:\x20
1185.47.2 by Martin Pool
Finish rio format and tests.
148
\t
149
\t
150
""")
151
        s2 = read_stanza(s.to_lines())
152
        self.assertEquals(s, s2)
153
154
    def test_whitespace_value(self):
155
        s = Stanza(space=' ', tabs='\t\t\t', combo='\n\t\t\n')
156
        self.assertEqualDiff(s.to_string(), """\
3943.8.2 by Marius Kruger
fix tests relying on trailing whitespace by replacing it with \x20.
157
combo:\x20
1185.47.2 by Martin Pool
Finish rio format and tests.
158
\t\t\t
159
\t
3943.8.2 by Marius Kruger
fix tests relying on trailing whitespace by replacing it with \x20.
160
space:\x20\x20
1185.47.2 by Martin Pool
Finish rio format and tests.
161
tabs: \t\t\t
162
""")
163
        s2 = read_stanza(s.to_lines())
164
        self.assertEquals(s, s2)
1534.10.2 by Aaron Bentley
Implemented rio_file to produce a light file object from stanzas
165
        self.rio_file_stanzas([s])
1185.47.2 by Martin Pool
Finish rio format and tests.
166
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
167
    def test_quoted(self):
168
        """rio quoted string cases"""
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
169
        s = Stanza(q1='"hello"',
170
                   q2=' "for',
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
171
                   q3='\n\n"for"\n',
172
                   q4='for\n"\nfor',
173
                   q5='\n',
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
174
                   q6='"',
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
175
                   q7='""',
176
                   q8='\\',
177
                   q9='\\"\\"',
178
                   )
179
        s2 = read_stanza(s.to_lines())
180
        self.assertEquals(s, s2)
1534.10.2 by Aaron Bentley
Implemented rio_file to produce a light file object from stanzas
181
        # apparent bug in read_stanza
182
        # s3 = read_stanza(self.stanzas_to_str([s]))
183
        # self.assertEquals(s, s3)
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
184
185
    def test_read_empty(self):
186
        """Detect end of rio file"""
187
        s = read_stanza([])
188
        self.assertEqual(s, None)
189
        self.assertTrue(s is None)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
190
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
191
    def test_read_iter(self):
192
        """Read several stanzas from file"""
193
        tmpf = TemporaryFile()
194
        tmpf.write("""\
1185.47.2 by Martin Pool
Finish rio format and tests.
195
version_header: 1
196
197
name: foo
198
val: 123
199
200
name: bar
201
val: 129319
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
202
""")
203
        tmpf.seek(0)
204
        reader = read_stanzas(tmpf)
205
        read_iter = iter(reader)
206
        stuff = list(reader)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
207
        self.assertEqual(stuff,
1185.47.2 by Martin Pool
Finish rio format and tests.
208
                [ Stanza(version_header='1'),
209
                  Stanza(name="foo", val='123'),
210
                  Stanza(name="bar", val='129319'), ])
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
211
212
    def test_read_several(self):
213
        """Read several stanzas from file"""
214
        tmpf = TemporaryFile()
215
        tmpf.write("""\
1185.47.2 by Martin Pool
Finish rio format and tests.
216
version_header: 1
217
218
name: foo
219
val: 123
220
221
name: quoted
222
address:   "Willowglen"
223
\t  42 Wallaby Way
224
\t  Sydney
225
226
name: bar
227
val: 129319
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
228
""")
229
        tmpf.seek(0)
230
        s = read_stanza(tmpf)
1185.47.2 by Martin Pool
Finish rio format and tests.
231
        self.assertEquals(s, Stanza(version_header='1'))
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
232
        s = read_stanza(tmpf)
1185.47.2 by Martin Pool
Finish rio format and tests.
233
        self.assertEquals(s, Stanza(name="foo", val='123'))
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
234
        s = read_stanza(tmpf)
235
        self.assertEqualDiff(s.get('name'), 'quoted')
236
        self.assertEqualDiff(s.get('address'), '  "Willowglen"\n  42 Wallaby Way\n  Sydney')
237
        s = read_stanza(tmpf)
1185.47.2 by Martin Pool
Finish rio format and tests.
238
        self.assertEquals(s, Stanza(name="bar", val='129319'))
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
239
        s = read_stanza(tmpf)
240
        self.assertEquals(s, None)
1534.10.2 by Aaron Bentley
Implemented rio_file to produce a light file object from stanzas
241
        self.check_rio_file(tmpf)
242
243
    def check_rio_file(self, real_file):
244
        real_file.seek(0)
245
        read_write = rio_file(RioReader(real_file)).read()
246
        real_file.seek(0)
247
        self.assertEquals(read_write, real_file.read())
248
249
    @staticmethod
250
    def stanzas_to_str(stanzas):
251
        return rio_file(stanzas).read()
252
253
    def rio_file_stanzas(self, stanzas):
254
        new_stanzas = list(RioReader(rio_file(stanzas)))
255
        self.assertEqual(new_stanzas, stanzas)
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
256
257
    def test_tricky_quoted(self):
258
        tmpf = TemporaryFile()
1185.47.2 by Martin Pool
Finish rio format and tests.
259
        tmpf.write('''\
260
s: "one"
261
3943.8.2 by Marius Kruger
fix tests relying on trailing whitespace by replacing it with \x20.
262
s:\x20
1185.47.2 by Martin Pool
Finish rio format and tests.
263
\t"one"
264
\t
265
266
s: "
267
268
s: ""
269
270
s: """
271
3943.8.2 by Marius Kruger
fix tests relying on trailing whitespace by replacing it with \x20.
272
s:\x20
1185.47.2 by Martin Pool
Finish rio format and tests.
273
\t
274
275
s: \\
276
3943.8.2 by Marius Kruger
fix tests relying on trailing whitespace by replacing it with \x20.
277
s:\x20
1185.47.2 by Martin Pool
Finish rio format and tests.
278
\t\\
279
\t\\\\
280
\t
281
282
s: word\\
283
284
s: quote"
285
286
s: backslashes\\\\\\
287
288
s: both\\\"
289
290
''')
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
291
        tmpf.seek(0)
292
        expected_vals = ['"one"',
293
            '\n"one"\n',
294
            '"',
295
            '""',
296
            '"""',
297
            '\n',
298
            '\\',
299
            '\n\\\n\\\\\n',
300
            'word\\',
301
            'quote\"',
302
            'backslashes\\\\\\',
303
            'both\\\"',
304
            ]
305
        for expected in expected_vals:
306
            stanza = read_stanza(tmpf)
1534.10.2 by Aaron Bentley
Implemented rio_file to produce a light file object from stanzas
307
            self.rio_file_stanzas([stanza])
1185.47.1 by Martin Pool
[broken] start converting basic_io to more rfc822-like format
308
            self.assertEquals(len(stanza), 1)
309
            self.assertEqualDiff(stanza.get('s'), expected)
310
311
    def test_write_empty_stanza(self):
312
        """Write empty stanza"""
313
        l = list(Stanza().to_lines())
314
        self.assertEquals(l, [])
1553.5.7 by Martin Pool
rio.Stanza.add should raise TypeError on invalid types.
315
316
    def test_rio_raises_type_error(self):
317
        """TypeError on adding invalid type to Stanza"""
318
        s = Stanza()
319
        self.assertRaises(TypeError, s.add, 'foo', {})
320
321
    def test_rio_raises_type_error_key(self):
322
        """TypeError on adding invalid type to Stanza"""
323
        s = Stanza()
324
        self.assertRaises(TypeError, s.add, 10, {})
1553.5.32 by Martin Pool
rio files are always externalized in utf-8. test this.
325
326
    def test_rio_unicode(self):
327
        uni_data = u'\N{KATAKANA LETTER O}'
328
        s = Stanza(foo=uni_data)
329
        self.assertEquals(s.get('foo'), uni_data)
330
        raw_lines = s.to_lines()
331
        self.assertEquals(raw_lines,
332
                ['foo: ' + uni_data.encode('utf-8') + '\n'])
333
        new_s = read_stanza(raw_lines)
334
        self.assertEquals(new_s.get('foo'), uni_data)
335
2030.1.1 by John Arbash Meinel
Make it easier to nest Stanzas with Unicode contents
336
    def test_rio_to_unicode(self):
337
        uni_data = u'\N{KATAKANA LETTER O}'
338
        s = Stanza(foo=uni_data)
2030.1.5 by John Arbash Meinel
Create a 'read_stanza_unicode' to handle unicode processing
339
        unicode_str = s.to_unicode()
340
        self.assertEqual(u'foo: %s\n' % (uni_data,), unicode_str)
341
        new_s = rio.read_stanza_unicode(unicode_str.splitlines(True))
342
        self.assertEqual(uni_data, new_s.get('foo'))
2030.1.1 by John Arbash Meinel
Make it easier to nest Stanzas with Unicode contents
343
344
    def test_nested_rio_unicode(self):
345
        uni_data = u'\N{KATAKANA LETTER O}'
346
        s = Stanza(foo=uni_data)
347
        parent_stanza = Stanza(child=s.to_unicode())
348
        raw_lines = parent_stanza.to_lines()
349
        self.assertEqual(['child: foo: ' + uni_data.encode('utf-8') + '\n',
350
                          '\t\n',
351
                         ], raw_lines)
352
        new_parent = read_stanza(raw_lines)
353
        child_text = new_parent.get('child')
354
        self.assertEqual(u'foo: %s\n' % uni_data, child_text)
2030.1.5 by John Arbash Meinel
Create a 'read_stanza_unicode' to handle unicode processing
355
        new_child = rio.read_stanza_unicode(child_text.splitlines(True))
2030.1.1 by John Arbash Meinel
Make it easier to nest Stanzas with Unicode contents
356
        self.assertEqual(uni_data, new_child.get('foo'))
1551.12.1 by Aaron Bentley
Basic RIO patch-compatible format is working
357
1551.12.22 by Aaron Bentley
Fix handling of whitespace-stripping without newline munging
358
    def mail_munge(self, lines, dos_nl=True):
1551.12.1 by Aaron Bentley
Basic RIO patch-compatible format is working
359
        new_lines = []
360
        for line in lines:
1551.12.22 by Aaron Bentley
Fix handling of whitespace-stripping without newline munging
361
            line = re.sub(' *\n', '\n', line)
362
            if dos_nl:
363
                line = re.sub('([^\r])\n', '\\1\r\n', line)
1551.12.1 by Aaron Bentley
Basic RIO patch-compatible format is working
364
            new_lines.append(line)
365
        return new_lines
366
367
    def test_patch_rio(self):
1551.12.21 by Aaron Bentley
Patch-RIO does line breaks in slightly more readable places
368
        stanza = Stanza(data='#\n\r\\r ', space=' ' * 255, hash='#' * 255)
1551.12.1 by Aaron Bentley
Basic RIO patch-compatible format is working
369
        lines = rio.to_patch_lines(stanza)
370
        for line in lines:
371
            self.assertContainsRe(line, '^# ')
1551.12.10 by Aaron Bentley
Reduce max width to 72
372
            self.assertTrue(72 >= len(line))
373
        for line in rio.to_patch_lines(stanza, max_width=12):
374
            self.assertTrue(12 >= len(line))
1551.12.22 by Aaron Bentley
Fix handling of whitespace-stripping without newline munging
375
        new_stanza = rio.read_patch_stanza(self.mail_munge(lines,
376
                                                           dos_nl=False))
1551.12.1 by Aaron Bentley
Basic RIO patch-compatible format is working
377
        lines = self.mail_munge(lines)
378
        new_stanza = rio.read_patch_stanza(lines)
379
        self.assertEqual('#\n\r\\r ', new_stanza.get('data'))
380
        self.assertEqual(' '* 255, new_stanza.get('space'))
1551.12.21 by Aaron Bentley
Patch-RIO does line breaks in slightly more readable places
381
        self.assertEqual('#'* 255, new_stanza.get('hash'))
382
383
    def test_patch_rio_linebreaks(self):
384
        stanza = Stanza(breaktest='linebreak -/'*30)
385
        self.assertContainsRe(rio.to_patch_lines(stanza, 71)[0],
386
                              'linebreak\\\\\n')
387
        stanza = Stanza(breaktest='linebreak-/'*30)
388
        self.assertContainsRe(rio.to_patch_lines(stanza, 70)[0],
389
                              'linebreak-\\\\\n')
390
        stanza = Stanza(breaktest='linebreak/'*30)
391
        self.assertContainsRe(rio.to_patch_lines(stanza, 70)[0],
392
                              'linebreak\\\\\n')