/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test__dirstate_helpers.py

Fix up inter_changes with dirstate both C and python.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007, 2008 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 the compiled dirstate helpers."""
 
18
 
 
19
import bisect
 
20
import os
 
21
 
 
22
from bzrlib import (
 
23
    dirstate,
 
24
    errors,
 
25
    tests,
 
26
    )
 
27
from bzrlib.tests import (
 
28
        SymlinkFeature,
 
29
        )
 
30
from bzrlib.tests import test_dirstate
 
31
 
 
32
 
 
33
class _CompiledDirstateHelpersFeature(tests.Feature):
 
34
    def _probe(self):
 
35
        try:
 
36
            import bzrlib._dirstate_helpers_c
 
37
        except ImportError:
 
38
            return False
 
39
        return True
 
40
 
 
41
    def feature_name(self):
 
42
        return 'bzrlib._dirstate_helpers_c'
 
43
 
 
44
CompiledDirstateHelpersFeature = _CompiledDirstateHelpersFeature()
 
45
 
 
46
 
 
47
class TestBisectPathMixin(object):
 
48
    """Test that _bisect_path_*() returns the expected values.
 
49
 
 
50
    _bisect_path_* is intended to work like bisect.bisect_*() except it
 
51
    knows it is working on paths that are sorted by ('path', 'to', 'foo')
 
52
    chunks rather than by raw 'path/to/foo'.
 
53
 
 
54
    Test Cases should inherit from this and override ``get_bisect_path`` return
 
55
    their implementation, and ``get_bisect`` to return the matching
 
56
    bisect.bisect_* function.
 
57
    """
 
58
 
 
59
    def get_bisect_path(self):
 
60
        """Return an implementation of _bisect_path_*"""
 
61
        raise NotImplementedError
 
62
 
 
63
    def get_bisect(self):
 
64
        """Return a version of bisect.bisect_*.
 
65
 
 
66
        Also, for the 'exists' check, return the offset to the real values.
 
67
        For example bisect_left returns the index of an entry, while
 
68
        bisect_right returns the index *after* an entry
 
69
 
 
70
        :return: (bisect_func, offset)
 
71
        """
 
72
        raise NotImplementedError
 
73
 
 
74
    def assertBisect(self, paths, split_paths, path, exists=True):
 
75
        """Assert that bisect_split works like bisect_left on the split paths.
 
76
 
 
77
        :param paths: A list of path names
 
78
        :param split_paths: A list of path names that are already split up by directory
 
79
            ('path/to/foo' => ('path', 'to', 'foo'))
 
80
        :param path: The path we are indexing.
 
81
        :param exists: The path should be present, so make sure the
 
82
            final location actually points to the right value.
 
83
 
 
84
        All other arguments will be passed along.
 
85
        """
 
86
        bisect_path = self.get_bisect_path()
 
87
        self.assertIsInstance(paths, list)
 
88
        bisect_path_idx = bisect_path(paths, path)
 
89
        split_path = self.split_for_dirblocks([path])[0]
 
90
        bisect_func, offset = self.get_bisect()
 
91
        bisect_split_idx = bisect_func(split_paths, split_path)
 
92
        self.assertEqual(bisect_split_idx, bisect_path_idx,
 
93
                         '%s disagreed. %s != %s'
 
94
                         ' for key %r'
 
95
                         % (bisect_path.__name__,
 
96
                            bisect_split_idx, bisect_path_idx, path)
 
97
                         )
 
98
        if exists:
 
99
            self.assertEqual(path, paths[bisect_path_idx+offset])
 
100
 
 
101
    def split_for_dirblocks(self, paths):
 
102
        dir_split_paths = []
 
103
        for path in paths:
 
104
            dirname, basename = os.path.split(path)
 
105
            dir_split_paths.append((dirname.split('/'), basename))
 
106
        dir_split_paths.sort()
 
107
        return dir_split_paths
 
108
 
 
109
    def test_simple(self):
 
110
        """In the simple case it works just like bisect_left"""
 
111
        paths = ['', 'a', 'b', 'c', 'd']
 
112
        split_paths = self.split_for_dirblocks(paths)
 
113
        for path in paths:
 
114
            self.assertBisect(paths, split_paths, path, exists=True)
 
115
        self.assertBisect(paths, split_paths, '_', exists=False)
 
116
        self.assertBisect(paths, split_paths, 'aa', exists=False)
 
117
        self.assertBisect(paths, split_paths, 'bb', exists=False)
 
118
        self.assertBisect(paths, split_paths, 'cc', exists=False)
 
119
        self.assertBisect(paths, split_paths, 'dd', exists=False)
 
120
        self.assertBisect(paths, split_paths, 'a/a', exists=False)
 
121
        self.assertBisect(paths, split_paths, 'b/b', exists=False)
 
122
        self.assertBisect(paths, split_paths, 'c/c', exists=False)
 
123
        self.assertBisect(paths, split_paths, 'd/d', exists=False)
 
124
 
 
125
    def test_involved(self):
 
126
        """This is where bisect_path_* diverges slightly."""
 
127
        # This is the list of paths and their contents
 
128
        # a/
 
129
        #   a/
 
130
        #     a
 
131
        #     z
 
132
        #   a-a/
 
133
        #     a
 
134
        #   a-z/
 
135
        #     z
 
136
        #   a=a/
 
137
        #     a
 
138
        #   a=z/
 
139
        #     z
 
140
        #   z/
 
141
        #     a
 
142
        #     z
 
143
        #   z-a
 
144
        #   z-z
 
145
        #   z=a
 
146
        #   z=z
 
147
        # a-a/
 
148
        #   a
 
149
        # a-z/
 
150
        #   z
 
151
        # a=a/
 
152
        #   a
 
153
        # a=z/
 
154
        #   z
 
155
        # This is the exact order that is stored by dirstate
 
156
        # All children in a directory are mentioned before an children of
 
157
        # children are mentioned.
 
158
        # So all the root-directory paths, then all the
 
159
        # first sub directory, etc.
 
160
        paths = [# content of '/'
 
161
                 '', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
 
162
                 # content of 'a/'
 
163
                 'a/a', 'a/a-a', 'a/a-z',
 
164
                 'a/a=a', 'a/a=z',
 
165
                 'a/z', 'a/z-a', 'a/z-z',
 
166
                 'a/z=a', 'a/z=z',
 
167
                 # content of 'a/a/'
 
168
                 'a/a/a', 'a/a/z',
 
169
                 # content of 'a/a-a'
 
170
                 'a/a-a/a',
 
171
                 # content of 'a/a-z'
 
172
                 'a/a-z/z',
 
173
                 # content of 'a/a=a'
 
174
                 'a/a=a/a',
 
175
                 # content of 'a/a=z'
 
176
                 'a/a=z/z',
 
177
                 # content of 'a/z/'
 
178
                 'a/z/a', 'a/z/z',
 
179
                 # content of 'a-a'
 
180
                 'a-a/a',
 
181
                 # content of 'a-z'
 
182
                 'a-z/z',
 
183
                 # content of 'a=a'
 
184
                 'a=a/a',
 
185
                 # content of 'a=z'
 
186
                 'a=z/z',
 
187
                ]
 
188
        split_paths = self.split_for_dirblocks(paths)
 
189
        sorted_paths = []
 
190
        for dir_parts, basename in split_paths:
 
191
            if dir_parts == ['']:
 
192
                sorted_paths.append(basename)
 
193
            else:
 
194
                sorted_paths.append('/'.join(dir_parts + [basename]))
 
195
 
 
196
        self.assertEqual(sorted_paths, paths)
 
197
 
 
198
        for path in paths:
 
199
            self.assertBisect(paths, split_paths, path, exists=True)
 
200
 
 
201
 
 
202
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
 
203
    """Run all Bisect Path tests against _bisect_path_left_py."""
 
204
 
 
205
    def get_bisect_path(self):
 
206
        from bzrlib._dirstate_helpers_py import _bisect_path_left_py
 
207
        return _bisect_path_left_py
 
208
 
 
209
    def get_bisect(self):
 
210
        return bisect.bisect_left, 0
 
211
 
 
212
 
 
213
class TestCompiledBisectPathLeft(TestBisectPathLeft):
 
214
    """Run all Bisect Path tests against _bisect_path_right_c"""
 
215
 
 
216
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
217
 
 
218
    def get_bisect_path(self):
 
219
        from bzrlib._dirstate_helpers_c import _bisect_path_left_c
 
220
        return _bisect_path_left_c
 
221
 
 
222
 
 
223
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
 
224
    """Run all Bisect Path tests against _bisect_path_right_py"""
 
225
 
 
226
    def get_bisect_path(self):
 
227
        from bzrlib._dirstate_helpers_py import _bisect_path_right_py
 
228
        return _bisect_path_right_py
 
229
 
 
230
    def get_bisect(self):
 
231
        return bisect.bisect_right, -1
 
232
 
 
233
 
 
234
class TestCompiledBisectPathRight(TestBisectPathRight):
 
235
    """Run all Bisect Path tests against _bisect_path_right_c"""
 
236
 
 
237
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
238
 
 
239
    def get_bisect_path(self):
 
240
        from bzrlib._dirstate_helpers_c import _bisect_path_right_c
 
241
        return _bisect_path_right_c
 
242
 
 
243
 
 
244
class TestBisectDirblock(tests.TestCase):
 
245
    """Test that bisect_dirblock() returns the expected values.
 
246
 
 
247
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
248
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
249
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
250
 
 
251
    This test is parameterized by calling get_bisect_dirblock(). Child test
 
252
    cases can override this function to test against a different
 
253
    implementation.
 
254
    """
 
255
 
 
256
    def get_bisect_dirblock(self):
 
257
        """Return an implementation of bisect_dirblock"""
 
258
        from bzrlib._dirstate_helpers_py import bisect_dirblock_py
 
259
        return bisect_dirblock_py
 
260
 
 
261
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
 
262
        """Assert that bisect_split works like bisect_left on the split paths.
 
263
 
 
264
        :param dirblocks: A list of (path, [info]) pairs.
 
265
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
 
266
        :param path: The path we are indexing.
 
267
 
 
268
        All other arguments will be passed along.
 
269
        """
 
270
        bisect_dirblock = self.get_bisect_dirblock()
 
271
        self.assertIsInstance(dirblocks, list)
 
272
        bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
 
273
        split_dirblock = (path.split('/'), [])
 
274
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
 
275
                                             *args)
 
276
        self.assertEqual(bisect_left_idx, bisect_split_idx,
 
277
                         'bisect_split disagreed. %s != %s'
 
278
                         ' for key %r'
 
279
                         % (bisect_left_idx, bisect_split_idx, path)
 
280
                         )
 
281
 
 
282
    def paths_to_dirblocks(self, paths):
 
283
        """Convert a list of paths into dirblock form.
 
284
 
 
285
        Also, ensure that the paths are in proper sorted order.
 
286
        """
 
287
        dirblocks = [(path, []) for path in paths]
 
288
        split_dirblocks = [(path.split('/'), []) for path in paths]
 
289
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
 
290
        return dirblocks, split_dirblocks
 
291
 
 
292
    def test_simple(self):
 
293
        """In the simple case it works just like bisect_left"""
 
294
        paths = ['', 'a', 'b', 'c', 'd']
 
295
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
296
        for path in paths:
 
297
            self.assertBisect(dirblocks, split_dirblocks, path)
 
298
        self.assertBisect(dirblocks, split_dirblocks, '_')
 
299
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
 
300
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
 
301
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
 
302
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
 
303
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
 
304
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
 
305
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
 
306
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
 
307
 
 
308
    def test_involved(self):
 
309
        """This is where bisect_left diverges slightly."""
 
310
        paths = ['', 'a',
 
311
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
312
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
313
                 'a-a', 'a-z',
 
314
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
315
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
316
                 'z-a', 'z-z',
 
317
                ]
 
318
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
319
        for path in paths:
 
320
            self.assertBisect(dirblocks, split_dirblocks, path)
 
321
 
 
322
    def test_involved_cached(self):
 
323
        """This is where bisect_left diverges slightly."""
 
324
        paths = ['', 'a',
 
325
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
 
326
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
 
327
                 'a-a', 'a-z',
 
328
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
 
329
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
 
330
                 'z-a', 'z-z',
 
331
                ]
 
332
        cache = {}
 
333
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
 
334
        for path in paths:
 
335
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
 
336
 
 
337
 
 
338
class TestCompiledBisectDirblock(TestBisectDirblock):
 
339
    """Test that bisect_dirblock() returns the expected values.
 
340
 
 
341
    bisect_dirblock is intended to work like bisect.bisect_left() except it
 
342
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
 
343
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
 
344
 
 
345
    This runs all the normal tests that TestBisectDirblock did, but uses the
 
346
    compiled version.
 
347
    """
 
348
 
 
349
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
350
 
 
351
    def get_bisect_dirblock(self):
 
352
        from bzrlib._dirstate_helpers_c import bisect_dirblock_c
 
353
        return bisect_dirblock_c
 
354
 
 
355
 
 
356
class TestCmpByDirs(tests.TestCase):
 
357
    """Test an implementation of cmp_by_dirs()
 
358
 
 
359
    cmp_by_dirs() compares 2 paths by their directory sections, rather than as
 
360
    plain strings.
 
361
 
 
362
    Child test cases can override ``get_cmp_by_dirs`` to test a specific
 
363
    implementation.
 
364
    """
 
365
 
 
366
    def get_cmp_by_dirs(self):
 
367
        """Get a specific implementation of cmp_by_dirs."""
 
368
        from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
 
369
        return cmp_by_dirs_py
 
370
 
 
371
    def assertCmpByDirs(self, expected, str1, str2):
 
372
        """Compare the two strings, in both directions.
 
373
 
 
374
        :param expected: The expected comparison value. -1 means str1 comes
 
375
            first, 0 means they are equal, 1 means str2 comes first
 
376
        :param str1: string to compare
 
377
        :param str2: string to compare
 
378
        """
 
379
        cmp_by_dirs = self.get_cmp_by_dirs()
 
380
        if expected == 0:
 
381
            self.assertEqual(str1, str2)
 
382
            self.assertEqual(0, cmp_by_dirs(str1, str2))
 
383
            self.assertEqual(0, cmp_by_dirs(str2, str1))
 
384
        elif expected > 0:
 
385
            self.assertPositive(cmp_by_dirs(str1, str2))
 
386
            self.assertNegative(cmp_by_dirs(str2, str1))
 
387
        else:
 
388
            self.assertNegative(cmp_by_dirs(str1, str2))
 
389
            self.assertPositive(cmp_by_dirs(str2, str1))
 
390
 
 
391
    def test_cmp_empty(self):
 
392
        """Compare against the empty string."""
 
393
        self.assertCmpByDirs(0, '', '')
 
394
        self.assertCmpByDirs(1, 'a', '')
 
395
        self.assertCmpByDirs(1, 'ab', '')
 
396
        self.assertCmpByDirs(1, 'abc', '')
 
397
        self.assertCmpByDirs(1, 'abcd', '')
 
398
        self.assertCmpByDirs(1, 'abcde', '')
 
399
        self.assertCmpByDirs(1, 'abcdef', '')
 
400
        self.assertCmpByDirs(1, 'abcdefg', '')
 
401
        self.assertCmpByDirs(1, 'abcdefgh', '')
 
402
        self.assertCmpByDirs(1, 'abcdefghi', '')
 
403
        self.assertCmpByDirs(1, 'test/ing/a/path/', '')
 
404
 
 
405
    def test_cmp_same_str(self):
 
406
        """Compare the same string"""
 
407
        self.assertCmpByDirs(0, 'a', 'a')
 
408
        self.assertCmpByDirs(0, 'ab', 'ab')
 
409
        self.assertCmpByDirs(0, 'abc', 'abc')
 
410
        self.assertCmpByDirs(0, 'abcd', 'abcd')
 
411
        self.assertCmpByDirs(0, 'abcde', 'abcde')
 
412
        self.assertCmpByDirs(0, 'abcdef', 'abcdef')
 
413
        self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
 
414
        self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
 
415
        self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
 
416
        self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
 
417
        self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
 
418
        self.assertCmpByDirs(0, 'a/b', 'a/b')
 
419
        self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
 
420
        self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
 
421
        self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
 
422
 
 
423
    def test_simple_paths(self):
 
424
        """Compare strings that act like normal string comparison"""
 
425
        self.assertCmpByDirs(-1, 'a', 'b')
 
426
        self.assertCmpByDirs(-1, 'aa', 'ab')
 
427
        self.assertCmpByDirs(-1, 'ab', 'bb')
 
428
        self.assertCmpByDirs(-1, 'aaa', 'aab')
 
429
        self.assertCmpByDirs(-1, 'aab', 'abb')
 
430
        self.assertCmpByDirs(-1, 'abb', 'bbb')
 
431
        self.assertCmpByDirs(-1, 'aaaa', 'aaab')
 
432
        self.assertCmpByDirs(-1, 'aaab', 'aabb')
 
433
        self.assertCmpByDirs(-1, 'aabb', 'abbb')
 
434
        self.assertCmpByDirs(-1, 'abbb', 'bbbb')
 
435
        self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
 
436
        self.assertCmpByDirs(-1, 'a/a', 'a/b')
 
437
        self.assertCmpByDirs(-1, 'a/b', 'b/b')
 
438
        self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
 
439
        self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
 
440
        self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
 
441
        self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
 
442
        self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
 
443
        self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
 
444
        self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
 
445
        self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
 
446
 
 
447
    def test_tricky_paths(self):
 
448
        self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
 
449
        self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
 
450
        self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
 
451
        self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
 
452
        self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
 
453
 
 
454
    def test_cmp_unicode_not_allowed(self):
 
455
        cmp_by_dirs = self.get_cmp_by_dirs()
 
456
        self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
 
457
        self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
 
458
        self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
 
459
 
 
460
    def test_cmp_non_ascii(self):
 
461
        self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
 
462
        self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
 
463
        self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
 
464
        self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
 
465
        self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
 
466
 
 
467
 
 
468
class TestCompiledCmpByDirs(TestCmpByDirs):
 
469
    """Test the pyrex implementation of cmp_by_dirs"""
 
470
 
 
471
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
472
 
 
473
    def get_cmp_by_dirs(self):
 
474
        from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
 
475
        return cmp_by_dirs_c
 
476
 
 
477
 
 
478
class TestCmpPathByDirblock(tests.TestCase):
 
479
    """Test an implementation of _cmp_path_by_dirblock()
 
480
 
 
481
    _cmp_path_by_dirblock() compares two paths using the sort order used by
 
482
    DirState. All paths in the same directory are sorted together.
 
483
 
 
484
    Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
 
485
    implementation.
 
486
    """
 
487
 
 
488
    def get_cmp_path_by_dirblock(self):
 
489
        """Get a specific implementation of _cmp_path_by_dirblock."""
 
490
        from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock_py
 
491
        return _cmp_path_by_dirblock_py
 
492
 
 
493
    def assertCmpPathByDirblock(self, paths):
 
494
        """Compare all paths and make sure they evaluate to the correct order.
 
495
 
 
496
        This does N^2 comparisons. It is assumed that ``paths`` is properly
 
497
        sorted list.
 
498
 
 
499
        :param paths: a sorted list of paths to compare
 
500
        """
 
501
        # First, make sure the paths being passed in are correct
 
502
        def _key(p):
 
503
            dirname, basename = os.path.split(p)
 
504
            return dirname.split('/'), basename
 
505
        self.assertEqual(sorted(paths, key=_key), paths)
 
506
 
 
507
        cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
 
508
        for idx1, path1 in enumerate(paths):
 
509
            for idx2, path2 in enumerate(paths):
 
510
                cmp_val = cmp_path_by_dirblock(path1, path2)
 
511
                if idx1 < idx2:
 
512
                    self.assertTrue(cmp_val < 0,
 
513
                        '%s did not state that %r came before %r, cmp=%s'
 
514
                        % (cmp_path_by_dirblock.__name__,
 
515
                           path1, path2, cmp_val))
 
516
                elif idx1 > idx2:
 
517
                    self.assertTrue(cmp_val > 0,
 
518
                        '%s did not state that %r came after %r, cmp=%s'
 
519
                        % (cmp_path_by_dirblock.__name__,
 
520
                           path1, path2, cmp_val))
 
521
                else: # idx1 == idx2
 
522
                    self.assertTrue(cmp_val == 0,
 
523
                        '%s did not state that %r == %r, cmp=%s'
 
524
                        % (cmp_path_by_dirblock.__name__,
 
525
                           path1, path2, cmp_val))
 
526
 
 
527
    def test_cmp_simple_paths(self):
 
528
        """Compare against the empty string."""
 
529
        self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
 
530
        self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
 
531
 
 
532
    def test_tricky_paths(self):
 
533
        self.assertCmpPathByDirblock([
 
534
            # Contents of ''
 
535
            '', 'a', 'a-a', 'a=a', 'b',
 
536
            # Contents of 'a'
 
537
            'a/a', 'a/a-a', 'a/a=a', 'a/b',
 
538
            # Contents of 'a/a'
 
539
            'a/a/a', 'a/a/a-a', 'a/a/a=a',
 
540
            # Contents of 'a/a/a'
 
541
            'a/a/a/a', 'a/a/a/b',
 
542
            # Contents of 'a/a/a-a',
 
543
            'a/a/a-a/a', 'a/a/a-a/b',
 
544
            # Contents of 'a/a/a=a',
 
545
            'a/a/a=a/a', 'a/a/a=a/b',
 
546
            # Contents of 'a/a-a'
 
547
            'a/a-a/a',
 
548
            # Contents of 'a/a-a/a'
 
549
            'a/a-a/a/a', 'a/a-a/a/b',
 
550
            # Contents of 'a/a=a'
 
551
            'a/a=a/a',
 
552
            # Contents of 'a/b'
 
553
            'a/b/a', 'a/b/b',
 
554
            # Contents of 'a-a',
 
555
            'a-a/a', 'a-a/b',
 
556
            # Contents of 'a=a',
 
557
            'a=a/a', 'a=a/b',
 
558
            # Contents of 'b',
 
559
            'b/a', 'b/b',
 
560
            ])
 
561
        self.assertCmpPathByDirblock([
 
562
                 # content of '/'
 
563
                 '', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
 
564
                 # content of 'a/'
 
565
                 'a/a', 'a/a-a', 'a/a-z',
 
566
                 'a/a=a', 'a/a=z',
 
567
                 'a/z', 'a/z-a', 'a/z-z',
 
568
                 'a/z=a', 'a/z=z',
 
569
                 # content of 'a/a/'
 
570
                 'a/a/a', 'a/a/z',
 
571
                 # content of 'a/a-a'
 
572
                 'a/a-a/a',
 
573
                 # content of 'a/a-z'
 
574
                 'a/a-z/z',
 
575
                 # content of 'a/a=a'
 
576
                 'a/a=a/a',
 
577
                 # content of 'a/a=z'
 
578
                 'a/a=z/z',
 
579
                 # content of 'a/z/'
 
580
                 'a/z/a', 'a/z/z',
 
581
                 # content of 'a-a'
 
582
                 'a-a/a',
 
583
                 # content of 'a-z'
 
584
                 'a-z/z',
 
585
                 # content of 'a=a'
 
586
                 'a=a/a',
 
587
                 # content of 'a=z'
 
588
                 'a=z/z',
 
589
                ])
 
590
 
 
591
    def test_unicode_not_allowed(self):
 
592
        cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
 
593
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
 
594
        self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
 
595
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
 
596
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
 
597
        self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
 
598
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
 
599
 
 
600
    def test_nonascii(self):
 
601
        self.assertCmpPathByDirblock([
 
602
            # content of '/'
 
603
            '', 'a', '\xc2\xb5', '\xc3\xa5',
 
604
            # content of 'a'
 
605
            'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
 
606
            # content of 'a/a'
 
607
            'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
 
608
            # content of 'a/\xc2\xb5'
 
609
            'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
 
610
            # content of 'a/\xc3\xa5'
 
611
            'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
 
612
            # content of '\xc2\xb5'
 
613
            '\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
 
614
            # content of '\xc2\xe5'
 
615
            '\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
 
616
            ])
 
617
 
 
618
 
 
619
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
 
620
    """Test the pyrex implementation of _cmp_path_by_dirblock"""
 
621
 
 
622
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
623
 
 
624
    def get_cmp_by_dirs(self):
 
625
        from bzrlib._dirstate_helpers_c import _cmp_path_by_dirblock_c
 
626
        return _cmp_path_by_dirblock_c
 
627
 
 
628
 
 
629
class TestMemRChr(tests.TestCase):
 
630
    """Test memrchr functionality"""
 
631
 
 
632
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
633
 
 
634
    def assertMemRChr(self, expected, s, c):
 
635
        from bzrlib._dirstate_helpers_c import _py_memrchr
 
636
        self.assertEqual(expected, _py_memrchr(s, c))
 
637
 
 
638
    def test_missing(self):
 
639
        self.assertMemRChr(None, '', 'a')
 
640
        self.assertMemRChr(None, '', 'c')
 
641
        self.assertMemRChr(None, 'abcdefghijklm', 'q')
 
642
        self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
 
643
 
 
644
    def test_single_entry(self):
 
645
        self.assertMemRChr(0, 'abcdefghijklm', 'a')
 
646
        self.assertMemRChr(1, 'abcdefghijklm', 'b')
 
647
        self.assertMemRChr(2, 'abcdefghijklm', 'c')
 
648
        self.assertMemRChr(10, 'abcdefghijklm', 'k')
 
649
        self.assertMemRChr(11, 'abcdefghijklm', 'l')
 
650
        self.assertMemRChr(12, 'abcdefghijklm', 'm')
 
651
 
 
652
    def test_multiple(self):
 
653
        self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
 
654
        self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
 
655
        self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
 
656
        self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
 
657
        self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
 
658
        self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
 
659
        self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
 
660
 
 
661
    def test_with_nulls(self):
 
662
        self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
 
663
        self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
 
664
        self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
 
665
        self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
 
666
        self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
 
667
        self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
 
668
        self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
 
669
        self.assertMemRChr(9, '\0\0\0\0\0\0\0\0\0\0', '\0')
 
670
 
 
671
 
 
672
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
 
673
    """Test an implementation of _read_dirblocks()
 
674
 
 
675
    _read_dirblocks() reads in all of the dirblock information from the disk
 
676
    file.
 
677
 
 
678
    Child test cases can override ``get_read_dirblocks`` to test a specific
 
679
    implementation.
 
680
    """
 
681
 
 
682
    def get_read_dirblocks(self):
 
683
        from bzrlib._dirstate_helpers_py import _read_dirblocks_py
 
684
        return _read_dirblocks_py
 
685
 
 
686
    def test_smoketest(self):
 
687
        """Make sure that we can create and read back a simple file."""
 
688
        tree, state, expected = self.create_basic_dirstate()
 
689
        del tree
 
690
        state._read_header_if_needed()
 
691
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
 
692
                         state._dirblock_state)
 
693
        read_dirblocks = self.get_read_dirblocks()
 
694
        read_dirblocks(state)
 
695
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
696
                         state._dirblock_state)
 
697
 
 
698
    def test_trailing_garbage(self):
 
699
        tree, state, expected = self.create_basic_dirstate()
 
700
        # We can modify the file as long as it hasn't been read yet.
 
701
        f = open('dirstate', 'ab')
 
702
        try:
 
703
            # Add bogus trailing garbage
 
704
            f.write('bogus\n')
 
705
        finally:
 
706
            f.close()
 
707
        e = self.assertRaises(errors.DirstateCorrupt,
 
708
                              state._read_dirblocks_if_needed)
 
709
        # Make sure we mention the bogus characters in the error
 
710
        self.assertContainsRe(str(e), 'bogus')
 
711
 
 
712
 
 
713
class TestCompiledReadDirblocks(TestReadDirblocks):
 
714
    """Test the pyrex implementation of _read_dirblocks"""
 
715
 
 
716
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
717
 
 
718
    def get_read_dirblocks(self):
 
719
        from bzrlib._dirstate_helpers_c import _read_dirblocks_c
 
720
        return _read_dirblocks_c
 
721
 
 
722
 
 
723
class TestUsingCompiledIfAvailable(tests.TestCase):
 
724
    """Check that any compiled functions that are available are the default.
 
725
 
 
726
    It is possible to have typos, etc in the import line, such that
 
727
    _dirstate_helpers_c is actually available, but the compiled functions are
 
728
    not being used.
 
729
    """
 
730
 
 
731
    def test_bisect_dirblock(self):
 
732
        if CompiledDirstateHelpersFeature.available():
 
733
            from bzrlib._dirstate_helpers_c import bisect_dirblock_c
 
734
            self.assertIs(bisect_dirblock_c, dirstate.bisect_dirblock)
 
735
        else:
 
736
            from bzrlib._dirstate_helpers_py import bisect_dirblock_py
 
737
            self.assertIs(bisect_dirblock_py, dirstate.bisect_dirblock)
 
738
 
 
739
    def test__bisect_path_left(self):
 
740
        if CompiledDirstateHelpersFeature.available():
 
741
            from bzrlib._dirstate_helpers_c import _bisect_path_left_c
 
742
            self.assertIs(_bisect_path_left_c, dirstate._bisect_path_left)
 
743
        else:
 
744
            from bzrlib._dirstate_helpers_py import _bisect_path_left_py
 
745
            self.assertIs(_bisect_path_left_py, dirstate._bisect_path_left)
 
746
 
 
747
    def test__bisect_path_right(self):
 
748
        if CompiledDirstateHelpersFeature.available():
 
749
            from bzrlib._dirstate_helpers_c import _bisect_path_right_c
 
750
            self.assertIs(_bisect_path_right_c, dirstate._bisect_path_right)
 
751
        else:
 
752
            from bzrlib._dirstate_helpers_py import _bisect_path_right_py
 
753
            self.assertIs(_bisect_path_right_py, dirstate._bisect_path_right)
 
754
 
 
755
    def test_cmp_by_dirs(self):
 
756
        if CompiledDirstateHelpersFeature.available():
 
757
            from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
 
758
            self.assertIs(cmp_by_dirs_c, dirstate.cmp_by_dirs)
 
759
        else:
 
760
            from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
 
761
            self.assertIs(cmp_by_dirs_py, dirstate.cmp_by_dirs)
 
762
 
 
763
    def test__read_dirblocks(self):
 
764
        if CompiledDirstateHelpersFeature.available():
 
765
            from bzrlib._dirstate_helpers_c import _read_dirblocks_c
 
766
            self.assertIs(_read_dirblocks_c, dirstate._read_dirblocks)
 
767
        else:
 
768
            from bzrlib._dirstate_helpers_py import _read_dirblocks_py
 
769
            self.assertIs(_read_dirblocks_py, dirstate._read_dirblocks)
 
770
 
 
771
    def test_update_entry(self):
 
772
        if CompiledDirstateHelpersFeature.available():
 
773
            from bzrlib._dirstate_helpers_c import update_entry
 
774
            self.assertIs(update_entry, dirstate.update_entry)
 
775
        else:
 
776
            from bzrlib.dirstate import py_update_entry
 
777
            self.assertIs(py_update_entry, dirstate.py_update_entry)
 
778
 
 
779
    def test_process_entry(self):
 
780
        if CompiledDirstateHelpersFeature.available():
 
781
            from bzrlib._dirstate_helpers_c import ProcessEntryC
 
782
            self.assertIs(ProcessEntryC, dirstate._process_entry)
 
783
        else:
 
784
            from bzrlib.dirstate import ProcessEntryPython
 
785
            self.assertIs(ProcessEntryPython, dirstate._process_entry)
 
786
 
 
787
 
 
788
class TestUpdateEntry(test_dirstate.TestCaseWithDirState):
 
789
    """Test the DirState.update_entry functions"""
 
790
 
 
791
    def get_state_with_a(self):
 
792
        """Create a DirState tracking a single object named 'a'"""
 
793
        state = test_dirstate.InstrumentedDirState.initialize('dirstate')
 
794
        self.addCleanup(state.unlock)
 
795
        state.add('a', 'a-id', 'file', None, '')
 
796
        entry = state._get_entry(0, path_utf8='a')
 
797
        self.set_update_entry()
 
798
        return state, entry
 
799
 
 
800
    def set_update_entry(self):
 
801
        self.update_entry = dirstate.py_update_entry
 
802
 
 
803
    def test_update_entry(self):
 
804
        state, entry = self.get_state_with_a()
 
805
        self.build_tree(['a'])
 
806
        # Add one where we don't provide the stat or sha already
 
807
        self.assertEqual(('', 'a', 'a-id'), entry[0])
 
808
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
 
809
                         entry[1])
 
810
        # Flush the buffers to disk
 
811
        state.save()
 
812
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
813
                         state._dirblock_state)
 
814
 
 
815
        stat_value = os.lstat('a')
 
816
        packed_stat = dirstate.pack_stat(stat_value)
 
817
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
818
                                          stat_value=stat_value)
 
819
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
820
                         link_or_sha1)
 
821
 
 
822
        # The dirblock entry should not cache the file's sha1
 
823
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
 
824
                         entry[1])
 
825
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
826
                         state._dirblock_state)
 
827
        mode = stat_value.st_mode
 
828
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
 
829
 
 
830
        state.save()
 
831
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
832
                         state._dirblock_state)
 
833
 
 
834
        # If we do it again right away, we don't know if the file has changed
 
835
        # so we will re-read the file. Roll the clock back so the file is
 
836
        # guaranteed to look too new.
 
837
        state.adjust_time(-10)
 
838
 
 
839
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
840
                                          stat_value=stat_value)
 
841
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
842
                          ('sha1', 'a'), ('is_exec', mode, False),
 
843
                         ], state._log)
 
844
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
845
                         link_or_sha1)
 
846
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
847
                         state._dirblock_state)
 
848
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
 
849
                         entry[1])
 
850
        state.save()
 
851
 
 
852
        # However, if we move the clock forward so the file is considered
 
853
        # "stable", it should just cache the value.
 
854
        state.adjust_time(+20)
 
855
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
856
                                          stat_value=stat_value)
 
857
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
858
                         link_or_sha1)
 
859
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
860
                          ('sha1', 'a'), ('is_exec', mode, False),
 
861
                          ('sha1', 'a'), ('is_exec', mode, False),
 
862
                         ], state._log)
 
863
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
864
                         entry[1])
 
865
 
 
866
        # Subsequent calls will just return the cached value
 
867
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
868
                                          stat_value=stat_value)
 
869
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
870
                         link_or_sha1)
 
871
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
 
872
                          ('sha1', 'a'), ('is_exec', mode, False),
 
873
                          ('sha1', 'a'), ('is_exec', mode, False),
 
874
                         ], state._log)
 
875
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
876
                         entry[1])
 
877
 
 
878
    def test_update_entry_symlink(self):
 
879
        """Update entry should read symlinks."""
 
880
        self.requireFeature(SymlinkFeature)
 
881
        state, entry = self.get_state_with_a()
 
882
        state.save()
 
883
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
884
                         state._dirblock_state)
 
885
        os.symlink('target', 'a')
 
886
 
 
887
        state.adjust_time(-10) # Make the symlink look new
 
888
        stat_value = os.lstat('a')
 
889
        packed_stat = dirstate.pack_stat(stat_value)
 
890
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
891
                                          stat_value=stat_value)
 
892
        self.assertEqual('target', link_or_sha1)
 
893
        self.assertEqual([('read_link', 'a', '')], state._log)
 
894
        # Dirblock is not updated (the link is too new)
 
895
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
 
896
                         entry[1])
 
897
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
898
                         state._dirblock_state)
 
899
 
 
900
        # Because the stat_value looks new, we should re-read the target
 
901
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
902
                                          stat_value=stat_value)
 
903
        self.assertEqual('target', link_or_sha1)
 
904
        self.assertEqual([('read_link', 'a', ''),
 
905
                          ('read_link', 'a', ''),
 
906
                         ], state._log)
 
907
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
 
908
                         entry[1])
 
909
        state.adjust_time(+20) # Skip into the future, all files look old
 
910
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
911
                                          stat_value=stat_value)
 
912
        self.assertEqual('target', link_or_sha1)
 
913
        # We need to re-read the link because only now can we cache it
 
914
        self.assertEqual([('read_link', 'a', ''),
 
915
                          ('read_link', 'a', ''),
 
916
                          ('read_link', 'a', ''),
 
917
                         ], state._log)
 
918
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
 
919
                         entry[1])
 
920
 
 
921
        # Another call won't re-read the link
 
922
        self.assertEqual([('read_link', 'a', ''),
 
923
                          ('read_link', 'a', ''),
 
924
                          ('read_link', 'a', ''),
 
925
                         ], state._log)
 
926
        link_or_sha1 = self.update_entry(state, entry, abspath='a',
 
927
                                          stat_value=stat_value)
 
928
        self.assertEqual('target', link_or_sha1)
 
929
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
 
930
                         entry[1])
 
931
 
 
932
    def do_update_entry(self, state, entry, abspath):
 
933
        stat_value = os.lstat(abspath)
 
934
        return self.update_entry(state, entry, abspath, stat_value)
 
935
 
 
936
    def test_update_entry_dir(self):
 
937
        state, entry = self.get_state_with_a()
 
938
        self.build_tree(['a/'])
 
939
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
 
940
 
 
941
    def test_update_entry_dir_unchanged(self):
 
942
        state, entry = self.get_state_with_a()
 
943
        self.build_tree(['a/'])
 
944
        state.adjust_time(+20)
 
945
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
 
946
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
947
                         state._dirblock_state)
 
948
        state.save()
 
949
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
950
                         state._dirblock_state)
 
951
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
 
952
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
953
                         state._dirblock_state)
 
954
 
 
955
    def test_update_entry_file_unchanged(self):
 
956
        state, entry = self.get_state_with_a()
 
957
        self.build_tree(['a'])
 
958
        sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
959
        state.adjust_time(+20)
 
960
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
 
961
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
962
                         state._dirblock_state)
 
963
        state.save()
 
964
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
965
                         state._dirblock_state)
 
966
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
 
967
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
968
                         state._dirblock_state)
 
969
 
 
970
    def create_and_test_file(self, state, entry):
 
971
        """Create a file at 'a' and verify the state finds it.
 
972
 
 
973
        The state should already be versioning *something* at 'a'. This makes
 
974
        sure that state.update_entry recognizes it as a file.
 
975
        """
 
976
        self.build_tree(['a'])
 
977
        stat_value = os.lstat('a')
 
978
        packed_stat = dirstate.pack_stat(stat_value)
 
979
 
 
980
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
981
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
 
982
                         link_or_sha1)
 
983
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
 
984
                         entry[1])
 
985
        return packed_stat
 
986
 
 
987
    def create_and_test_dir(self, state, entry):
 
988
        """Create a directory at 'a' and verify the state finds it.
 
989
 
 
990
        The state should already be versioning *something* at 'a'. This makes
 
991
        sure that state.update_entry recognizes it as a directory.
 
992
        """
 
993
        self.build_tree(['a/'])
 
994
        stat_value = os.lstat('a')
 
995
        packed_stat = dirstate.pack_stat(stat_value)
 
996
 
 
997
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
998
        self.assertIs(None, link_or_sha1)
 
999
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
 
1000
 
 
1001
        return packed_stat
 
1002
 
 
1003
    def create_and_test_symlink(self, state, entry):
 
1004
        """Create a symlink at 'a' and verify the state finds it.
 
1005
 
 
1006
        The state should already be versioning *something* at 'a'. This makes
 
1007
        sure that state.update_entry recognizes it as a symlink.
 
1008
 
 
1009
        This should not be called if this platform does not have symlink
 
1010
        support.
 
1011
        """
 
1012
        # caller should care about skipping test on platforms without symlinks
 
1013
        os.symlink('path/to/foo', 'a')
 
1014
 
 
1015
        stat_value = os.lstat('a')
 
1016
        packed_stat = dirstate.pack_stat(stat_value)
 
1017
 
 
1018
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
 
1019
        self.assertEqual('path/to/foo', link_or_sha1)
 
1020
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
 
1021
                         entry[1])
 
1022
        return packed_stat
 
1023
 
 
1024
    def test_update_file_to_dir(self):
 
1025
        """If a file changes to a directory we return None for the sha.
 
1026
        We also update the inventory record.
 
1027
        """
 
1028
        state, entry = self.get_state_with_a()
 
1029
        # The file sha1 won't be cached unless the file is old
 
1030
        state.adjust_time(+10)
 
1031
        self.create_and_test_file(state, entry)
 
1032
        os.remove('a')
 
1033
        self.create_and_test_dir(state, entry)
 
1034
 
 
1035
    def test_update_file_to_symlink(self):
 
1036
        """File becomes a symlink"""
 
1037
        self.requireFeature(SymlinkFeature)
 
1038
        state, entry = self.get_state_with_a()
 
1039
        # The file sha1 won't be cached unless the file is old
 
1040
        state.adjust_time(+10)
 
1041
        self.create_and_test_file(state, entry)
 
1042
        os.remove('a')
 
1043
        self.create_and_test_symlink(state, entry)
 
1044
 
 
1045
    def test_update_dir_to_file(self):
 
1046
        """Directory becoming a file updates the entry."""
 
1047
        state, entry = self.get_state_with_a()
 
1048
        # The file sha1 won't be cached unless the file is old
 
1049
        state.adjust_time(+10)
 
1050
        self.create_and_test_dir(state, entry)
 
1051
        os.rmdir('a')
 
1052
        self.create_and_test_file(state, entry)
 
1053
 
 
1054
    def test_update_dir_to_symlink(self):
 
1055
        """Directory becomes a symlink"""
 
1056
        self.requireFeature(SymlinkFeature)
 
1057
        state, entry = self.get_state_with_a()
 
1058
        # The symlink target won't be cached if it isn't old
 
1059
        state.adjust_time(+10)
 
1060
        self.create_and_test_dir(state, entry)
 
1061
        os.rmdir('a')
 
1062
        self.create_and_test_symlink(state, entry)
 
1063
 
 
1064
    def test_update_symlink_to_file(self):
 
1065
        """Symlink becomes a file"""
 
1066
        self.requireFeature(SymlinkFeature)
 
1067
        state, entry = self.get_state_with_a()
 
1068
        # The symlink and file info won't be cached unless old
 
1069
        state.adjust_time(+10)
 
1070
        self.create_and_test_symlink(state, entry)
 
1071
        os.remove('a')
 
1072
        self.create_and_test_file(state, entry)
 
1073
 
 
1074
    def test_update_symlink_to_dir(self):
 
1075
        """Symlink becomes a directory"""
 
1076
        self.requireFeature(SymlinkFeature)
 
1077
        state, entry = self.get_state_with_a()
 
1078
        # The symlink target won't be cached if it isn't old
 
1079
        state.adjust_time(+10)
 
1080
        self.create_and_test_symlink(state, entry)
 
1081
        os.remove('a')
 
1082
        self.create_and_test_dir(state, entry)
 
1083
 
 
1084
    def test__is_executable_win32(self):
 
1085
        state, entry = self.get_state_with_a()
 
1086
        self.build_tree(['a'])
 
1087
 
 
1088
        # Make sure we are using the win32 implementation of _is_executable
 
1089
        state._is_executable = state._is_executable_win32
 
1090
 
 
1091
        # The file on disk is not executable, but we are marking it as though
 
1092
        # it is. With _is_executable_win32 we ignore what is on disk.
 
1093
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
 
1094
 
 
1095
        stat_value = os.lstat('a')
 
1096
        packed_stat = dirstate.pack_stat(stat_value)
 
1097
 
 
1098
        state.adjust_time(-10) # Make sure everything is new
 
1099
        self.update_entry(state, entry, abspath='a', stat_value=stat_value)
 
1100
 
 
1101
        # The row is updated, but the executable bit stays set.
 
1102
        self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
 
1103
                         entry[1])
 
1104
 
 
1105
        # Make the disk object look old enough to cache
 
1106
        state.adjust_time(+20)
 
1107
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
 
1108
        self.update_entry(state, entry, abspath='a', stat_value=stat_value)
 
1109
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
 
1110
 
 
1111
 
 
1112
class TestCompiledUpdateEntry(TestUpdateEntry):
 
1113
    """Test the pyrex implementation of _read_dirblocks"""
 
1114
 
 
1115
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
1116
 
 
1117
    def set_update_entry(self):
 
1118
        from bzrlib._dirstate_helpers_c import update_entry
 
1119
        self.update_entry = update_entry