21
21
# TODO: might be nice to create a versionedfile with some type of corruption
22
22
# considered typical and check that it can be detected/corrected.
24
from itertools import chain, izip
25
from StringIO import StringIO
24
from gzip import GzipFile
29
29
graph as _mod_graph,
36
from bzrlib.errors import (
38
RevisionAlreadyPresent,
41
from bzrlib.knit import (
39
from ..errors import (
41
RevisionAlreadyPresent,
43
from ..bzr.knit import (
48
from bzrlib.tests import (
48
from ..sixish import (
50
54
TestCaseWithMemoryTransport,
54
split_suite_by_condition,
57
from bzrlib.tests.http_utils import TestCaseWithWebserver
58
from bzrlib.trace import mutter
59
from bzrlib.transport import get_transport
60
from bzrlib.transport.memory import MemoryTransport
61
from bzrlib.tsort import topo_sort
62
from bzrlib.tuned_gzip import GzipFile
63
import bzrlib.versionedfile as versionedfile
64
from bzrlib.versionedfile import (
58
from .http_utils import TestCaseWithWebserver
59
from ..transport.memory import MemoryTransport
60
from ..bzr import versionedfile as versionedfile
61
from ..bzr.versionedfile import (
66
63
HashEscapedPrefixMapper,
68
65
VirtualVersionedFiles,
69
66
make_versioned_files_factory,
71
from bzrlib.weave import WeaveFile
72
from bzrlib.weavefile import read_weave, write_weave
75
def load_tests(standard_tests, module, loader):
76
"""Parameterize VersionedFiles tests for different implementations."""
77
to_adapt, result = split_suite_by_condition(
78
standard_tests, condition_isinstance(TestVersionedFiles))
79
# We want to be sure of behaviour for:
80
# weaves prefix layout (weave texts)
81
# individually named weaves (weave inventories)
82
# annotated knits - prefix|hash|hash-escape layout, we test the third only
83
# as it is the most complex mapper.
84
# individually named knits
85
# individual no-graph knits in packs (signatures)
86
# individual graph knits in packs (inventories)
87
# individual graph nocompression knits in packs (revisions)
88
# plain text knits in packs (texts)
92
'factory':make_versioned_files_factory(WeaveFile,
93
ConstantMapper('inventory')),
96
'support_partial_insertion': False,
100
'factory':make_file_factory(False, ConstantMapper('revisions')),
103
'support_partial_insertion': False,
105
('named-nograph-nodelta-knit-pack', {
106
'cleanup':cleanup_pack_knit,
107
'factory':make_pack_factory(False, False, 1),
110
'support_partial_insertion': False,
112
('named-graph-knit-pack', {
113
'cleanup':cleanup_pack_knit,
114
'factory':make_pack_factory(True, True, 1),
117
'support_partial_insertion': True,
119
('named-graph-nodelta-knit-pack', {
120
'cleanup':cleanup_pack_knit,
121
'factory':make_pack_factory(True, False, 1),
124
'support_partial_insertion': False,
126
('groupcompress-nograph', {
127
'cleanup':groupcompress.cleanup_pack_group,
128
'factory':groupcompress.make_pack_factory(False, False, 1),
131
'support_partial_insertion':False,
134
len_two_scenarios = [
137
'factory':make_versioned_files_factory(WeaveFile,
141
'support_partial_insertion': False,
143
('annotated-knit-escape', {
145
'factory':make_file_factory(True, HashEscapedPrefixMapper()),
148
'support_partial_insertion': False,
150
('plain-knit-pack', {
151
'cleanup':cleanup_pack_knit,
152
'factory':make_pack_factory(True, True, 2),
155
'support_partial_insertion': True,
158
'cleanup':groupcompress.cleanup_pack_group,
159
'factory':groupcompress.make_pack_factory(True, False, 1),
162
'support_partial_insertion':False,
165
scenarios = len_one_scenarios + len_two_scenarios
166
return multiply_tests(to_adapt, scenarios, result)
68
from ..bzr.weave import WeaveFile
69
from ..bzr.weavefile import write_weave
70
from .scenarios import load_tests_apply_scenarios
73
load_tests = load_tests_apply_scenarios
169
76
def get_diamond_vf(f, trailing_eol=True, left_only=False):
314
221
self.assertTrue('r0' in versions)
315
222
self.assertTrue('r1' in versions)
316
223
self.assertTrue('r2' in versions)
317
self.assertEquals(f.get_lines('r0'), ['a\n', 'b\n'])
318
self.assertEquals(f.get_lines('r1'), ['b\n', 'c\n'])
319
self.assertEquals(f.get_lines('r2'), ['c\n', 'd\n'])
224
self.assertEqual(f.get_lines('r0'), ['a\n', 'b\n'])
225
self.assertEqual(f.get_lines('r1'), ['b\n', 'c\n'])
226
self.assertEqual(f.get_lines('r2'), ['c\n', 'd\n'])
320
227
self.assertEqual(3, f.num_versions())
321
228
origins = f.annotate('r1')
322
self.assertEquals(origins[0][0], 'r0')
323
self.assertEquals(origins[1][0], 'r1')
229
self.assertEqual(origins[0][0], 'r0')
230
self.assertEqual(origins[1][0], 'r1')
324
231
origins = f.annotate('r2')
325
self.assertEquals(origins[0][0], 'r1')
326
self.assertEquals(origins[1][0], 'r2')
232
self.assertEqual(origins[0][0], 'r1')
233
self.assertEqual(origins[1][0], 'r2')
329
236
f = self.reopen_file()
844
751
['base', 'a_ghost'],
845
752
['line\n', 'line_b\n', 'line_c\n'])
846
753
origins = vf.annotate('references_ghost')
847
self.assertEquals(('base', 'line\n'), origins[0])
848
self.assertEquals(('base', 'line_b\n'), origins[1])
849
self.assertEquals(('references_ghost', 'line_c\n'), origins[2])
754
self.assertEqual(('base', 'line\n'), origins[0])
755
self.assertEqual(('base', 'line_b\n'), origins[1])
756
self.assertEqual(('references_ghost', 'line_c\n'), origins[2])
851
758
def test_readonly_mode(self):
852
transport = get_transport(self.get_url('.'))
759
t = self.get_transport()
853
760
factory = self.get_factory()
854
vf = factory('id', transport, 0777, create=True, access_mode='w')
855
vf = factory('id', transport, access_mode='r')
761
vf = factory('id', t, 0o777, create=True, access_mode='w')
762
vf = factory('id', t, access_mode='r')
856
763
self.assertRaises(errors.ReadOnlyError, vf.add_lines, 'base', [], [])
857
764
self.assertRaises(errors.ReadOnlyError,
858
765
vf.add_lines_with_ghosts,
880
787
class TestWeave(TestCaseWithMemoryTransport, VersionedFileTestMixIn):
882
789
def get_file(self, name='foo'):
883
return WeaveFile(name, get_transport(self.get_url('.')), create=True,
884
get_scope=self.get_transaction)
790
return WeaveFile(name, self.get_transport(),
792
get_scope=self.get_transaction)
886
794
def get_file_corrupted_text(self):
887
w = WeaveFile('foo', get_transport(self.get_url('.')), create=True,
888
get_scope=self.get_transaction)
795
w = WeaveFile('foo', self.get_transport(),
797
get_scope=self.get_transaction)
889
798
w.add_lines('v1', [], ['hello\n'])
890
799
w.add_lines('v2', ['v1'], ['hello\n', 'there\n'])
999
909
# we should be able to read from http with a versioned file.
1000
910
vf = self.get_file()
1001
911
# try an empty file access
1002
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
912
readonly_vf = self.get_factory()('foo',
913
transport.get_transport_from_url(self.get_readonly_url('.')))
1003
914
self.assertEqual([], readonly_vf.versions())
916
def test_readonly_http_works_with_feeling(self):
917
# we should be able to read from http with a versioned file.
1004
919
# now with feeling.
1005
920
vf.add_lines('1', [], ['a\n'])
1006
921
vf.add_lines('2', ['1'], ['b\n', 'a\n'])
1007
readonly_vf = self.get_factory()('foo', get_transport(self.get_readonly_url('.')))
922
readonly_vf = self.get_factory()('foo',
923
transport.get_transport_from_url(self.get_readonly_url('.')))
1008
924
self.assertEqual(['1', '2'], vf.versions())
925
self.assertEqual(['1', '2'], readonly_vf.versions())
1009
926
for version in readonly_vf.versions():
1010
927
readonly_vf.get_lines(version)
1023
941
class MergeCasesMixin(object):
1025
943
def doMerge(self, base, a, b, mp):
1026
from cStringIO import StringIO
1027
944
from textwrap import dedent
1032
949
w = self.get_file()
1033
w.add_lines('text0', [], map(addcrlf, base))
1034
w.add_lines('text1', ['text0'], map(addcrlf, a))
1035
w.add_lines('text2', ['text0'], map(addcrlf, b))
950
w.add_lines('text0', [], list(map(addcrlf, base)))
951
w.add_lines('text1', ['text0'], list(map(addcrlf, a)))
952
w.add_lines('text2', ['text0'], list(map(addcrlf, b)))
1037
954
self.log_contents(w)
1463
1381
class TestVersionedFiles(TestCaseWithMemoryTransport):
1464
1382
"""Tests for the multiple-file variant of VersionedFile."""
1384
# We want to be sure of behaviour for:
1385
# weaves prefix layout (weave texts)
1386
# individually named weaves (weave inventories)
1387
# annotated knits - prefix|hash|hash-escape layout, we test the third only
1388
# as it is the most complex mapper.
1389
# individually named knits
1390
# individual no-graph knits in packs (signatures)
1391
# individual graph knits in packs (inventories)
1392
# individual graph nocompression knits in packs (revisions)
1393
# plain text knits in packs (texts)
1394
len_one_scenarios = [
1397
'factory':make_versioned_files_factory(WeaveFile,
1398
ConstantMapper('inventory')),
1401
'support_partial_insertion': False,
1405
'factory':make_file_factory(False, ConstantMapper('revisions')),
1408
'support_partial_insertion': False,
1410
('named-nograph-nodelta-knit-pack', {
1411
'cleanup':cleanup_pack_knit,
1412
'factory':make_pack_factory(False, False, 1),
1415
'support_partial_insertion': False,
1417
('named-graph-knit-pack', {
1418
'cleanup':cleanup_pack_knit,
1419
'factory':make_pack_factory(True, True, 1),
1422
'support_partial_insertion': True,
1424
('named-graph-nodelta-knit-pack', {
1425
'cleanup':cleanup_pack_knit,
1426
'factory':make_pack_factory(True, False, 1),
1429
'support_partial_insertion': False,
1431
('groupcompress-nograph', {
1432
'cleanup':groupcompress.cleanup_pack_group,
1433
'factory':groupcompress.make_pack_factory(False, False, 1),
1436
'support_partial_insertion':False,
1439
len_two_scenarios = [
1442
'factory':make_versioned_files_factory(WeaveFile,
1446
'support_partial_insertion': False,
1448
('annotated-knit-escape', {
1450
'factory':make_file_factory(True, HashEscapedPrefixMapper()),
1453
'support_partial_insertion': False,
1455
('plain-knit-pack', {
1456
'cleanup':cleanup_pack_knit,
1457
'factory':make_pack_factory(True, True, 2),
1460
'support_partial_insertion': True,
1463
'cleanup':groupcompress.cleanup_pack_group,
1464
'factory':groupcompress.make_pack_factory(True, False, 1),
1467
'support_partial_insertion':False,
1471
scenarios = len_one_scenarios + len_two_scenarios
1466
1473
def get_versionedfiles(self, relpath='files'):
1467
1474
transport = self.get_transport(relpath)
1468
1475
if relpath != '.':
1480
1487
return ('FileA',) + (suffix,)
1489
def test_add_fallback_implies_without_fallbacks(self):
1490
f = self.get_versionedfiles('files')
1491
if getattr(f, 'add_fallback_versioned_files', None) is None:
1492
raise TestNotApplicable("%s doesn't support fallbacks"
1493
% (f.__class__.__name__,))
1494
g = self.get_versionedfiles('fallback')
1495
key_a = self.get_simple_key('a')
1496
g.add_lines(key_a, [], ['\n'])
1497
f.add_fallback_versioned_files(g)
1498
self.assertTrue(key_a in f.get_parent_map([key_a]))
1499
self.assertFalse(key_a in f.without_fallbacks().get_parent_map([key_a]))
1482
1501
def test_add_lines(self):
1483
1502
f = self.get_versionedfiles()
1484
1503
key0 = self.get_simple_key('r0')
2755
2774
def test_get_sha1s_nonexistent(self):
2756
self.assertEquals({}, self.texts.get_sha1s([("NONEXISTENT",)]))
2775
self.assertEqual({}, self.texts.get_sha1s([("NONEXISTENT",)]))
2758
2777
def test_get_sha1s(self):
2759
2778
self._lines["key"] = ["dataline1", "dataline2"]
2760
self.assertEquals({("key",): osutils.sha_strings(self._lines["key"])},
2779
self.assertEqual({("key",): osutils.sha_strings(self._lines["key"])},
2761
2780
self.texts.get_sha1s([("key",)]))
2763
2782
def test_get_parent_map(self):
2764
2783
self._parent_map = {"G": ("A", "B")}
2765
self.assertEquals({("G",): (("A",),("B",))},
2784
self.assertEqual({("G",): (("A",),("B",))},
2766
2785
self.texts.get_parent_map([("G",), ("L",)]))
2768
2787
def test_get_record_stream(self):
2769
2788
self._lines["A"] = ["FOO", "BAR"]
2770
2789
it = self.texts.get_record_stream([("A",)], "unordered", True)
2772
self.assertEquals("chunked", record.storage_kind)
2773
self.assertEquals("FOOBAR", record.get_bytes_as("fulltext"))
2774
self.assertEquals(["FOO", "BAR"], record.get_bytes_as("chunked"))
2791
self.assertEqual("chunked", record.storage_kind)
2792
self.assertEqual("FOOBAR", record.get_bytes_as("fulltext"))
2793
self.assertEqual(["FOO", "BAR"], record.get_bytes_as("chunked"))
2776
2795
def test_get_record_stream_absent(self):
2777
2796
it = self.texts.get_record_stream([("A",)], "unordered", True)
2779
self.assertEquals("absent", record.storage_kind)
2798
self.assertEqual("absent", record.storage_kind)
2781
2800
def test_iter_lines_added_or_present_in_keys(self):
2782
2801
self._lines["A"] = ["FOO", "BAR"]
2783
2802
self._lines["B"] = ["HEY"]
2784
2803
self._lines["C"] = ["Alberta"]
2785
2804
it = self.texts.iter_lines_added_or_present_in_keys([("A",), ("B",)])
2786
self.assertEquals(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2805
self.assertEqual(sorted([("FOO", "A"), ("BAR", "A"), ("HEY", "B")]),
2787
2806
sorted(list(it)))