1
# Copyright (C) 2007-2011 Canonical Ltd
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for the compiled dirstate helpers."""
29
from bzrlib.tests import (
32
from bzrlib.tests.test_osutils import dir_reader_scenarios
33
from bzrlib.tests.scenarios import (
34
load_tests_apply_scenarios,
39
load_tests = load_tests_apply_scenarios
42
compiled_dirstate_helpers_feature = tests.ModuleAvailableFeature(
43
'bzrlib._dirstate_helpers_pyx')
46
# FIXME: we should also parametrize against SHA1Provider !
48
ue_scenarios = [('dirstate_Python',
49
{'update_entry': dirstate.py_update_entry})]
50
if compiled_dirstate_helpers_feature.available():
51
update_entry = compiled_dirstate_helpers_feature.module.update_entry
52
ue_scenarios.append(('dirstate_Pyrex', {'update_entry': update_entry}))
54
pe_scenarios = [('dirstate_Python',
55
{'_process_entry': dirstate.ProcessEntryPython})]
56
if compiled_dirstate_helpers_feature.available():
57
process_entry = compiled_dirstate_helpers_feature.module.ProcessEntryC
58
pe_scenarios.append(('dirstate_Pyrex', {'_process_entry': process_entry}))
61
class TestBisectPathMixin(object):
62
"""Test that _bisect_path_*() returns the expected values.
64
_bisect_path_* is intended to work like bisect.bisect_*() except it
65
knows it is working on paths that are sorted by ('path', 'to', 'foo')
66
chunks rather than by raw 'path/to/foo'.
68
Test Cases should inherit from this and override ``get_bisect_path`` return
69
their implementation, and ``get_bisect`` to return the matching
70
bisect.bisect_* function.
73
def get_bisect_path(self):
74
"""Return an implementation of _bisect_path_*"""
75
raise NotImplementedError
78
"""Return a version of bisect.bisect_*.
80
Also, for the 'exists' check, return the offset to the real values.
81
For example bisect_left returns the index of an entry, while
82
bisect_right returns the index *after* an entry
84
:return: (bisect_func, offset)
86
raise NotImplementedError
88
def assertBisect(self, paths, split_paths, path, exists=True):
89
"""Assert that bisect_split works like bisect_left on the split paths.
91
:param paths: A list of path names
92
:param split_paths: A list of path names that are already split up by directory
93
('path/to/foo' => ('path', 'to', 'foo'))
94
:param path: The path we are indexing.
95
:param exists: The path should be present, so make sure the
96
final location actually points to the right value.
98
All other arguments will be passed along.
100
bisect_path = self.get_bisect_path()
101
self.assertIsInstance(paths, list)
102
bisect_path_idx = bisect_path(paths, path)
103
split_path = self.split_for_dirblocks([path])[0]
104
bisect_func, offset = self.get_bisect()
105
bisect_split_idx = bisect_func(split_paths, split_path)
106
self.assertEqual(bisect_split_idx, bisect_path_idx,
107
'%s disagreed. %s != %s'
109
% (bisect_path.__name__,
110
bisect_split_idx, bisect_path_idx, path)
113
self.assertEqual(path, paths[bisect_path_idx+offset])
115
def split_for_dirblocks(self, paths):
118
dirname, basename = os.path.split(path)
119
dir_split_paths.append((dirname.split('/'), basename))
120
dir_split_paths.sort()
121
return dir_split_paths
123
def test_simple(self):
124
"""In the simple case it works just like bisect_left"""
125
paths = ['', 'a', 'b', 'c', 'd']
126
split_paths = self.split_for_dirblocks(paths)
128
self.assertBisect(paths, split_paths, path, exists=True)
129
self.assertBisect(paths, split_paths, '_', exists=False)
130
self.assertBisect(paths, split_paths, 'aa', exists=False)
131
self.assertBisect(paths, split_paths, 'bb', exists=False)
132
self.assertBisect(paths, split_paths, 'cc', exists=False)
133
self.assertBisect(paths, split_paths, 'dd', exists=False)
134
self.assertBisect(paths, split_paths, 'a/a', exists=False)
135
self.assertBisect(paths, split_paths, 'b/b', exists=False)
136
self.assertBisect(paths, split_paths, 'c/c', exists=False)
137
self.assertBisect(paths, split_paths, 'd/d', exists=False)
139
def test_involved(self):
140
"""This is where bisect_path_* diverges slightly."""
141
# This is the list of paths and their contents
169
# This is the exact order that is stored by dirstate
170
# All children in a directory are mentioned before an children of
171
# children are mentioned.
172
# So all the root-directory paths, then all the
173
# first sub directory, etc.
174
paths = [# content of '/'
175
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
177
'a/a', 'a/a-a', 'a/a-z',
179
'a/z', 'a/z-a', 'a/z-z',
202
split_paths = self.split_for_dirblocks(paths)
204
for dir_parts, basename in split_paths:
205
if dir_parts == ['']:
206
sorted_paths.append(basename)
208
sorted_paths.append('/'.join(dir_parts + [basename]))
210
self.assertEqual(sorted_paths, paths)
213
self.assertBisect(paths, split_paths, path, exists=True)
216
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
217
"""Run all Bisect Path tests against _bisect_path_left."""
219
def get_bisect_path(self):
220
from bzrlib._dirstate_helpers_py import _bisect_path_left
221
return _bisect_path_left
223
def get_bisect(self):
224
return bisect.bisect_left, 0
227
class TestCompiledBisectPathLeft(TestBisectPathLeft):
228
"""Run all Bisect Path tests against _bisect_path_lect"""
230
_test_needs_features = [compiled_dirstate_helpers_feature]
232
def get_bisect_path(self):
233
from bzrlib._dirstate_helpers_pyx import _bisect_path_left
234
return _bisect_path_left
237
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
238
"""Run all Bisect Path tests against _bisect_path_right"""
240
def get_bisect_path(self):
241
from bzrlib._dirstate_helpers_py import _bisect_path_right
242
return _bisect_path_right
244
def get_bisect(self):
245
return bisect.bisect_right, -1
248
class TestCompiledBisectPathRight(TestBisectPathRight):
249
"""Run all Bisect Path tests against _bisect_path_right"""
251
_test_needs_features = [compiled_dirstate_helpers_feature]
253
def get_bisect_path(self):
254
from bzrlib._dirstate_helpers_pyx import _bisect_path_right
255
return _bisect_path_right
258
class TestBisectDirblock(tests.TestCase):
259
"""Test that bisect_dirblock() returns the expected values.
261
bisect_dirblock is intended to work like bisect.bisect_left() except it
262
knows it is working on dirblocks and that dirblocks are sorted by ('path',
263
'to', 'foo') chunks rather than by raw 'path/to/foo'.
265
This test is parameterized by calling get_bisect_dirblock(). Child test
266
cases can override this function to test against a different
270
def get_bisect_dirblock(self):
271
"""Return an implementation of bisect_dirblock"""
272
from bzrlib._dirstate_helpers_py import bisect_dirblock
273
return bisect_dirblock
275
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
276
"""Assert that bisect_split works like bisect_left on the split paths.
278
:param dirblocks: A list of (path, [info]) pairs.
279
:param split_dirblocks: A list of ((split, path), [info]) pairs.
280
:param path: The path we are indexing.
282
All other arguments will be passed along.
284
bisect_dirblock = self.get_bisect_dirblock()
285
self.assertIsInstance(dirblocks, list)
286
bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
287
split_dirblock = (path.split('/'), [])
288
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
290
self.assertEqual(bisect_left_idx, bisect_split_idx,
291
'bisect_split disagreed. %s != %s'
293
% (bisect_left_idx, bisect_split_idx, path)
296
def paths_to_dirblocks(self, paths):
297
"""Convert a list of paths into dirblock form.
299
Also, ensure that the paths are in proper sorted order.
301
dirblocks = [(path, []) for path in paths]
302
split_dirblocks = [(path.split('/'), []) for path in paths]
303
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
304
return dirblocks, split_dirblocks
306
def test_simple(self):
307
"""In the simple case it works just like bisect_left"""
308
paths = ['', 'a', 'b', 'c', 'd']
309
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
311
self.assertBisect(dirblocks, split_dirblocks, path)
312
self.assertBisect(dirblocks, split_dirblocks, '_')
313
self.assertBisect(dirblocks, split_dirblocks, 'aa')
314
self.assertBisect(dirblocks, split_dirblocks, 'bb')
315
self.assertBisect(dirblocks, split_dirblocks, 'cc')
316
self.assertBisect(dirblocks, split_dirblocks, 'dd')
317
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
318
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
319
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
320
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
322
def test_involved(self):
323
"""This is where bisect_left diverges slightly."""
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',
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',
332
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
334
self.assertBisect(dirblocks, split_dirblocks, path)
336
def test_involved_cached(self):
337
"""This is where bisect_left diverges slightly."""
339
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
340
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
342
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
343
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
347
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
349
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
352
class TestCompiledBisectDirblock(TestBisectDirblock):
353
"""Test that bisect_dirblock() returns the expected values.
355
bisect_dirblock is intended to work like bisect.bisect_left() except it
356
knows it is working on dirblocks and that dirblocks are sorted by ('path',
357
'to', 'foo') chunks rather than by raw 'path/to/foo'.
359
This runs all the normal tests that TestBisectDirblock did, but uses the
363
_test_needs_features = [compiled_dirstate_helpers_feature]
365
def get_bisect_dirblock(self):
366
from bzrlib._dirstate_helpers_pyx import bisect_dirblock
367
return bisect_dirblock
370
class TestCmpByDirs(tests.TestCase):
371
"""Test an implementation of cmp_by_dirs()
373
cmp_by_dirs() compares 2 paths by their directory sections, rather than as
376
Child test cases can override ``get_cmp_by_dirs`` to test a specific
380
def get_cmp_by_dirs(self):
381
"""Get a specific implementation of cmp_by_dirs."""
382
from bzrlib._dirstate_helpers_py import cmp_by_dirs
385
def assertCmpByDirs(self, expected, str1, str2):
386
"""Compare the two strings, in both directions.
388
:param expected: The expected comparison value. -1 means str1 comes
389
first, 0 means they are equal, 1 means str2 comes first
390
:param str1: string to compare
391
:param str2: string to compare
393
cmp_by_dirs = self.get_cmp_by_dirs()
395
self.assertEqual(str1, str2)
396
self.assertEqual(0, cmp_by_dirs(str1, str2))
397
self.assertEqual(0, cmp_by_dirs(str2, str1))
399
self.assertPositive(cmp_by_dirs(str1, str2))
400
self.assertNegative(cmp_by_dirs(str2, str1))
402
self.assertNegative(cmp_by_dirs(str1, str2))
403
self.assertPositive(cmp_by_dirs(str2, str1))
405
def test_cmp_empty(self):
406
"""Compare against the empty string."""
407
self.assertCmpByDirs(0, '', '')
408
self.assertCmpByDirs(1, 'a', '')
409
self.assertCmpByDirs(1, 'ab', '')
410
self.assertCmpByDirs(1, 'abc', '')
411
self.assertCmpByDirs(1, 'abcd', '')
412
self.assertCmpByDirs(1, 'abcde', '')
413
self.assertCmpByDirs(1, 'abcdef', '')
414
self.assertCmpByDirs(1, 'abcdefg', '')
415
self.assertCmpByDirs(1, 'abcdefgh', '')
416
self.assertCmpByDirs(1, 'abcdefghi', '')
417
self.assertCmpByDirs(1, 'test/ing/a/path/', '')
419
def test_cmp_same_str(self):
420
"""Compare the same string"""
421
self.assertCmpByDirs(0, 'a', 'a')
422
self.assertCmpByDirs(0, 'ab', 'ab')
423
self.assertCmpByDirs(0, 'abc', 'abc')
424
self.assertCmpByDirs(0, 'abcd', 'abcd')
425
self.assertCmpByDirs(0, 'abcde', 'abcde')
426
self.assertCmpByDirs(0, 'abcdef', 'abcdef')
427
self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
428
self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
429
self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
430
self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
431
self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
432
self.assertCmpByDirs(0, 'a/b', 'a/b')
433
self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
434
self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
435
self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
437
def test_simple_paths(self):
438
"""Compare strings that act like normal string comparison"""
439
self.assertCmpByDirs(-1, 'a', 'b')
440
self.assertCmpByDirs(-1, 'aa', 'ab')
441
self.assertCmpByDirs(-1, 'ab', 'bb')
442
self.assertCmpByDirs(-1, 'aaa', 'aab')
443
self.assertCmpByDirs(-1, 'aab', 'abb')
444
self.assertCmpByDirs(-1, 'abb', 'bbb')
445
self.assertCmpByDirs(-1, 'aaaa', 'aaab')
446
self.assertCmpByDirs(-1, 'aaab', 'aabb')
447
self.assertCmpByDirs(-1, 'aabb', 'abbb')
448
self.assertCmpByDirs(-1, 'abbb', 'bbbb')
449
self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
450
self.assertCmpByDirs(-1, 'a/a', 'a/b')
451
self.assertCmpByDirs(-1, 'a/b', 'b/b')
452
self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
453
self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
454
self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
455
self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
456
self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
457
self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
458
self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
459
self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
461
def test_tricky_paths(self):
462
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
463
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
464
self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
465
self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
466
self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
468
def test_cmp_unicode_not_allowed(self):
469
cmp_by_dirs = self.get_cmp_by_dirs()
470
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
471
self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
472
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
474
def test_cmp_non_ascii(self):
475
self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
476
self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
477
self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
478
self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
479
self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
482
class TestCompiledCmpByDirs(TestCmpByDirs):
483
"""Test the pyrex implementation of cmp_by_dirs"""
485
_test_needs_features = [compiled_dirstate_helpers_feature]
487
def get_cmp_by_dirs(self):
488
from bzrlib._dirstate_helpers_pyx import cmp_by_dirs
492
class TestCmpPathByDirblock(tests.TestCase):
493
"""Test an implementation of _cmp_path_by_dirblock()
495
_cmp_path_by_dirblock() compares two paths using the sort order used by
496
DirState. All paths in the same directory are sorted together.
498
Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
502
def get_cmp_path_by_dirblock(self):
503
"""Get a specific implementation of _cmp_path_by_dirblock."""
504
from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock
505
return _cmp_path_by_dirblock
507
def assertCmpPathByDirblock(self, paths):
508
"""Compare all paths and make sure they evaluate to the correct order.
510
This does N^2 comparisons. It is assumed that ``paths`` is properly
513
:param paths: a sorted list of paths to compare
515
# First, make sure the paths being passed in are correct
517
dirname, basename = os.path.split(p)
518
return dirname.split('/'), basename
519
self.assertEqual(sorted(paths, key=_key), paths)
521
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
522
for idx1, path1 in enumerate(paths):
523
for idx2, path2 in enumerate(paths):
524
cmp_val = cmp_path_by_dirblock(path1, path2)
526
self.assertTrue(cmp_val < 0,
527
'%s did not state that %r came before %r, cmp=%s'
528
% (cmp_path_by_dirblock.__name__,
529
path1, path2, cmp_val))
531
self.assertTrue(cmp_val > 0,
532
'%s did not state that %r came after %r, cmp=%s'
533
% (cmp_path_by_dirblock.__name__,
534
path1, path2, cmp_val))
536
self.assertTrue(cmp_val == 0,
537
'%s did not state that %r == %r, cmp=%s'
538
% (cmp_path_by_dirblock.__name__,
539
path1, path2, cmp_val))
541
def test_cmp_simple_paths(self):
542
"""Compare against the empty string."""
543
self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
544
self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
546
def test_tricky_paths(self):
547
self.assertCmpPathByDirblock([
549
'', 'a', 'a-a', 'a=a', 'b',
551
'a/a', 'a/a-a', 'a/a=a', 'a/b',
553
'a/a/a', 'a/a/a-a', 'a/a/a=a',
554
# Contents of 'a/a/a'
555
'a/a/a/a', 'a/a/a/b',
556
# Contents of 'a/a/a-a',
557
'a/a/a-a/a', 'a/a/a-a/b',
558
# Contents of 'a/a/a=a',
559
'a/a/a=a/a', 'a/a/a=a/b',
560
# Contents of 'a/a-a'
562
# Contents of 'a/a-a/a'
563
'a/a-a/a/a', 'a/a-a/a/b',
564
# Contents of 'a/a=a'
575
self.assertCmpPathByDirblock([
577
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
579
'a/a', 'a/a-a', 'a/a-z',
581
'a/z', 'a/z-a', 'a/z-z',
605
def test_unicode_not_allowed(self):
606
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
607
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
608
self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
609
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
610
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
611
self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
612
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
614
def test_nonascii(self):
615
self.assertCmpPathByDirblock([
617
'', 'a', '\xc2\xb5', '\xc3\xa5',
619
'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
621
'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
622
# content of 'a/\xc2\xb5'
623
'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
624
# content of 'a/\xc3\xa5'
625
'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
626
# content of '\xc2\xb5'
627
'\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
628
# content of '\xc2\xe5'
629
'\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
633
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
634
"""Test the pyrex implementation of _cmp_path_by_dirblock"""
636
_test_needs_features = [compiled_dirstate_helpers_feature]
638
def get_cmp_by_dirs(self):
639
from bzrlib._dirstate_helpers_pyx import _cmp_path_by_dirblock
640
return _cmp_path_by_dirblock
643
class TestMemRChr(tests.TestCase):
644
"""Test memrchr functionality"""
646
_test_needs_features = [compiled_dirstate_helpers_feature]
648
def assertMemRChr(self, expected, s, c):
649
from bzrlib._dirstate_helpers_pyx import _py_memrchr
650
self.assertEqual(expected, _py_memrchr(s, c))
652
def test_missing(self):
653
self.assertMemRChr(None, '', 'a')
654
self.assertMemRChr(None, '', 'c')
655
self.assertMemRChr(None, 'abcdefghijklm', 'q')
656
self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
658
def test_single_entry(self):
659
self.assertMemRChr(0, 'abcdefghijklm', 'a')
660
self.assertMemRChr(1, 'abcdefghijklm', 'b')
661
self.assertMemRChr(2, 'abcdefghijklm', 'c')
662
self.assertMemRChr(10, 'abcdefghijklm', 'k')
663
self.assertMemRChr(11, 'abcdefghijklm', 'l')
664
self.assertMemRChr(12, 'abcdefghijklm', 'm')
666
def test_multiple(self):
667
self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
668
self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
669
self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
670
self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
671
self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
672
self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
673
self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
675
def test_with_nulls(self):
676
self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
677
self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
678
self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
679
self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
680
self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
681
self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
682
self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
683
self.assertMemRChr(9, '\0\0\0\0\0\0\0\0\0\0', '\0')
686
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
687
"""Test an implementation of _read_dirblocks()
689
_read_dirblocks() reads in all of the dirblock information from the disk
692
Child test cases can override ``get_read_dirblocks`` to test a specific
696
# inherits scenarios from test_dirstate
698
def get_read_dirblocks(self):
699
from bzrlib._dirstate_helpers_py import _read_dirblocks
700
return _read_dirblocks
702
def test_smoketest(self):
703
"""Make sure that we can create and read back a simple file."""
704
tree, state, expected = self.create_basic_dirstate()
706
state._read_header_if_needed()
707
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
708
state._dirblock_state)
709
read_dirblocks = self.get_read_dirblocks()
710
read_dirblocks(state)
711
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
712
state._dirblock_state)
714
def test_trailing_garbage(self):
715
tree, state, expected = self.create_basic_dirstate()
716
# On Unix, we can write extra data as long as we haven't read yet, but
717
# on Win32, if you've opened the file with FILE_SHARE_READ, trying to
718
# open it in append mode will fail.
720
f = open('dirstate', 'ab')
722
# Add bogus trailing garbage
727
e = self.assertRaises(errors.DirstateCorrupt,
728
state._read_dirblocks_if_needed)
729
# Make sure we mention the bogus characters in the error
730
self.assertContainsRe(str(e), 'bogus')
733
class TestCompiledReadDirblocks(TestReadDirblocks):
734
"""Test the pyrex implementation of _read_dirblocks"""
736
_test_needs_features = [compiled_dirstate_helpers_feature]
738
def get_read_dirblocks(self):
739
from bzrlib._dirstate_helpers_pyx import _read_dirblocks
740
return _read_dirblocks
743
class TestUsingCompiledIfAvailable(tests.TestCase):
744
"""Check that any compiled functions that are available are the default.
746
It is possible to have typos, etc in the import line, such that
747
_dirstate_helpers_pyx is actually available, but the compiled functions are
751
def test_bisect_dirblock(self):
752
if compiled_dirstate_helpers_feature.available():
753
from bzrlib._dirstate_helpers_pyx import bisect_dirblock
755
from bzrlib._dirstate_helpers_py import bisect_dirblock
756
self.assertIs(bisect_dirblock, dirstate.bisect_dirblock)
758
def test__bisect_path_left(self):
759
if compiled_dirstate_helpers_feature.available():
760
from bzrlib._dirstate_helpers_pyx import _bisect_path_left
762
from bzrlib._dirstate_helpers_py import _bisect_path_left
763
self.assertIs(_bisect_path_left, dirstate._bisect_path_left)
765
def test__bisect_path_right(self):
766
if compiled_dirstate_helpers_feature.available():
767
from bzrlib._dirstate_helpers_pyx import _bisect_path_right
769
from bzrlib._dirstate_helpers_py import _bisect_path_right
770
self.assertIs(_bisect_path_right, dirstate._bisect_path_right)
772
def test_cmp_by_dirs(self):
773
if compiled_dirstate_helpers_feature.available():
774
from bzrlib._dirstate_helpers_pyx import cmp_by_dirs
776
from bzrlib._dirstate_helpers_py import cmp_by_dirs
777
self.assertIs(cmp_by_dirs, dirstate.cmp_by_dirs)
779
def test__read_dirblocks(self):
780
if compiled_dirstate_helpers_feature.available():
781
from bzrlib._dirstate_helpers_pyx import _read_dirblocks
783
from bzrlib._dirstate_helpers_py import _read_dirblocks
784
self.assertIs(_read_dirblocks, dirstate._read_dirblocks)
786
def test_update_entry(self):
787
if compiled_dirstate_helpers_feature.available():
788
from bzrlib._dirstate_helpers_pyx import update_entry
790
from bzrlib.dirstate import update_entry
791
self.assertIs(update_entry, dirstate.update_entry)
793
def test_process_entry(self):
794
if compiled_dirstate_helpers_feature.available():
795
from bzrlib._dirstate_helpers_pyx import ProcessEntryC
796
self.assertIs(ProcessEntryC, dirstate._process_entry)
798
from bzrlib.dirstate import ProcessEntryPython
799
self.assertIs(ProcessEntryPython, dirstate._process_entry)
802
class TestUpdateEntry(test_dirstate.TestCaseWithDirState):
803
"""Test the DirState.update_entry functions"""
805
scenarios = multiply_scenarios(
806
dir_reader_scenarios(), ue_scenarios)
812
super(TestUpdateEntry, self).setUp()
813
self.overrideAttr(dirstate, 'update_entry', self.update_entry)
815
def get_state_with_a(self):
816
"""Create a DirState tracking a single object named 'a'"""
817
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
818
self.addCleanup(state.unlock)
819
state.add('a', 'a-id', 'file', None, '')
820
entry = state._get_entry(0, path_utf8='a')
823
def test_observed_sha1_cachable(self):
824
state, entry = self.get_state_with_a()
826
atime = time.time() - 10
827
self.build_tree(['a'])
828
statvalue = test_dirstate._FakeStat.from_stat(os.lstat('a'))
829
statvalue.st_mtime = statvalue.st_ctime = atime
830
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
831
state._dirblock_state)
832
state._observed_sha1(entry, "foo", statvalue)
833
self.assertEqual('foo', entry[1][0][1])
834
packed_stat = dirstate.pack_stat(statvalue)
835
self.assertEqual(packed_stat, entry[1][0][4])
836
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
837
state._dirblock_state)
839
def test_observed_sha1_not_cachable(self):
840
state, entry = self.get_state_with_a()
842
oldval = entry[1][0][1]
843
oldstat = entry[1][0][4]
844
self.build_tree(['a'])
845
statvalue = os.lstat('a')
846
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
847
state._dirblock_state)
848
state._observed_sha1(entry, "foo", statvalue)
849
self.assertEqual(oldval, entry[1][0][1])
850
self.assertEqual(oldstat, entry[1][0][4])
851
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
852
state._dirblock_state)
854
def test_update_entry(self):
855
state, _ = self.get_state_with_a()
856
tree = self.make_branch_and_tree('tree')
858
empty_revid = tree.commit('empty')
859
self.build_tree(['tree/a'])
860
tree.add(['a'], ['a-id'])
861
with_a_id = tree.commit('with_a')
862
self.addCleanup(tree.unlock)
863
state.set_parent_trees(
864
[(empty_revid, tree.branch.repository.revision_tree(empty_revid))],
866
entry = state._get_entry(0, path_utf8='a')
867
self.build_tree(['a'])
868
# Add one where we don't provide the stat or sha already
869
self.assertEqual(('', 'a', 'a-id'), entry[0])
870
self.assertEqual(('f', '', 0, False, dirstate.DirState.NULLSTAT),
872
# Flush the buffers to disk
874
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
875
state._dirblock_state)
877
stat_value = os.lstat('a')
878
packed_stat = dirstate.pack_stat(stat_value)
879
link_or_sha1 = self.update_entry(state, entry, abspath='a',
880
stat_value=stat_value)
881
self.assertEqual(None, link_or_sha1)
883
# The dirblock entry should not have computed or cached the file's
884
# sha1, but it did update the files' st_size. However, this is not
885
# worth writing a dirstate file for, so we leave the state UNMODIFIED
886
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
888
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
889
state._dirblock_state)
890
mode = stat_value.st_mode
891
self.assertEqual([('is_exec', mode, False)], state._log)
894
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
895
state._dirblock_state)
897
# Roll the clock back so the file is guaranteed to look too new. We
898
# should still not compute the sha1.
899
state.adjust_time(-10)
902
link_or_sha1 = self.update_entry(state, entry, abspath='a',
903
stat_value=stat_value)
904
self.assertEqual([('is_exec', mode, False)], state._log)
905
self.assertEqual(None, link_or_sha1)
906
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
907
state._dirblock_state)
908
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
912
# If it is cachable (the clock has moved forward) but new it still
913
# won't calculate the sha or cache it.
914
state.adjust_time(+20)
916
link_or_sha1 = dirstate.update_entry(state, entry, abspath='a',
917
stat_value=stat_value)
918
self.assertEqual(None, link_or_sha1)
919
self.assertEqual([('is_exec', mode, False)], state._log)
920
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
922
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
923
state._dirblock_state)
925
# If the file is no longer new, and the clock has been moved forward
926
# sufficiently, it will cache the sha.
928
state.set_parent_trees(
929
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
931
entry = state._get_entry(0, path_utf8='a')
933
link_or_sha1 = self.update_entry(state, entry, abspath='a',
934
stat_value=stat_value)
935
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
937
self.assertEqual([('is_exec', mode, False), ('sha1', 'a')],
939
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
942
# Subsequent calls will just return the cached value
944
link_or_sha1 = self.update_entry(state, entry, abspath='a',
945
stat_value=stat_value)
946
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
948
self.assertEqual([], state._log)
949
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
952
def test_update_entry_symlink(self):
953
"""Update entry should read symlinks."""
954
self.requireFeature(tests.SymlinkFeature)
955
state, entry = self.get_state_with_a()
957
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
958
state._dirblock_state)
959
os.symlink('target', 'a')
961
state.adjust_time(-10) # Make the symlink look new
962
stat_value = os.lstat('a')
963
packed_stat = dirstate.pack_stat(stat_value)
964
link_or_sha1 = self.update_entry(state, entry, abspath='a',
965
stat_value=stat_value)
966
self.assertEqual('target', link_or_sha1)
967
self.assertEqual([('read_link', 'a', '')], state._log)
968
# Dirblock is not updated (the link is too new)
969
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
971
# The file entry turned into a symlink, that is considered
972
# HASH modified worthy.
973
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
974
state._dirblock_state)
976
# Because the stat_value looks new, we should re-read the target
978
link_or_sha1 = self.update_entry(state, entry, abspath='a',
979
stat_value=stat_value)
980
self.assertEqual('target', link_or_sha1)
981
self.assertEqual([('read_link', 'a', '')], state._log)
982
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
985
state.adjust_time(+20) # Skip into the future, all files look old
987
link_or_sha1 = self.update_entry(state, entry, abspath='a',
988
stat_value=stat_value)
989
# The symlink stayed a symlink. So while it is new enough to cache, we
990
# don't bother setting the flag, because it is not really worth saving
991
# (when we stat the symlink, we'll have paged in the target.)
992
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
993
state._dirblock_state)
994
self.assertEqual('target', link_or_sha1)
995
# We need to re-read the link because only now can we cache it
996
self.assertEqual([('read_link', 'a', '')], state._log)
997
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1001
# Another call won't re-read the link
1002
self.assertEqual([], state._log)
1003
link_or_sha1 = self.update_entry(state, entry, abspath='a',
1004
stat_value=stat_value)
1005
self.assertEqual('target', link_or_sha1)
1006
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1009
def do_update_entry(self, state, entry, abspath):
1010
stat_value = os.lstat(abspath)
1011
return self.update_entry(state, entry, abspath, stat_value)
1013
def test_update_entry_dir(self):
1014
state, entry = self.get_state_with_a()
1015
self.build_tree(['a/'])
1016
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1018
def test_update_entry_dir_unchanged(self):
1019
state, entry = self.get_state_with_a()
1020
self.build_tree(['a/'])
1021
state.adjust_time(+20)
1022
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1023
# a/ used to be a file, but is now a directory, worth saving
1024
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1025
state._dirblock_state)
1027
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1028
state._dirblock_state)
1029
# No changes to a/ means not worth saving.
1030
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1031
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1032
state._dirblock_state)
1033
# Change the last-modified time for the directory
1034
t = time.time() - 100.0
1036
os.utime('a', (t, t))
1038
# It looks like Win32 + FAT doesn't allow to change times on a dir.
1039
raise tests.TestSkipped("can't update mtime of a dir on FAT")
1040
saved_packed_stat = entry[1][0][-1]
1041
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1042
# We *do* go ahead and update the information in the dirblocks, but we
1043
# don't bother setting IN_MEMORY_MODIFIED because it is trivial to
1045
self.assertNotEqual(saved_packed_stat, entry[1][0][-1])
1046
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1047
state._dirblock_state)
1049
def test_update_entry_file_unchanged(self):
1050
state, _ = self.get_state_with_a()
1051
tree = self.make_branch_and_tree('tree')
1053
self.build_tree(['tree/a'])
1054
tree.add(['a'], ['a-id'])
1055
with_a_id = tree.commit('witha')
1056
self.addCleanup(tree.unlock)
1057
state.set_parent_trees(
1058
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
1060
entry = state._get_entry(0, path_utf8='a')
1061
self.build_tree(['a'])
1062
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1063
state.adjust_time(+20)
1064
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1065
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1066
state._dirblock_state)
1068
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1069
state._dirblock_state)
1070
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1071
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1072
state._dirblock_state)
1074
def test_update_entry_tree_reference(self):
1075
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
1076
self.addCleanup(state.unlock)
1077
state.add('r', 'r-id', 'tree-reference', None, '')
1078
self.build_tree(['r/'])
1079
entry = state._get_entry(0, path_utf8='r')
1080
self.do_update_entry(state, entry, 'r')
1081
entry = state._get_entry(0, path_utf8='r')
1082
self.assertEqual('t', entry[1][0][0])
1084
def create_and_test_file(self, state, entry):
1085
"""Create a file at 'a' and verify the state finds it during update.
1087
The state should already be versioning *something* at 'a'. This makes
1088
sure that state.update_entry recognizes it as a file.
1090
self.build_tree(['a'])
1091
stat_value = os.lstat('a')
1092
packed_stat = dirstate.pack_stat(stat_value)
1094
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1095
self.assertEqual(None, link_or_sha1)
1096
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1100
def create_and_test_dir(self, state, entry):
1101
"""Create a directory at 'a' and verify the state finds it.
1103
The state should already be versioning *something* at 'a'. This makes
1104
sure that state.update_entry recognizes it as a directory.
1106
self.build_tree(['a/'])
1107
stat_value = os.lstat('a')
1108
packed_stat = dirstate.pack_stat(stat_value)
1110
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1111
self.assertIs(None, link_or_sha1)
1112
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1116
# FIXME: Add unicode version
1117
def create_and_test_symlink(self, state, entry):
1118
"""Create a symlink at 'a' and verify the state finds it.
1120
The state should already be versioning *something* at 'a'. This makes
1121
sure that state.update_entry recognizes it as a symlink.
1123
This should not be called if this platform does not have symlink
1126
# caller should care about skipping test on platforms without symlinks
1127
os.symlink('path/to/foo', 'a')
1129
stat_value = os.lstat('a')
1130
packed_stat = dirstate.pack_stat(stat_value)
1132
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1133
self.assertEqual('path/to/foo', link_or_sha1)
1134
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1138
def test_update_file_to_dir(self):
1139
"""If a file changes to a directory we return None for the sha.
1140
We also update the inventory record.
1142
state, entry = self.get_state_with_a()
1143
# The file sha1 won't be cached unless the file is old
1144
state.adjust_time(+10)
1145
self.create_and_test_file(state, entry)
1147
self.create_and_test_dir(state, entry)
1149
def test_update_file_to_symlink(self):
1150
"""File becomes a symlink"""
1151
self.requireFeature(tests.SymlinkFeature)
1152
state, entry = self.get_state_with_a()
1153
# The file sha1 won't be cached unless the file is old
1154
state.adjust_time(+10)
1155
self.create_and_test_file(state, entry)
1157
self.create_and_test_symlink(state, entry)
1159
def test_update_dir_to_file(self):
1160
"""Directory becoming a file updates the entry."""
1161
state, entry = self.get_state_with_a()
1162
# The file sha1 won't be cached unless the file is old
1163
state.adjust_time(+10)
1164
self.create_and_test_dir(state, entry)
1166
self.create_and_test_file(state, entry)
1168
def test_update_dir_to_symlink(self):
1169
"""Directory becomes a symlink"""
1170
self.requireFeature(tests.SymlinkFeature)
1171
state, entry = self.get_state_with_a()
1172
# The symlink target won't be cached if it isn't old
1173
state.adjust_time(+10)
1174
self.create_and_test_dir(state, entry)
1176
self.create_and_test_symlink(state, entry)
1178
def test_update_symlink_to_file(self):
1179
"""Symlink becomes a file"""
1180
self.requireFeature(tests.SymlinkFeature)
1181
state, entry = self.get_state_with_a()
1182
# The symlink and file info won't be cached unless old
1183
state.adjust_time(+10)
1184
self.create_and_test_symlink(state, entry)
1186
self.create_and_test_file(state, entry)
1188
def test_update_symlink_to_dir(self):
1189
"""Symlink becomes a directory"""
1190
self.requireFeature(tests.SymlinkFeature)
1191
state, entry = self.get_state_with_a()
1192
# The symlink target won't be cached if it isn't old
1193
state.adjust_time(+10)
1194
self.create_and_test_symlink(state, entry)
1196
self.create_and_test_dir(state, entry)
1198
def test__is_executable_win32(self):
1199
state, entry = self.get_state_with_a()
1200
self.build_tree(['a'])
1202
# Make sure we are using the win32 implementation of _is_executable
1203
state._is_executable = state._is_executable_win32
1205
# The file on disk is not executable, but we are marking it as though
1206
# it is. With _is_executable_win32 we ignore what is on disk.
1207
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1209
stat_value = os.lstat('a')
1210
packed_stat = dirstate.pack_stat(stat_value)
1212
state.adjust_time(-10) # Make sure everything is new
1213
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1215
# The row is updated, but the executable bit stays set.
1216
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1219
# Make the disk object look old enough to cache (but it won't cache the
1220
# sha as it is a new file).
1221
state.adjust_time(+20)
1222
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1223
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1224
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1227
def _prepare_tree(self):
1229
text = 'Hello World\n'
1230
tree = self.make_branch_and_tree('tree')
1231
self.build_tree_contents([('tree/a file', text)])
1232
tree.add('a file', 'a-file-id')
1233
# Note: dirstate does not sha prior to the first commit
1234
# so commit now in order for the test to work
1235
tree.commit('first')
1238
def test_sha1provider_sha1_used(self):
1239
tree, text = self._prepare_tree()
1240
state = dirstate.DirState.from_tree(tree, 'dirstate',
1241
UppercaseSHA1Provider())
1242
self.addCleanup(state.unlock)
1243
expected_sha = osutils.sha_string(text.upper() + "foo")
1244
entry = state._get_entry(0, path_utf8='a file')
1245
state._sha_cutoff_time()
1246
state._cutoff_time += 10
1247
sha1 = self.update_entry(state, entry, 'tree/a file',
1248
os.lstat('tree/a file'))
1249
self.assertEqual(expected_sha, sha1)
1251
def test_sha1provider_stat_and_sha1_used(self):
1252
tree, text = self._prepare_tree()
1254
self.addCleanup(tree.unlock)
1255
state = tree._current_dirstate()
1256
state._sha1_provider = UppercaseSHA1Provider()
1257
# If we used the standard provider, it would look like nothing has
1259
file_ids_changed = [change[0] for change
1260
in tree.iter_changes(tree.basis_tree())]
1261
self.assertEqual(['a-file-id'], file_ids_changed)
1264
class UppercaseSHA1Provider(dirstate.SHA1Provider):
1265
"""A custom SHA1Provider."""
1267
def sha1(self, abspath):
1268
return self.stat_and_sha1(abspath)[1]
1270
def stat_and_sha1(self, abspath):
1271
file_obj = file(abspath, 'rb')
1273
statvalue = os.fstat(file_obj.fileno())
1274
text = ''.join(file_obj.readlines())
1275
sha1 = osutils.sha_string(text.upper() + "foo")
1278
return statvalue, sha1
1281
class TestProcessEntry(test_dirstate.TestCaseWithDirState):
1283
scenarios = multiply_scenarios(dir_reader_scenarios(), pe_scenarios)
1286
_process_entry = None
1289
super(TestProcessEntry, self).setUp()
1290
self.overrideAttr(dirstate, '_process_entry', self._process_entry)
1292
def assertChangedFileIds(self, expected, tree):
1295
file_ids = [info[0] for info
1296
in tree.iter_changes(tree.basis_tree())]
1299
self.assertEqual(sorted(expected), sorted(file_ids))
1301
def test_exceptions_raised(self):
1302
# This is a direct test of bug #495023, it relies on osutils.is_inside
1303
# getting called in an inner function. Which makes it a bit brittle,
1304
# but at least it does reproduce the bug.
1305
tree = self.make_branch_and_tree('tree')
1306
self.build_tree(['tree/file', 'tree/dir/', 'tree/dir/sub',
1307
'tree/dir2/', 'tree/dir2/sub2'])
1308
tree.add(['file', 'dir', 'dir/sub', 'dir2', 'dir2/sub2'])
1309
tree.commit('first commit')
1311
self.addCleanup(tree.unlock)
1312
basis_tree = tree.basis_tree()
1313
def is_inside_raises(*args, **kwargs):
1314
raise RuntimeError('stop this')
1315
self.overrideAttr(osutils, 'is_inside', is_inside_raises)
1316
self.assertListRaises(RuntimeError, tree.iter_changes, basis_tree)
1318
def test_simple_changes(self):
1319
tree = self.make_branch_and_tree('tree')
1320
self.build_tree(['tree/file'])
1321
tree.add(['file'], ['file-id'])
1322
self.assertChangedFileIds([tree.get_root_id(), 'file-id'], tree)
1324
self.assertChangedFileIds([], tree)
1326
def test_sha1provider_stat_and_sha1_used(self):
1327
tree = self.make_branch_and_tree('tree')
1328
self.build_tree(['tree/file'])
1329
tree.add(['file'], ['file-id'])
1332
self.addCleanup(tree.unlock)
1333
state = tree._current_dirstate()
1334
state._sha1_provider = UppercaseSHA1Provider()
1335
self.assertChangedFileIds(['file-id'], tree)