source: trunk/src/allmydata/test/mutable/test_update.py

Last change on this file was 1cfe843d, checked in by Alexandre Detiste <alexandre.detiste@…>, at 2024-02-22T23:40:25Z

more python2 removal

  • Property mode set to 100644
File size: 10.1 KB
Line 
1"""
2Ported to Python 3.
3"""
4
5import re
6from ..common import AsyncTestCase
7from testtools.matchers import (
8    Equals,
9    IsInstance,
10    GreaterThan,
11)
12from twisted.internet import defer
13from allmydata.interfaces import MDMF_VERSION
14from allmydata.mutable.filenode import MutableFileNode
15from allmydata.mutable.publish import MutableData, DEFAULT_MUTABLE_MAX_SEGMENT_SIZE
16from ..no_network import GridTestMixin
17from .. import common_util as testutil
18
19# We should really force a smaller segsize for the duration of the tests, to
20# let them run faster, but Many of them tests depend upon a specific segment
21# size. Factor out this expectation here, to start the process of cleaning
22# this up.
23SEGSIZE = 128*1024
24
25class Update(GridTestMixin, AsyncTestCase, testutil.ShouldFailMixin):
26    def setUp(self):
27        GridTestMixin.setUp(self)
28        self.basedir = self.mktemp()
29        self.set_up_grid(num_servers=13)
30        self.c = self.g.clients[0]
31        self.nm = self.c.nodemaker
32        # self.data should be at least three segments long.
33        td = b"testdata "
34        self.data = td*(int(3*SEGSIZE//len(td))+10) # currently about 400kB
35        self.assertThat(len(self.data), GreaterThan(3*SEGSIZE))
36        self.small_data = b"test data" * 10 # 90 B; SDMF
37
38
39    def do_upload_sdmf(self):
40        d = self.nm.create_mutable_file(MutableData(self.small_data))
41        def _then(n):
42            self.assertThat(n, IsInstance(MutableFileNode))
43            self.sdmf_node = n
44        d.addCallback(_then)
45        return d
46
47    def do_upload_mdmf(self):
48        d = self.nm.create_mutable_file(MutableData(self.data),
49                                        version=MDMF_VERSION)
50        def _then(n):
51            self.assertThat(n, IsInstance(MutableFileNode))
52            self.mdmf_node = n
53        d.addCallback(_then)
54        return d
55
56    def _test_replace(self, offset, new_data):
57        expected = self.data[:offset]+new_data+self.data[offset+len(new_data):]
58        d0 = self.do_upload_mdmf()
59        def _run(ign):
60            d = defer.succeed(None)
61            d.addCallback(lambda ign: self.mdmf_node.get_best_mutable_version())
62            d.addCallback(lambda mv: mv.update(MutableData(new_data), offset))
63            d.addCallback(lambda ign: self.mdmf_node.download_best_version())
64            def _check(results):
65                if results != expected:
66                    print()
67                    print("got: %s ... %s" % (results[:20], results[-20:]))
68                    print("exp: %s ... %s" % (expected[:20], expected[-20:]))
69                    self.fail("results != expected")
70            d.addCallback(_check)
71            return d
72        d0.addCallback(_run)
73        return d0
74
75    def test_append(self):
76        # We should be able to append data to a mutable file and get
77        # what we expect.
78        return self._test_replace(len(self.data), b"appended")
79
80    def test_replace_middle(self):
81        # We should be able to replace data in the middle of a mutable
82        # file and get what we expect back.
83        return self._test_replace(100, b"replaced")
84
85    def test_replace_beginning(self):
86        # We should be able to replace data at the beginning of the file
87        # without truncating the file
88        return self._test_replace(0, b"beginning")
89
90    def test_replace_segstart1(self):
91        return self._test_replace(128*1024+1, b"NNNN")
92
93    def test_replace_zero_length_beginning(self):
94        return self._test_replace(0, b"")
95
96    def test_replace_zero_length_middle(self):
97        return self._test_replace(50, b"")
98
99    def test_replace_zero_length_segstart1(self):
100        return self._test_replace(128*1024+1, b"")
101
102    def test_replace_and_extend(self):
103        # We should be able to replace data in the middle of a mutable
104        # file and extend that mutable file and get what we expect.
105        return self._test_replace(100, b"modified " * 100000)
106
107
108    def _check_differences(self, got, expected):
109        # displaying arbitrary file corruption is tricky for a
110        # 1MB file of repeating data,, so look for likely places
111        # with problems and display them separately
112        gotmods = [mo.span() for mo in re.finditer(b'([A-Z]+)', got)]
113        expmods = [mo.span() for mo in re.finditer(b'([A-Z]+)', expected)]
114        gotspans = ["%d:%d=%r" % (start,end,got[start:end])
115                    for (start,end) in gotmods]
116        expspans = ["%d:%d=%r" % (start,end,expected[start:end])
117                    for (start,end) in expmods]
118        #print("expecting: %s" % expspans)
119
120        if got != expected:
121            print("differences:")
122            for segnum in range(len(expected)//SEGSIZE):
123                start = segnum * SEGSIZE
124                end = (segnum+1) * SEGSIZE
125                got_ends = "%s .. %s" % (got[start:start+20], got[end-20:end])
126                exp_ends = "%s .. %s" % (expected[start:start+20], expected[end-20:end])
127                if got_ends != exp_ends:
128                    print("expected[%d]: %s" % (start, exp_ends))
129                    print("got     [%d]: %s" % (start, got_ends))
130            if expspans != gotspans:
131                print("expected: %s" % expspans)
132                print("got     : %s" % gotspans)
133            open("EXPECTED","wb").write(expected)
134            open("GOT","wb").write(got)
135            print("wrote data to EXPECTED and GOT")
136            self.fail("didn't get expected data")
137
138
139    def test_replace_locations(self):
140        # exercise fencepost conditions
141        suspects = list(range(SEGSIZE-3, SEGSIZE+1)) + list(
142            range(2*SEGSIZE-3, 2*SEGSIZE+1))
143        letters = iter("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
144        d0 = self.do_upload_mdmf()
145        def _run(ign):
146            expected = self.data
147            d = defer.succeed(None)
148            for offset in suspects:
149                new_data = next(letters).encode("ascii") * 2 # "AA", then "BB", etc
150                expected = expected[:offset]+new_data+expected[offset+2:]
151                d.addCallback(lambda ign:
152                              self.mdmf_node.get_best_mutable_version())
153                def _modify(mv, offset=offset, new_data=new_data):
154                    # close over 'offset','new_data'
155                    md = MutableData(new_data)
156                    return mv.update(md, offset)
157                d.addCallback(_modify)
158                d.addCallback(lambda ignored:
159                              self.mdmf_node.download_best_version())
160                d.addCallback(self._check_differences, expected)
161            return d
162        d0.addCallback(_run)
163        return d0
164
165
166    def test_append_power_of_two(self):
167        # If we attempt to extend a mutable file so that its segment
168        # count crosses a power-of-two boundary, the update operation
169        # should know how to reencode the file.
170
171        # Note that the data populating self.mdmf_node is about 900 KiB
172        # long -- this is 7 segments in the default segment size. So we
173        # need to add 2 segments worth of data to push it over a
174        # power-of-two boundary.
175        segment = b"a" * DEFAULT_MUTABLE_MAX_SEGMENT_SIZE
176        new_data = self.data + (segment * 2)
177        d0 = self.do_upload_mdmf()
178        def _run(ign):
179            d = defer.succeed(None)
180            d.addCallback(lambda ign: self.mdmf_node.get_best_mutable_version())
181            d.addCallback(lambda mv: mv.update(MutableData(segment * 2),
182                                               len(self.data)))
183            d.addCallback(lambda ign: self.mdmf_node.download_best_version())
184            d.addCallback(lambda results:
185                          self.assertThat(results, Equals(new_data)))
186            return d
187        d0.addCallback(_run)
188        return d0
189
190    def test_update_sdmf(self):
191        # Running update on a single-segment file should still work.
192        new_data = self.small_data + b"appended"
193        d0 = self.do_upload_sdmf()
194        def _run(ign):
195            d = defer.succeed(None)
196            d.addCallback(lambda ign: self.sdmf_node.get_best_mutable_version())
197            d.addCallback(lambda mv: mv.update(MutableData(b"appended"),
198                                               len(self.small_data)))
199            d.addCallback(lambda ign: self.sdmf_node.download_best_version())
200            d.addCallback(lambda results:
201                          self.assertThat(results, Equals(new_data)))
202            return d
203        d0.addCallback(_run)
204        return d0
205
206    def test_replace_in_last_segment(self):
207        # The wrapper should know how to handle the tail segment
208        # appropriately.
209        replace_offset = len(self.data) - 100
210        new_data = self.data[:replace_offset] + b"replaced"
211        rest_offset = replace_offset + len(b"replaced")
212        new_data += self.data[rest_offset:]
213        d0 = self.do_upload_mdmf()
214        def _run(ign):
215            d = defer.succeed(None)
216            d.addCallback(lambda ign: self.mdmf_node.get_best_mutable_version())
217            d.addCallback(lambda mv: mv.update(MutableData(b"replaced"),
218                                               replace_offset))
219            d.addCallback(lambda ign: self.mdmf_node.download_best_version())
220            d.addCallback(lambda results:
221                          self.assertThat(results, Equals(new_data)))
222            return d
223        d0.addCallback(_run)
224        return d0
225
226    def test_multiple_segment_replace(self):
227        replace_offset = 2 * DEFAULT_MUTABLE_MAX_SEGMENT_SIZE
228        new_data = self.data[:replace_offset]
229        new_segment = b"a" * DEFAULT_MUTABLE_MAX_SEGMENT_SIZE
230        new_data += 2 * new_segment
231        new_data += b"replaced"
232        rest_offset = len(new_data)
233        new_data += self.data[rest_offset:]
234        d0 = self.do_upload_mdmf()
235        def _run(ign):
236            d = defer.succeed(None)
237            d.addCallback(lambda ign: self.mdmf_node.get_best_mutable_version())
238            d.addCallback(lambda mv: mv.update(MutableData((2 * new_segment) + b"replaced"),
239                                               replace_offset))
240            d.addCallback(lambda ignored: self.mdmf_node.download_best_version())
241            d.addCallback(lambda results:
242                          self.assertThat(results, Equals(new_data)))
243            return d
244        d0.addCallback(_run)
245        return d0
Note: See TracBrowser for help on using the repository browser.