~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test__dirstate_helpers.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007, 2008 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Tests for the compiled dirstate helpers."""
 
18
 
 
19
import bisect
 
20
import os
 
21
 
 
22
from bzrlib import (
 
23
    dirstate,
 
24
    errors,
 
25
    tests,
 
26
    )
 
27
from bzrlib.tests import test_dirstate
 
28
 
 
29
 
 
30
class _CompiledDirstateHelpersFeature(tests.Feature):
 
31
    def _probe(self):
 
32
        try:
 
33
            import bzrlib._dirstate_helpers_c
 
34
        except ImportError:
 
35
            return False
 
36
        return True
 
37
 
 
38
    def feature_name(self):
 
39
        return 'bzrlib._dirstate_helpers_c'
 
40
 
 
41
CompiledDirstateHelpersFeature = _CompiledDirstateHelpersFeature()
 
42
 
 
43
 
 
44
class TestBisectPathMixin(object):
 
45
    """Test that _bisect_path_*() returns the expected values.
 
46
 
 
47
    _bisect_path_* is intended to work like bisect.bisect_*() except it
 
48
    knows it is working on paths that are sorted by ('path', 'to', 'foo')
 
49
    chunks rather than by raw 'path/to/foo'.
 
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.
 
54
    """
 
55
 
 
56
    def get_bisect_path(self):
 
57
        """Return an implementation of _bisect_path_*"""
 
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):
 
200
    """Run all Bisect Path tests against _bisect_path_left_py."""
 
201
 
 
202
    def get_bisect_path(self):
 
203
        from bzrlib._dirstate_helpers_py import _bisect_path_left_py
 
204
        return _bisect_path_left_py
 
205
 
 
206
    def get_bisect(self):
 
207
        return bisect.bisect_left, 0
 
208
 
 
209
 
 
210
class TestCompiledBisectPathLeft(TestBisectPathLeft):
 
211
    """Run all Bisect Path tests against _bisect_path_right_c"""
 
212
 
 
213
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
214
 
 
215
    def get_bisect_path(self):
 
216
        from bzrlib._dirstate_helpers_c import _bisect_path_left_c
 
217
        return _bisect_path_left_c
 
218
 
 
219
 
 
220
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
 
221
    """Run all Bisect Path tests against _bisect_path_right_py"""
 
222
 
 
223
    def get_bisect_path(self):
 
224
        from bzrlib._dirstate_helpers_py import _bisect_path_right_py
 
225
        return _bisect_path_right_py
 
226
 
 
227
    def get_bisect(self):
 
228
        return bisect.bisect_right, -1
 
229
 
 
230
 
 
231
class TestCompiledBisectPathRight(TestBisectPathRight):
 
232
    """Run all Bisect Path tests against _bisect_path_right_c"""
 
233
 
 
234
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
235
 
 
236
    def get_bisect_path(self):
 
237
        from bzrlib._dirstate_helpers_c import _bisect_path_right_c
 
238
        return _bisect_path_right_c
 
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'.
 
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.
 
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
 
 
353
class TestCmpByDirs(tests.TestCase):
 
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
    """
 
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
 
 
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
 
 
464
 
 
465
class TestCompiledCmpByDirs(TestCmpByDirs):
 
466
    """Test the pyrex implementation of cmp_by_dirs"""
 
467
 
 
468
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
469
 
 
470
    def get_cmp_by_dirs(self):
 
471
        from bzrlib._dirstate_helpers_c import cmp_by_dirs_c
 
472
        return cmp_by_dirs_c
 
473
 
 
474
 
 
475
class TestCmpPathByDirblock(tests.TestCase):
 
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
    """
 
484
 
 
485
    def get_cmp_path_by_dirblock(self):
 
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
 
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
 
 
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
 
 
615
 
 
616
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
 
617
    """Test the pyrex implementation of _cmp_path_by_dirblock"""
 
618
 
 
619
    _test_needs_features = [CompiledDirstateHelpersFeature]
 
620
 
 
621
    def get_cmp_by_dirs(self):
 
622
        from bzrlib._dirstate_helpers_c import _cmp_path_by_dirblock_c
 
623
        return _cmp_path_by_dirblock_c
 
624
 
 
625
 
 
626
class TestMemRChr(tests.TestCase):
 
627
    """Test memrchr functionality"""
 
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')
 
667
 
 
668
 
 
669
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
 
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
    """
 
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
 
 
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()
 
704
        e = self.assertRaises(errors.DirstateCorrupt,
 
705
                              state._read_dirblocks_if_needed)
 
706
        # Make sure we mention the bogus characters in the error
 
707
        self.assertContainsRe(str(e), 'bogus')
 
708
 
 
709
 
 
710
class TestCompiledReadDirblocks(TestReadDirblocks):
 
711
    """Test the pyrex implementation 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
 
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