/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3640.2.4 by John Arbash Meinel
Copyright updates
1
# Copyright (C) 2007, 2008 Canonical Ltd
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
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
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
19
import bisect
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
20
import os
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
21
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
22
from bzrlib import (
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
23
    dirstate,
3640.2.5 by John Arbash Meinel
Change from using AssertionError to using DirstateCorrupt in a few places
24
    errors,
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
25
    tests,
26
    )
3696.4.1 by Robert Collins
Refactor to allow a pluggable dirstate update_entry with interface tests.
27
from bzrlib.tests import (
28
        SymlinkFeature,
29
        )
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
30
from bzrlib.tests import test_dirstate
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
31
32
33
class _CompiledDirstateHelpersFeature(tests.Feature):
34
    def _probe(self):
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
35
        try:
36
            import bzrlib._dirstate_helpers_c
37
        except ImportError:
38
            return False
39
        return True
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
40
41
    def feature_name(self):
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
42
        return 'bzrlib._dirstate_helpers_c'
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
43
44
CompiledDirstateHelpersFeature = _CompiledDirstateHelpersFeature()
45
46
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
47
class TestBisectPathMixin(object):
2474.1.66 by John Arbash Meinel
Some restructuring.
48
    """Test that _bisect_path_*() returns the expected values.
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
49
2474.1.66 by John Arbash Meinel
Some restructuring.
50
    _bisect_path_* is intended to work like bisect.bisect_*() except it
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
51
    knows it is working on paths that are sorted by ('path', 'to', 'foo')
52
    chunks rather than by raw 'path/to/foo'.
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
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.
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
57
    """
58
59
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
60
        """Return an implementation of _bisect_path_*"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
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):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
203
    """Run all Bisect Path tests against _bisect_path_left_py."""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
204
205
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
206
        from bzrlib._dirstate_helpers_py import _bisect_path_left_py
207
        return _bisect_path_left_py
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
208
209
    def get_bisect(self):
210
        return bisect.bisect_left, 0
211
212
213
class TestCompiledBisectPathLeft(TestBisectPathLeft):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
214
    """Run all Bisect Path tests against _bisect_path_right_c"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
215
216
    _test_needs_features = [CompiledDirstateHelpersFeature]
217
218
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
219
        from bzrlib._dirstate_helpers_c import _bisect_path_left_c
220
        return _bisect_path_left_c
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
221
222
223
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
224
    """Run all Bisect Path tests against _bisect_path_right_py"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
225
226
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
227
        from bzrlib._dirstate_helpers_py import _bisect_path_right_py
228
        return _bisect_path_right_py
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
229
230
    def get_bisect(self):
231
        return bisect.bisect_right, -1
232
233
234
class TestCompiledBisectPathRight(TestBisectPathRight):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
235
    """Run all Bisect Path tests against _bisect_path_right_c"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
236
237
    _test_needs_features = [CompiledDirstateHelpersFeature]
238
239
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
240
        from bzrlib._dirstate_helpers_c import _bisect_path_right_c
241
        return _bisect_path_right_c
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
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'.
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
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.
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
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
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
356
class TestCmpByDirs(tests.TestCase):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
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
    """
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
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
2474.1.70 by John Arbash Meinel
Lot's of fixes from Martin's comments.
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
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
467
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
468
class TestCompiledCmpByDirs(TestCmpByDirs):
469
    """Test the pyrex implementation of cmp_by_dirs"""
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
470
471
    _test_needs_features = [CompiledDirstateHelpersFeature]
472
2474.1.41 by John Arbash Meinel
Change the name of cmp_dirblock_strings to cmp_by_dirs
473
    def get_cmp_by_dirs(self):
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
474
        from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
475
        return cmp_by_dirs_c
476
477
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
478
class TestCmpPathByDirblock(tests.TestCase):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
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
    """
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
487
488
    def get_cmp_path_by_dirblock(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
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
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
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
2474.1.70 by John Arbash Meinel
Lot's of fixes from Martin's comments.
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
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
618
619
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
2474.1.66 by John Arbash Meinel
Some restructuring.
620
    """Test the pyrex implementation of _cmp_path_by_dirblock"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
621
622
    _test_needs_features = [CompiledDirstateHelpersFeature]
623
624
    def get_cmp_by_dirs(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
625
        from bzrlib._dirstate_helpers_c import _cmp_path_by_dirblock_c
626
        return _cmp_path_by_dirblock_c
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
627
628
629
class TestMemRChr(tests.TestCase):
2474.1.66 by John Arbash Meinel
Some restructuring.
630
    """Test memrchr functionality"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
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')
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
670
671
672
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
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
    """
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
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
3640.2.1 by John Arbash Meinel
More safety checks around PyString_FromStringAndSize,
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()
3640.2.5 by John Arbash Meinel
Change from using AssertionError to using DirstateCorrupt in a few places
707
        e = self.assertRaises(errors.DirstateCorrupt,
708
                              state._read_dirblocks_if_needed)
3640.2.1 by John Arbash Meinel
More safety checks around PyString_FromStringAndSize,
709
        # Make sure we mention the bogus characters in the error
710
        self.assertContainsRe(str(e), 'bogus')
711
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
712
713
class TestCompiledReadDirblocks(TestReadDirblocks):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
714
    """Test the pyrex implementation of _read_dirblocks"""
2474.1.63 by John Arbash Meinel
Found a small bug in the python version 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
2474.1.66 by John Arbash Meinel
Some restructuring.
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
3696.4.11 by Robert Collins
Some Cification of iter_changes, and making the python one actually work.
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
3696.4.5 by Robert Collins
Simple 'compiled with pyrex' ProcessEntry class. faster.
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
3696.4.1 by Robert Collins
Refactor to allow a pluggable dirstate update_entry with interface tests.
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