1
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011, 2016 Canonical Ltd
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for rio serialization
19
A simple, reproducible structured IO format.
21
rio itself works in Unicode strings. It is typically encoded to UTF-8,
22
but this depends on the transport.
26
from tempfile import TemporaryFile
31
from breezy.tests import TestCase
32
from breezy.rio import (
41
class TestRio(TestCase):
43
def test_stanza(self):
44
"""Construct rio stanza in memory"""
45
s = Stanza(number='42', name="fred")
46
self.assertTrue('number' in s)
47
self.assertFalse('color' in s)
48
self.assertFalse('42' in s)
49
self.assertEqual(list(s.iter_pairs()),
50
[('name', 'fred'), ('number', '42')])
51
self.assertEqual(s.get('number'), '42')
52
self.assertEqual(s.get('name'), 'fred')
54
def test_empty_value(self):
55
"""Serialize stanza with empty field"""
57
self.assertEquals(s.to_string(),
60
def test_to_lines(self):
61
"""Write simple rio stanza to string"""
62
s = Stanza(number='42', name='fred')
63
self.assertEqual(list(s.to_lines()),
67
def test_as_dict(self):
68
"""Convert rio Stanza to dictionary"""
69
s = Stanza(number='42', name='fred')
71
self.assertEqual(sd, dict(number='42', name='fred'))
73
def test_to_file(self):
74
"""Write rio to file"""
75
tmpf = TemporaryFile()
76
s = Stanza(a_thing='something with "quotes like \\"this\\""', number='42', name='fred')
79
self.assertEqual(tmpf.read(), b'''\
80
a_thing: something with "quotes like \\"this\\""
85
def test_multiline_string(self):
86
tmpf = TemporaryFile()
87
s = Stanza(motto="war is peace\nfreedom is slavery\nignorance is strength")
90
self.assertEqual(tmpf.read(), b'''\
93
\tignorance is strength
96
s2 = read_stanza(tmpf)
97
self.assertEqual(s, s2)
99
def test_read_stanza(self):
100
"""Load stanza from string"""
102
revision: mbp@sourcefrog.net-123-abc
103
timestamp: 1130653962
105
committer: Martin Pool <mbp@test.sourcefrog.net>
107
s = read_stanza(lines)
108
self.assertTrue('revision' in s)
109
self.assertEqual(s.get('revision'), 'mbp@sourcefrog.net-123-abc')
110
self.assertEqual(list(s.iter_pairs()),
111
[('revision', 'mbp@sourcefrog.net-123-abc'),
112
('timestamp', '1130653962'),
113
('timezone', '36000'),
114
('committer', "Martin Pool <mbp@test.sourcefrog.net>")])
115
self.assertEqual(len(s), 4)
117
def test_repeated_field(self):
118
"""Repeated field in rio"""
120
for k, v in [('a', '10'), ('b', '20'), ('a', '100'), ('b', '200'),
121
('a', '1000'), ('b', '2000')]:
123
s2 = read_stanza(s.to_lines())
124
self.assertEqual(s, s2)
125
self.assertEqual(s.get_all('a'), ['10', '100', '1000'])
126
self.assertEqual(s.get_all('b'), ['20', '200', '2000'])
128
def test_backslash(self):
131
self.assertEqual(t, b'q: \\\n')
132
s2 = read_stanza(s.to_lines())
133
self.assertEqual(s, s2)
135
def test_blank_line(self):
136
s = Stanza(none='', one='\n', two='\n\n')
137
self.assertEqual(s.to_string(), b"""\
145
s2 = read_stanza(s.to_lines())
146
self.assertEqual(s, s2)
148
def test_whitespace_value(self):
149
s = Stanza(space=' ', tabs='\t\t\t', combo='\n\t\t\n')
150
self.assertEqual(s.to_string(), b"""\
157
s2 = read_stanza(s.to_lines())
158
self.assertEqual(s, s2)
159
self.rio_file_stanzas([s])
161
def test_quoted(self):
162
"""rio quoted string cases"""
163
s = Stanza(q1='"hello"',
173
s2 = read_stanza(s.to_lines())
174
self.assertEqual(s, s2)
175
# apparent bug in read_stanza
176
# s3 = read_stanza(self.stanzas_to_str([s]))
177
# self.assertEqual(s, s3)
179
def test_read_empty(self):
180
"""Detect end of rio file"""
182
self.assertEqual(s, None)
183
self.assertTrue(s is None)
185
def test_read_nul_byte(self):
186
"""File consisting of a nul byte causes an error."""
187
self.assertRaises(ValueError, read_stanza, [b'\0'])
189
def test_read_nul_bytes(self):
190
"""File consisting of many nul bytes causes an error."""
191
self.assertRaises(ValueError, read_stanza, [b'\0' * 100])
193
def test_read_iter(self):
194
"""Read several stanzas from file"""
195
tmpf = TemporaryFile()
206
reader = read_stanzas(tmpf)
207
read_iter = iter(reader)
209
self.assertEqual(stuff,
210
[ Stanza(version_header='1'),
211
Stanza(name="foo", val='123'),
212
Stanza(name="bar", val='129319'), ])
214
def test_read_several(self):
215
"""Read several stanzas from file"""
216
tmpf = TemporaryFile()
224
address: "Willowglen"
232
s = read_stanza(tmpf)
233
self.assertEqual(s, Stanza(version_header='1'))
234
s = read_stanza(tmpf)
235
self.assertEqual(s, Stanza(name="foo", val='123'))
236
s = read_stanza(tmpf)
237
self.assertEqual(s.get('name'), 'quoted')
239
s.get('address'), ' "Willowglen"\n 42 Wallaby Way\n Sydney')
240
s = read_stanza(tmpf)
241
self.assertEqual(s, Stanza(name="bar", val='129319'))
242
s = read_stanza(tmpf)
243
self.assertEqual(s, None)
244
self.check_rio_file(tmpf)
246
def check_rio_file(self, real_file):
248
read_write = rio_file(RioReader(real_file)).read()
250
self.assertEqual(read_write, real_file.read())
253
def stanzas_to_str(stanzas):
254
return rio_file(stanzas).read()
256
def rio_file_stanzas(self, stanzas):
257
new_stanzas = list(RioReader(rio_file(stanzas)))
258
self.assertEqual(new_stanzas, stanzas)
260
def test_tricky_quoted(self):
261
tmpf = TemporaryFile()
295
expected_vals = ['"one"',
308
for expected in expected_vals:
309
stanza = read_stanza(tmpf)
310
self.rio_file_stanzas([stanza])
311
self.assertEqual(len(stanza), 1)
312
self.assertEqual(stanza.get('s'), expected)
314
def test_write_empty_stanza(self):
315
"""Write empty stanza"""
316
l = list(Stanza().to_lines())
317
self.assertEqual(l, [])
319
def test_rio_raises_type_error(self):
320
"""TypeError on adding invalid type to Stanza"""
322
self.assertRaises(TypeError, s.add, 'foo', {})
324
def test_rio_raises_type_error_key(self):
325
"""TypeError on adding invalid type to Stanza"""
327
self.assertRaises(TypeError, s.add, 10, {})
329
def test_rio_unicode(self):
330
uni_data = u'\N{KATAKANA LETTER O}'
331
s = Stanza(foo=uni_data)
332
self.assertEqual(s.get('foo'), uni_data)
333
raw_lines = s.to_lines()
334
self.assertEqual(raw_lines,
335
[b'foo: ' + uni_data.encode('utf-8') + b'\n'])
336
new_s = read_stanza(raw_lines)
337
self.assertEqual(new_s.get('foo'), uni_data)
339
def test_rio_to_unicode(self):
340
uni_data = u'\N{KATAKANA LETTER O}'
341
s = Stanza(foo=uni_data)
342
unicode_str = s.to_unicode()
343
self.assertEqual(u'foo: %s\n' % (uni_data,), unicode_str)
344
new_s = rio.read_stanza_unicode(unicode_str.splitlines(True))
345
self.assertEqual(uni_data, new_s.get('foo'))
347
def test_nested_rio_unicode(self):
348
uni_data = u'\N{KATAKANA LETTER O}'
349
s = Stanza(foo=uni_data)
350
parent_stanza = Stanza(child=s.to_unicode())
351
raw_lines = parent_stanza.to_lines()
352
self.assertEqual([b'child: foo: ' + uni_data.encode('utf-8') + b'\n',
355
new_parent = read_stanza(raw_lines)
356
child_text = new_parent.get('child')
357
self.assertEqual(u'foo: %s\n' % uni_data, child_text)
358
new_child = rio.read_stanza_unicode(child_text.splitlines(True))
359
self.assertEqual(uni_data, new_child.get('foo'))
361
def mail_munge(self, lines, dos_nl=True):
364
line = re.sub(b' *\n', b'\n', line)
366
line = re.sub(b'([^\r])\n', b'\\1\r\n', line)
367
new_lines.append(line)
370
def test_patch_rio(self):
371
stanza = Stanza(data='#\n\r\\r ', space=' ' * 255, hash='#' * 255)
372
lines = rio.to_patch_lines(stanza)
374
self.assertContainsRe(line, b'^# ')
375
self.assertTrue(72 >= len(line))
376
for line in rio.to_patch_lines(stanza, max_width=12):
377
self.assertTrue(12 >= len(line))
378
new_stanza = rio.read_patch_stanza(self.mail_munge(lines,
380
lines = self.mail_munge(lines)
381
new_stanza = rio.read_patch_stanza(lines)
382
self.assertEqual('#\n\r\\r ', new_stanza.get('data'))
383
self.assertEqual(' '* 255, new_stanza.get('space'))
384
self.assertEqual('#'* 255, new_stanza.get('hash'))
386
def test_patch_rio_linebreaks(self):
387
stanza = Stanza(breaktest='linebreak -/'*30)
388
self.assertContainsRe(rio.to_patch_lines(stanza, 71)[0],
390
stanza = Stanza(breaktest='linebreak-/'*30)
391
self.assertContainsRe(rio.to_patch_lines(stanza, 70)[0],
393
stanza = Stanza(breaktest='linebreak/'*30)
394
self.assertContainsRe(rio.to_patch_lines(stanza, 70)[0],