~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_rio.py

(mbp) merge bzr.dev to 0.8, prepare for release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 by Canonical Ltd
 
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
 
 
25
import cStringIO
 
26
import os
 
27
import sys
 
28
from tempfile import TemporaryFile
 
29
 
 
30
from bzrlib.tests import TestCaseInTempDir, TestCase
 
31
from bzrlib.rio import (RioWriter, Stanza, read_stanza, read_stanzas, rio_file,
 
32
                        RioReader)
 
33
 
 
34
 
 
35
class TestRio(TestCase):
 
36
 
 
37
    def test_stanza(self):
 
38
        """Construct rio stanza in memory"""
 
39
        s = Stanza(number='42', name="fred")
 
40
        self.assertTrue('number' in s)
 
41
        self.assertFalse('color' in s)
 
42
        self.assertFalse('42' in s)
 
43
        self.assertEquals(list(s.iter_pairs()),
 
44
                [('name', 'fred'), ('number', '42')])
 
45
        self.assertEquals(s.get('number'), '42')
 
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"""
 
64
        s = Stanza(number='42', name='fred')
 
65
        self.assertEquals(list(s.to_lines()),
 
66
                ['name: fred\n',
 
67
                 'number: 42\n'])
 
68
 
 
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
 
 
75
    def test_to_file(self):
 
76
        """Write rio to file"""
 
77
        tmpf = TemporaryFile()
 
78
        s = Stanza(a_thing='something with "quotes like \\"this\\""', number='42', name='fred')
 
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
 
 
130
    def test_backslash(self):
 
131
        s = Stanza(q='\\')
 
132
        t = s.to_string()
 
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)
 
161
        self.rio_file_stanzas([s])
 
162
 
 
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)
 
177
        # apparent bug in read_stanza
 
178
        # s3 = read_stanza(self.stanzas_to_str([s]))
 
179
        # self.assertEquals(s, s3)
 
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("""\
 
191
version_header: 1
 
192
 
 
193
name: foo
 
194
val: 123
 
195
 
 
196
name: bar
 
197
val: 129319
 
198
""")
 
199
        tmpf.seek(0)
 
200
        reader = read_stanzas(tmpf)
 
201
        read_iter = iter(reader)
 
202
        stuff = list(reader)
 
203
        self.assertEqual(stuff, 
 
204
                [ Stanza(version_header='1'),
 
205
                  Stanza(name="foo", val='123'),
 
206
                  Stanza(name="bar", val='129319'), ])
 
207
 
 
208
    def test_read_several(self):
 
209
        """Read several stanzas from file"""
 
210
        tmpf = TemporaryFile()
 
211
        tmpf.write("""\
 
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
 
224
""")
 
225
        tmpf.seek(0)
 
226
        s = read_stanza(tmpf)
 
227
        self.assertEquals(s, Stanza(version_header='1'))
 
228
        s = read_stanza(tmpf)
 
229
        self.assertEquals(s, Stanza(name="foo", val='123'))
 
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)
 
234
        self.assertEquals(s, Stanza(name="bar", val='129319'))
 
235
        s = read_stanza(tmpf)
 
236
        self.assertEquals(s, None)
 
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)
 
252
 
 
253
    def test_tricky_quoted(self):
 
254
        tmpf = TemporaryFile()
 
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
''')
 
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)
 
303
            self.rio_file_stanzas([stanza])
 
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, [])
 
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, {})
 
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