~bzr-pqm/bzr/bzr.dev

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