~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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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)
57
        ## self.assertRaises(ValueError, 
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()
126
        for k, v in [('a', '10'), ('b', '20'), ('a', '100'), ('b', '200'), 
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(), """\
144
none: 
145
one: 
146
\t
147
two: 
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(), """\
157
combo: 
158
\t\t\t
159
\t
160
space:  
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"""
169
        s = Stanza(q1='"hello"', 
170
                   q2=' "for', 
171
                   q3='\n\n"for"\n',
172
                   q4='for\n"\nfor',
173
                   q5='\n',
174
                   q6='"', 
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)
190
        
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)
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
262
s: 
263
\t"one"
264
\t
265
266
s: "
267
268
s: ""
269
270
s: """
271
272
s: 
273
\t
274
275
s: \\
276
277
s: 
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')