~bzr-pqm/bzr/bzr.dev

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
    )
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
27
from bzrlib.tests import test_dirstate
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
28
29
30
class _CompiledDirstateHelpersFeature(tests.Feature):
31
    def _probe(self):
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
32
        try:
33
            import bzrlib._dirstate_helpers_c
34
        except ImportError:
35
            return False
36
        return True
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
37
38
    def feature_name(self):
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
39
        return 'bzrlib._dirstate_helpers_c'
2474.1.7 by John Arbash Meinel
Add some tests for a helper function that lets us
40
41
CompiledDirstateHelpersFeature = _CompiledDirstateHelpersFeature()
42
43
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
44
class TestBisectPathMixin(object):
2474.1.66 by John Arbash Meinel
Some restructuring.
45
    """Test that _bisect_path_*() returns the expected values.
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
46
2474.1.66 by John Arbash Meinel
Some restructuring.
47
    _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*
48
    knows it is working on paths that are sorted by ('path', 'to', 'foo')
49
    chunks rather than by raw 'path/to/foo'.
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
50
51
    Test Cases should inherit from this and override ``get_bisect_path`` return
52
    their implementation, and ``get_bisect`` to return the matching
53
    bisect.bisect_* function.
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
54
    """
55
56
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
57
        """Return an implementation of _bisect_path_*"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
58
        raise NotImplementedError
59
60
    def get_bisect(self):
61
        """Return a version of bisect.bisect_*.
62
63
        Also, for the 'exists' check, return the offset to the real values.
64
        For example bisect_left returns the index of an entry, while
65
        bisect_right returns the index *after* an entry
66
67
        :return: (bisect_func, offset)
68
        """
69
        raise NotImplementedError
70
71
    def assertBisect(self, paths, split_paths, path, exists=True):
72
        """Assert that bisect_split works like bisect_left on the split paths.
73
74
        :param paths: A list of path names
75
        :param split_paths: A list of path names that are already split up by directory
76
            ('path/to/foo' => ('path', 'to', 'foo'))
77
        :param path: The path we are indexing.
78
        :param exists: The path should be present, so make sure the
79
            final location actually points to the right value.
80
81
        All other arguments will be passed along.
82
        """
83
        bisect_path = self.get_bisect_path()
84
        self.assertIsInstance(paths, list)
85
        bisect_path_idx = bisect_path(paths, path)
86
        split_path = self.split_for_dirblocks([path])[0]
87
        bisect_func, offset = self.get_bisect()
88
        bisect_split_idx = bisect_func(split_paths, split_path)
89
        self.assertEqual(bisect_split_idx, bisect_path_idx,
90
                         '%s disagreed. %s != %s'
91
                         ' for key %r'
92
                         % (bisect_path.__name__,
93
                            bisect_split_idx, bisect_path_idx, path)
94
                         )
95
        if exists:
96
            self.assertEqual(path, paths[bisect_path_idx+offset])
97
98
    def split_for_dirblocks(self, paths):
99
        dir_split_paths = []
100
        for path in paths:
101
            dirname, basename = os.path.split(path)
102
            dir_split_paths.append((dirname.split('/'), basename))
103
        dir_split_paths.sort()
104
        return dir_split_paths
105
106
    def test_simple(self):
107
        """In the simple case it works just like bisect_left"""
108
        paths = ['', 'a', 'b', 'c', 'd']
109
        split_paths = self.split_for_dirblocks(paths)
110
        for path in paths:
111
            self.assertBisect(paths, split_paths, path, exists=True)
112
        self.assertBisect(paths, split_paths, '_', exists=False)
113
        self.assertBisect(paths, split_paths, 'aa', exists=False)
114
        self.assertBisect(paths, split_paths, 'bb', exists=False)
115
        self.assertBisect(paths, split_paths, 'cc', exists=False)
116
        self.assertBisect(paths, split_paths, 'dd', exists=False)
117
        self.assertBisect(paths, split_paths, 'a/a', exists=False)
118
        self.assertBisect(paths, split_paths, 'b/b', exists=False)
119
        self.assertBisect(paths, split_paths, 'c/c', exists=False)
120
        self.assertBisect(paths, split_paths, 'd/d', exists=False)
121
122
    def test_involved(self):
123
        """This is where bisect_path_* diverges slightly."""
124
        # This is the list of paths and their contents
125
        # a/
126
        #   a/
127
        #     a
128
        #     z
129
        #   a-a/
130
        #     a
131
        #   a-z/
132
        #     z
133
        #   a=a/
134
        #     a
135
        #   a=z/
136
        #     z
137
        #   z/
138
        #     a
139
        #     z
140
        #   z-a
141
        #   z-z
142
        #   z=a
143
        #   z=z
144
        # a-a/
145
        #   a
146
        # a-z/
147
        #   z
148
        # a=a/
149
        #   a
150
        # a=z/
151
        #   z
152
        # This is the exact order that is stored by dirstate
153
        # All children in a directory are mentioned before an children of
154
        # children are mentioned.
155
        # So all the root-directory paths, then all the
156
        # first sub directory, etc.
157
        paths = [# content of '/'
158
                 '', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
159
                 # content of 'a/'
160
                 'a/a', 'a/a-a', 'a/a-z',
161
                 'a/a=a', 'a/a=z',
162
                 'a/z', 'a/z-a', 'a/z-z',
163
                 'a/z=a', 'a/z=z',
164
                 # content of 'a/a/'
165
                 'a/a/a', 'a/a/z',
166
                 # content of 'a/a-a'
167
                 'a/a-a/a',
168
                 # content of 'a/a-z'
169
                 'a/a-z/z',
170
                 # content of 'a/a=a'
171
                 'a/a=a/a',
172
                 # content of 'a/a=z'
173
                 'a/a=z/z',
174
                 # content of 'a/z/'
175
                 'a/z/a', 'a/z/z',
176
                 # content of 'a-a'
177
                 'a-a/a',
178
                 # content of 'a-z'
179
                 'a-z/z',
180
                 # content of 'a=a'
181
                 'a=a/a',
182
                 # content of 'a=z'
183
                 'a=z/z',
184
                ]
185
        split_paths = self.split_for_dirblocks(paths)
186
        sorted_paths = []
187
        for dir_parts, basename in split_paths:
188
            if dir_parts == ['']:
189
                sorted_paths.append(basename)
190
            else:
191
                sorted_paths.append('/'.join(dir_parts + [basename]))
192
193
        self.assertEqual(sorted_paths, paths)
194
195
        for path in paths:
196
            self.assertBisect(paths, split_paths, path, exists=True)
197
198
199
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
200
    """Run all Bisect Path tests against _bisect_path_left_py."""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
201
202
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
203
        from bzrlib._dirstate_helpers_py import _bisect_path_left_py
204
        return _bisect_path_left_py
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
205
206
    def get_bisect(self):
207
        return bisect.bisect_left, 0
208
209
210
class TestCompiledBisectPathLeft(TestBisectPathLeft):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
211
    """Run all Bisect Path tests against _bisect_path_right_c"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
212
213
    _test_needs_features = [CompiledDirstateHelpersFeature]
214
215
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
216
        from bzrlib._dirstate_helpers_c import _bisect_path_left_c
217
        return _bisect_path_left_c
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
218
219
220
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
221
    """Run all Bisect Path tests against _bisect_path_right_py"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
222
223
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
224
        from bzrlib._dirstate_helpers_py import _bisect_path_right_py
225
        return _bisect_path_right_py
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
226
227
    def get_bisect(self):
228
        return bisect.bisect_right, -1
229
230
231
class TestCompiledBisectPathRight(TestBisectPathRight):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
232
    """Run all Bisect Path tests against _bisect_path_right_c"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
233
234
    _test_needs_features = [CompiledDirstateHelpersFeature]
235
236
    def get_bisect_path(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
237
        from bzrlib._dirstate_helpers_c import _bisect_path_right_c
238
        return _bisect_path_right_c
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
239
240
241
class TestBisectDirblock(tests.TestCase):
242
    """Test that bisect_dirblock() returns the expected values.
243
244
    bisect_dirblock is intended to work like bisect.bisect_left() except it
245
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
246
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
247
248
    This test is parameterized by calling get_bisect_dirblock(). Child test
249
    cases can override this function to test against a different
250
    implementation.
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
251
    """
252
253
    def get_bisect_dirblock(self):
254
        """Return an implementation of bisect_dirblock"""
255
        from bzrlib._dirstate_helpers_py import bisect_dirblock_py
256
        return bisect_dirblock_py
257
258
    def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
259
        """Assert that bisect_split works like bisect_left on the split paths.
260
261
        :param dirblocks: A list of (path, [info]) pairs.
262
        :param split_dirblocks: A list of ((split, path), [info]) pairs.
263
        :param path: The path we are indexing.
264
265
        All other arguments will be passed along.
266
        """
267
        bisect_dirblock = self.get_bisect_dirblock()
268
        self.assertIsInstance(dirblocks, list)
269
        bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
270
        split_dirblock = (path.split('/'), [])
271
        bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
272
                                             *args)
273
        self.assertEqual(bisect_left_idx, bisect_split_idx,
274
                         'bisect_split disagreed. %s != %s'
275
                         ' for key %r'
276
                         % (bisect_left_idx, bisect_split_idx, path)
277
                         )
278
279
    def paths_to_dirblocks(self, paths):
280
        """Convert a list of paths into dirblock form.
281
282
        Also, ensure that the paths are in proper sorted order.
283
        """
284
        dirblocks = [(path, []) for path in paths]
285
        split_dirblocks = [(path.split('/'), []) for path in paths]
286
        self.assertEqual(sorted(split_dirblocks), split_dirblocks)
287
        return dirblocks, split_dirblocks
288
289
    def test_simple(self):
290
        """In the simple case it works just like bisect_left"""
291
        paths = ['', 'a', 'b', 'c', 'd']
292
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
293
        for path in paths:
294
            self.assertBisect(dirblocks, split_dirblocks, path)
295
        self.assertBisect(dirblocks, split_dirblocks, '_')
296
        self.assertBisect(dirblocks, split_dirblocks, 'aa')
297
        self.assertBisect(dirblocks, split_dirblocks, 'bb')
298
        self.assertBisect(dirblocks, split_dirblocks, 'cc')
299
        self.assertBisect(dirblocks, split_dirblocks, 'dd')
300
        self.assertBisect(dirblocks, split_dirblocks, 'a/a')
301
        self.assertBisect(dirblocks, split_dirblocks, 'b/b')
302
        self.assertBisect(dirblocks, split_dirblocks, 'c/c')
303
        self.assertBisect(dirblocks, split_dirblocks, 'd/d')
304
305
    def test_involved(self):
306
        """This is where bisect_left diverges slightly."""
307
        paths = ['', 'a',
308
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
309
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
310
                 'a-a', 'a-z',
311
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
312
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
313
                 'z-a', 'z-z',
314
                ]
315
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
316
        for path in paths:
317
            self.assertBisect(dirblocks, split_dirblocks, path)
318
319
    def test_involved_cached(self):
320
        """This is where bisect_left diverges slightly."""
321
        paths = ['', 'a',
322
                 'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
323
                 'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
324
                 'a-a', 'a-z',
325
                 'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
326
                 'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
327
                 'z-a', 'z-z',
328
                ]
329
        cache = {}
330
        dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
331
        for path in paths:
332
            self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
333
334
335
class TestCompiledBisectDirblock(TestBisectDirblock):
336
    """Test that bisect_dirblock() returns the expected values.
337
338
    bisect_dirblock is intended to work like bisect.bisect_left() except it
339
    knows it is working on dirblocks and that dirblocks are sorted by ('path',
340
    'to', 'foo') chunks rather than by raw 'path/to/foo'.
341
342
    This runs all the normal tests that TestBisectDirblock did, but uses the
343
    compiled version.
344
    """
345
346
    _test_needs_features = [CompiledDirstateHelpersFeature]
347
348
    def get_bisect_dirblock(self):
349
        from bzrlib._dirstate_helpers_c import bisect_dirblock_c
350
        return bisect_dirblock_c
351
352
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
353
class TestCmpByDirs(tests.TestCase):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
354
    """Test an implementation of cmp_by_dirs()
355
356
    cmp_by_dirs() compares 2 paths by their directory sections, rather than as
357
    plain strings.
358
359
    Child test cases can override ``get_cmp_by_dirs`` to test a specific
360
    implementation.
361
    """
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
362
363
    def get_cmp_by_dirs(self):
364
        """Get a specific implementation of cmp_by_dirs."""
365
        from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
366
        return cmp_by_dirs_py
367
368
    def assertCmpByDirs(self, expected, str1, str2):
369
        """Compare the two strings, in both directions.
370
371
        :param expected: The expected comparison value. -1 means str1 comes
372
            first, 0 means they are equal, 1 means str2 comes first
373
        :param str1: string to compare
374
        :param str2: string to compare
375
        """
376
        cmp_by_dirs = self.get_cmp_by_dirs()
377
        if expected == 0:
378
            self.assertEqual(str1, str2)
379
            self.assertEqual(0, cmp_by_dirs(str1, str2))
380
            self.assertEqual(0, cmp_by_dirs(str2, str1))
381
        elif expected > 0:
382
            self.assertPositive(cmp_by_dirs(str1, str2))
383
            self.assertNegative(cmp_by_dirs(str2, str1))
384
        else:
385
            self.assertNegative(cmp_by_dirs(str1, str2))
386
            self.assertPositive(cmp_by_dirs(str2, str1))
387
388
    def test_cmp_empty(self):
389
        """Compare against the empty string."""
390
        self.assertCmpByDirs(0, '', '')
391
        self.assertCmpByDirs(1, 'a', '')
392
        self.assertCmpByDirs(1, 'ab', '')
393
        self.assertCmpByDirs(1, 'abc', '')
394
        self.assertCmpByDirs(1, 'abcd', '')
395
        self.assertCmpByDirs(1, 'abcde', '')
396
        self.assertCmpByDirs(1, 'abcdef', '')
397
        self.assertCmpByDirs(1, 'abcdefg', '')
398
        self.assertCmpByDirs(1, 'abcdefgh', '')
399
        self.assertCmpByDirs(1, 'abcdefghi', '')
400
        self.assertCmpByDirs(1, 'test/ing/a/path/', '')
401
402
    def test_cmp_same_str(self):
403
        """Compare the same string"""
404
        self.assertCmpByDirs(0, 'a', 'a')
405
        self.assertCmpByDirs(0, 'ab', 'ab')
406
        self.assertCmpByDirs(0, 'abc', 'abc')
407
        self.assertCmpByDirs(0, 'abcd', 'abcd')
408
        self.assertCmpByDirs(0, 'abcde', 'abcde')
409
        self.assertCmpByDirs(0, 'abcdef', 'abcdef')
410
        self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
411
        self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
412
        self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
413
        self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
414
        self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
415
        self.assertCmpByDirs(0, 'a/b', 'a/b')
416
        self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
417
        self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
418
        self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
419
420
    def test_simple_paths(self):
421
        """Compare strings that act like normal string comparison"""
422
        self.assertCmpByDirs(-1, 'a', 'b')
423
        self.assertCmpByDirs(-1, 'aa', 'ab')
424
        self.assertCmpByDirs(-1, 'ab', 'bb')
425
        self.assertCmpByDirs(-1, 'aaa', 'aab')
426
        self.assertCmpByDirs(-1, 'aab', 'abb')
427
        self.assertCmpByDirs(-1, 'abb', 'bbb')
428
        self.assertCmpByDirs(-1, 'aaaa', 'aaab')
429
        self.assertCmpByDirs(-1, 'aaab', 'aabb')
430
        self.assertCmpByDirs(-1, 'aabb', 'abbb')
431
        self.assertCmpByDirs(-1, 'abbb', 'bbbb')
432
        self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
433
        self.assertCmpByDirs(-1, 'a/a', 'a/b')
434
        self.assertCmpByDirs(-1, 'a/b', 'b/b')
435
        self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
436
        self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
437
        self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
438
        self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
439
        self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
440
        self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
441
        self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
442
        self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
443
444
    def test_tricky_paths(self):
445
        self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
446
        self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
447
        self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
448
        self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
449
        self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
450
2474.1.70 by John Arbash Meinel
Lot's of fixes from Martin's comments.
451
    def test_cmp_unicode_not_allowed(self):
452
        cmp_by_dirs = self.get_cmp_by_dirs()
453
        self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
454
        self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
455
        self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
456
457
    def test_cmp_non_ascii(self):
458
        self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
459
        self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
460
        self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
461
        self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
462
        self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
463
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
464
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
465
class TestCompiledCmpByDirs(TestCmpByDirs):
466
    """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
467
468
    _test_needs_features = [CompiledDirstateHelpersFeature]
469
2474.1.41 by John Arbash Meinel
Change the name of cmp_dirblock_strings to cmp_by_dirs
470
    def get_cmp_by_dirs(self):
2474.1.57 by John Arbash Meinel
Move code around to refactor according to our pyrex extension design.
471
        from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
472
        return cmp_by_dirs_c
473
474
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
475
class TestCmpPathByDirblock(tests.TestCase):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
476
    """Test an implementation of _cmp_path_by_dirblock()
477
478
    _cmp_path_by_dirblock() compares two paths using the sort order used by
479
    DirState. All paths in the same directory are sorted together.
480
481
    Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
482
    implementation.
483
    """
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
484
485
    def get_cmp_path_by_dirblock(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
486
        """Get a specific implementation of _cmp_path_by_dirblock."""
487
        from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock_py
488
        return _cmp_path_by_dirblock_py
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
489
490
    def assertCmpPathByDirblock(self, paths):
491
        """Compare all paths and make sure they evaluate to the correct order.
492
493
        This does N^2 comparisons. It is assumed that ``paths`` is properly
494
        sorted list.
495
496
        :param paths: a sorted list of paths to compare
497
        """
498
        # First, make sure the paths being passed in are correct
499
        def _key(p):
500
            dirname, basename = os.path.split(p)
501
            return dirname.split('/'), basename
502
        self.assertEqual(sorted(paths, key=_key), paths)
503
504
        cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
505
        for idx1, path1 in enumerate(paths):
506
            for idx2, path2 in enumerate(paths):
507
                cmp_val = cmp_path_by_dirblock(path1, path2)
508
                if idx1 < idx2:
509
                    self.assertTrue(cmp_val < 0,
510
                        '%s did not state that %r came before %r, cmp=%s'
511
                        % (cmp_path_by_dirblock.__name__,
512
                           path1, path2, cmp_val))
513
                elif idx1 > idx2:
514
                    self.assertTrue(cmp_val > 0,
515
                        '%s did not state that %r came after %r, cmp=%s'
516
                        % (cmp_path_by_dirblock.__name__,
517
                           path1, path2, cmp_val))
518
                else: # idx1 == idx2
519
                    self.assertTrue(cmp_val == 0,
520
                        '%s did not state that %r == %r, cmp=%s'
521
                        % (cmp_path_by_dirblock.__name__,
522
                           path1, path2, cmp_val))
523
524
    def test_cmp_simple_paths(self):
525
        """Compare against the empty string."""
526
        self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
527
        self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
528
529
    def test_tricky_paths(self):
530
        self.assertCmpPathByDirblock([
531
            # Contents of ''
532
            '', 'a', 'a-a', 'a=a', 'b',
533
            # Contents of 'a'
534
            'a/a', 'a/a-a', 'a/a=a', 'a/b',
535
            # Contents of 'a/a'
536
            'a/a/a', 'a/a/a-a', 'a/a/a=a',
537
            # Contents of 'a/a/a'
538
            'a/a/a/a', 'a/a/a/b',
539
            # Contents of 'a/a/a-a',
540
            'a/a/a-a/a', 'a/a/a-a/b',
541
            # Contents of 'a/a/a=a',
542
            'a/a/a=a/a', 'a/a/a=a/b',
543
            # Contents of 'a/a-a'
544
            'a/a-a/a',
545
            # Contents of 'a/a-a/a'
546
            'a/a-a/a/a', 'a/a-a/a/b',
547
            # Contents of 'a/a=a'
548
            'a/a=a/a',
549
            # Contents of 'a/b'
550
            'a/b/a', 'a/b/b',
551
            # Contents of 'a-a',
552
            'a-a/a', 'a-a/b',
553
            # Contents of 'a=a',
554
            'a=a/a', 'a=a/b',
555
            # Contents of 'b',
556
            'b/a', 'b/b',
557
            ])
558
        self.assertCmpPathByDirblock([
559
                 # content of '/'
560
                 '', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
561
                 # content of 'a/'
562
                 'a/a', 'a/a-a', 'a/a-z',
563
                 'a/a=a', 'a/a=z',
564
                 'a/z', 'a/z-a', 'a/z-z',
565
                 'a/z=a', 'a/z=z',
566
                 # content of 'a/a/'
567
                 'a/a/a', 'a/a/z',
568
                 # content of 'a/a-a'
569
                 'a/a-a/a',
570
                 # content of 'a/a-z'
571
                 'a/a-z/z',
572
                 # content of 'a/a=a'
573
                 'a/a=a/a',
574
                 # content of 'a/a=z'
575
                 'a/a=z/z',
576
                 # content of 'a/z/'
577
                 'a/z/a', 'a/z/z',
578
                 # content of 'a-a'
579
                 'a-a/a',
580
                 # content of 'a-z'
581
                 'a-z/z',
582
                 # content of 'a=a'
583
                 'a=a/a',
584
                 # content of 'a=z'
585
                 'a=z/z',
586
                ])
587
2474.1.70 by John Arbash Meinel
Lot's of fixes from Martin's comments.
588
    def test_unicode_not_allowed(self):
589
        cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
590
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
591
        self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
592
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
593
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
594
        self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
595
        self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
596
597
    def test_nonascii(self):
598
        self.assertCmpPathByDirblock([
599
            # content of '/'
600
            '', 'a', '\xc2\xb5', '\xc3\xa5',
601
            # content of 'a'
602
            'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
603
            # content of 'a/a'
604
            'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
605
            # content of 'a/\xc2\xb5'
606
            'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
607
            # content of 'a/\xc3\xa5'
608
            'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
609
            # content of '\xc2\xb5'
610
            '\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
611
            # content of '\xc2\xe5'
612
            '\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
613
            ])
614
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
615
616
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
2474.1.66 by John Arbash Meinel
Some restructuring.
617
    """Test the pyrex implementation of _cmp_path_by_dirblock"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
618
619
    _test_needs_features = [CompiledDirstateHelpersFeature]
620
621
    def get_cmp_by_dirs(self):
2474.1.66 by John Arbash Meinel
Some restructuring.
622
        from bzrlib._dirstate_helpers_c import _cmp_path_by_dirblock_c
623
        return _cmp_path_by_dirblock_c
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
624
625
626
class TestMemRChr(tests.TestCase):
2474.1.66 by John Arbash Meinel
Some restructuring.
627
    """Test memrchr functionality"""
2474.1.58 by John Arbash Meinel
(broken) Try to properly implement DirState._bisect*
628
629
    _test_needs_features = [CompiledDirstateHelpersFeature]
630
631
    def assertMemRChr(self, expected, s, c):
632
        from bzrlib._dirstate_helpers_c import _py_memrchr
633
        self.assertEqual(expected, _py_memrchr(s, c))
634
635
    def test_missing(self):
636
        self.assertMemRChr(None, '', 'a')
637
        self.assertMemRChr(None, '', 'c')
638
        self.assertMemRChr(None, 'abcdefghijklm', 'q')
639
        self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
640
641
    def test_single_entry(self):
642
        self.assertMemRChr(0, 'abcdefghijklm', 'a')
643
        self.assertMemRChr(1, 'abcdefghijklm', 'b')
644
        self.assertMemRChr(2, 'abcdefghijklm', 'c')
645
        self.assertMemRChr(10, 'abcdefghijklm', 'k')
646
        self.assertMemRChr(11, 'abcdefghijklm', 'l')
647
        self.assertMemRChr(12, 'abcdefghijklm', 'm')
648
649
    def test_multiple(self):
650
        self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
651
        self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
652
        self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
653
        self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
654
        self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
655
        self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
656
        self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
657
658
    def test_with_nulls(self):
659
        self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
660
        self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
661
        self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
662
        self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
663
        self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
664
        self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
665
        self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
666
        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.
667
668
669
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
670
    """Test an implementation of _read_dirblocks()
671
672
    _read_dirblocks() reads in all of the dirblock information from the disk
673
    file.
674
675
    Child test cases can override ``get_read_dirblocks`` to test a specific
676
    implementation.
677
    """
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
678
679
    def get_read_dirblocks(self):
680
        from bzrlib._dirstate_helpers_py import _read_dirblocks_py
681
        return _read_dirblocks_py
682
683
    def test_smoketest(self):
684
        """Make sure that we can create and read back a simple file."""
685
        tree, state, expected = self.create_basic_dirstate()
686
        del tree
687
        state._read_header_if_needed()
688
        self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
689
                         state._dirblock_state)
690
        read_dirblocks = self.get_read_dirblocks()
691
        read_dirblocks(state)
692
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
693
                         state._dirblock_state)
694
3640.2.1 by John Arbash Meinel
More safety checks around PyString_FromStringAndSize,
695
    def test_trailing_garbage(self):
696
        tree, state, expected = self.create_basic_dirstate()
697
        # We can modify the file as long as it hasn't been read yet.
698
        f = open('dirstate', 'ab')
699
        try:
700
            # Add bogus trailing garbage
701
            f.write('bogus\n')
702
        finally:
703
            f.close()
3640.2.5 by John Arbash Meinel
Change from using AssertionError to using DirstateCorrupt in a few places
704
        e = self.assertRaises(errors.DirstateCorrupt,
705
                              state._read_dirblocks_if_needed)
3640.2.1 by John Arbash Meinel
More safety checks around PyString_FromStringAndSize,
706
        # Make sure we mention the bogus characters in the error
707
        self.assertContainsRe(str(e), 'bogus')
708
2474.1.63 by John Arbash Meinel
Found a small bug in the python version of _read_dirblocks.
709
710
class TestCompiledReadDirblocks(TestReadDirblocks):
2474.1.68 by John Arbash Meinel
Review feedback from Martin, mostly documentation updates.
711
    """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.
712
713
    _test_needs_features = [CompiledDirstateHelpersFeature]
714
715
    def get_read_dirblocks(self):
716
        from bzrlib._dirstate_helpers_c import _read_dirblocks_c
717
        return _read_dirblocks_c
2474.1.66 by John Arbash Meinel
Some restructuring.
718
719
720
class TestUsingCompiledIfAvailable(tests.TestCase):
721
    """Check that any compiled functions that are available are the default.
722
723
    It is possible to have typos, etc in the import line, such that
724
    _dirstate_helpers_c is actually available, but the compiled functions are
725
    not being used.
726
    """
727
728
    def test_bisect_dirblock(self):
729
        if CompiledDirstateHelpersFeature.available():
730
            from bzrlib._dirstate_helpers_c import bisect_dirblock_c
731
            self.assertIs(bisect_dirblock_c, dirstate.bisect_dirblock)
732
        else:
733
            from bzrlib._dirstate_helpers_py import bisect_dirblock_py
734
            self.assertIs(bisect_dirblock_py, dirstate.bisect_dirblock)
735
736
    def test__bisect_path_left(self):
737
        if CompiledDirstateHelpersFeature.available():
738
            from bzrlib._dirstate_helpers_c import _bisect_path_left_c
739
            self.assertIs(_bisect_path_left_c, dirstate._bisect_path_left)
740
        else:
741
            from bzrlib._dirstate_helpers_py import _bisect_path_left_py
742
            self.assertIs(_bisect_path_left_py, dirstate._bisect_path_left)
743
744
    def test__bisect_path_right(self):
745
        if CompiledDirstateHelpersFeature.available():
746
            from bzrlib._dirstate_helpers_c import _bisect_path_right_c
747
            self.assertIs(_bisect_path_right_c, dirstate._bisect_path_right)
748
        else:
749
            from bzrlib._dirstate_helpers_py import _bisect_path_right_py
750
            self.assertIs(_bisect_path_right_py, dirstate._bisect_path_right)
751
752
    def test_cmp_by_dirs(self):
753
        if CompiledDirstateHelpersFeature.available():
754
            from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
755
            self.assertIs(cmp_by_dirs_c, dirstate.cmp_by_dirs)
756
        else:
757
            from bzrlib._dirstate_helpers_py import cmp_by_dirs_py
758
            self.assertIs(cmp_by_dirs_py, dirstate.cmp_by_dirs)
759
760
    def test__read_dirblocks(self):
761
        if CompiledDirstateHelpersFeature.available():
762
            from bzrlib._dirstate_helpers_c import _read_dirblocks_c
763
            self.assertIs(_read_dirblocks_c, dirstate._read_dirblocks)
764
        else:
765
            from bzrlib._dirstate_helpers_py import _read_dirblocks_py
766
            self.assertIs(_read_dirblocks_py, dirstate._read_dirblocks)
767