~bzr-pqm/bzr/bzr.dev

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