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