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."""
30
from bzrlib.tests import (
33
from bzrlib.tests.test_osutils import dir_reader_scenarios
34
from bzrlib.tests.scenarios import (
35
load_tests_apply_scenarios,
38
from bzrlib.tests import (
43
load_tests = load_tests_apply_scenarios
46
compiled_dirstate_helpers_feature = features.ModuleAvailableFeature(
47
'bzrlib._dirstate_helpers_pyx')
50
# FIXME: we should also parametrize against SHA1Provider !
52
ue_scenarios = [('dirstate_Python',
53
{'update_entry': dirstate.py_update_entry})]
54
if compiled_dirstate_helpers_feature.available():
55
update_entry = compiled_dirstate_helpers_feature.module.update_entry
56
ue_scenarios.append(('dirstate_Pyrex', {'update_entry': update_entry}))
58
pe_scenarios = [('dirstate_Python',
59
{'_process_entry': dirstate.ProcessEntryPython})]
60
if compiled_dirstate_helpers_feature.available():
61
process_entry = compiled_dirstate_helpers_feature.module.ProcessEntryC
62
pe_scenarios.append(('dirstate_Pyrex', {'_process_entry': process_entry}))
64
helper_scenarios = [('dirstate_Python', {'helpers': _dirstate_helpers_py})]
65
if compiled_dirstate_helpers_feature.available():
66
helper_scenarios.append(('dirstate_Pyrex',
67
{'helpers': compiled_dirstate_helpers_feature.module}))
70
class TestBisectPathMixin(object):
71
"""Test that _bisect_path_*() returns the expected values.
73
_bisect_path_* is intended to work like bisect.bisect_*() except it
74
knows it is working on paths that are sorted by ('path', 'to', 'foo')
75
chunks rather than by raw 'path/to/foo'.
77
Test Cases should inherit from this and override ``get_bisect_path`` return
78
their implementation, and ``get_bisect`` to return the matching
79
bisect.bisect_* function.
82
def get_bisect_path(self):
83
"""Return an implementation of _bisect_path_*"""
84
raise NotImplementedError
87
"""Return a version of bisect.bisect_*.
89
Also, for the 'exists' check, return the offset to the real values.
90
For example bisect_left returns the index of an entry, while
91
bisect_right returns the index *after* an entry
93
:return: (bisect_func, offset)
95
raise NotImplementedError
97
def assertBisect(self, paths, split_paths, path, exists=True):
98
"""Assert that bisect_split works like bisect_left on the split paths.
100
:param paths: A list of path names
101
:param split_paths: A list of path names that are already split up by directory
102
('path/to/foo' => ('path', 'to', 'foo'))
103
:param path: The path we are indexing.
104
:param exists: The path should be present, so make sure the
105
final location actually points to the right value.
107
All other arguments will be passed along.
109
bisect_path = self.get_bisect_path()
110
self.assertIsInstance(paths, list)
111
bisect_path_idx = bisect_path(paths, path)
112
split_path = self.split_for_dirblocks([path])[0]
113
bisect_func, offset = self.get_bisect()
114
bisect_split_idx = bisect_func(split_paths, split_path)
115
self.assertEqual(bisect_split_idx, bisect_path_idx,
116
'%s disagreed. %s != %s'
118
% (bisect_path.__name__,
119
bisect_split_idx, bisect_path_idx, path)
122
self.assertEqual(path, paths[bisect_path_idx+offset])
124
def split_for_dirblocks(self, paths):
127
dirname, basename = os.path.split(path)
128
dir_split_paths.append((dirname.split('/'), basename))
129
dir_split_paths.sort()
130
return dir_split_paths
132
def test_simple(self):
133
"""In the simple case it works just like bisect_left"""
134
paths = ['', 'a', 'b', 'c', 'd']
135
split_paths = self.split_for_dirblocks(paths)
137
self.assertBisect(paths, split_paths, path, exists=True)
138
self.assertBisect(paths, split_paths, '_', exists=False)
139
self.assertBisect(paths, split_paths, 'aa', exists=False)
140
self.assertBisect(paths, split_paths, 'bb', exists=False)
141
self.assertBisect(paths, split_paths, 'cc', exists=False)
142
self.assertBisect(paths, split_paths, 'dd', exists=False)
143
self.assertBisect(paths, split_paths, 'a/a', exists=False)
144
self.assertBisect(paths, split_paths, 'b/b', exists=False)
145
self.assertBisect(paths, split_paths, 'c/c', exists=False)
146
self.assertBisect(paths, split_paths, 'd/d', exists=False)
148
def test_involved(self):
149
"""This is where bisect_path_* diverges slightly."""
150
# This is the list of paths and their contents
178
# This is the exact order that is stored by dirstate
179
# All children in a directory are mentioned before an children of
180
# children are mentioned.
181
# So all the root-directory paths, then all the
182
# first sub directory, etc.
183
paths = [# content of '/'
184
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
186
'a/a', 'a/a-a', 'a/a-z',
188
'a/z', 'a/z-a', 'a/z-z',
211
split_paths = self.split_for_dirblocks(paths)
213
for dir_parts, basename in split_paths:
214
if dir_parts == ['']:
215
sorted_paths.append(basename)
217
sorted_paths.append('/'.join(dir_parts + [basename]))
219
self.assertEqual(sorted_paths, paths)
222
self.assertBisect(paths, split_paths, path, exists=True)
225
class TestBisectPathLeft(tests.TestCase, TestBisectPathMixin):
226
"""Run all Bisect Path tests against _bisect_path_left."""
228
def get_bisect_path(self):
229
from bzrlib._dirstate_helpers_py import _bisect_path_left
230
return _bisect_path_left
232
def get_bisect(self):
233
return bisect.bisect_left, 0
236
class TestCompiledBisectPathLeft(TestBisectPathLeft):
237
"""Run all Bisect Path tests against _bisect_path_lect"""
239
_test_needs_features = [compiled_dirstate_helpers_feature]
241
def get_bisect_path(self):
242
from bzrlib._dirstate_helpers_pyx import _bisect_path_left
243
return _bisect_path_left
246
class TestBisectPathRight(tests.TestCase, TestBisectPathMixin):
247
"""Run all Bisect Path tests against _bisect_path_right"""
249
def get_bisect_path(self):
250
from bzrlib._dirstate_helpers_py import _bisect_path_right
251
return _bisect_path_right
253
def get_bisect(self):
254
return bisect.bisect_right, -1
257
class TestCompiledBisectPathRight(TestBisectPathRight):
258
"""Run all Bisect Path tests against _bisect_path_right"""
260
_test_needs_features = [compiled_dirstate_helpers_feature]
262
def get_bisect_path(self):
263
from bzrlib._dirstate_helpers_pyx import _bisect_path_right
264
return _bisect_path_right
267
class TestBisectDirblock(tests.TestCase):
268
"""Test that bisect_dirblock() returns the expected values.
270
bisect_dirblock is intended to work like bisect.bisect_left() except it
271
knows it is working on dirblocks and that dirblocks are sorted by ('path',
272
'to', 'foo') chunks rather than by raw 'path/to/foo'.
274
This test is parameterized by calling get_bisect_dirblock(). Child test
275
cases can override this function to test against a different
279
def get_bisect_dirblock(self):
280
"""Return an implementation of bisect_dirblock"""
281
from bzrlib._dirstate_helpers_py import bisect_dirblock
282
return bisect_dirblock
284
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
285
"""Assert that bisect_split works like bisect_left on the split paths.
287
:param dirblocks: A list of (path, [info]) pairs.
288
:param split_dirblocks: A list of ((split, path), [info]) pairs.
289
:param path: The path we are indexing.
291
All other arguments will be passed along.
293
bisect_dirblock = self.get_bisect_dirblock()
294
self.assertIsInstance(dirblocks, list)
295
bisect_split_idx = bisect_dirblock(dirblocks, path, *args, **kwargs)
296
split_dirblock = (path.split('/'), [])
297
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
299
self.assertEqual(bisect_left_idx, bisect_split_idx,
300
'bisect_split disagreed. %s != %s'
302
% (bisect_left_idx, bisect_split_idx, path)
305
def paths_to_dirblocks(self, paths):
306
"""Convert a list of paths into dirblock form.
308
Also, ensure that the paths are in proper sorted order.
310
dirblocks = [(path, []) for path in paths]
311
split_dirblocks = [(path.split('/'), []) for path in paths]
312
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
313
return dirblocks, split_dirblocks
315
def test_simple(self):
316
"""In the simple case it works just like bisect_left"""
317
paths = ['', 'a', 'b', 'c', 'd']
318
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
320
self.assertBisect(dirblocks, split_dirblocks, path)
321
self.assertBisect(dirblocks, split_dirblocks, '_')
322
self.assertBisect(dirblocks, split_dirblocks, 'aa')
323
self.assertBisect(dirblocks, split_dirblocks, 'bb')
324
self.assertBisect(dirblocks, split_dirblocks, 'cc')
325
self.assertBisect(dirblocks, split_dirblocks, 'dd')
326
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
327
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
328
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
329
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
331
def test_involved(self):
332
"""This is where bisect_left diverges slightly."""
334
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
335
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
337
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
338
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
341
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
343
self.assertBisect(dirblocks, split_dirblocks, path)
345
def test_involved_cached(self):
346
"""This is where bisect_left diverges slightly."""
348
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
349
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
351
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
352
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
356
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
358
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
361
class TestCompiledBisectDirblock(TestBisectDirblock):
362
"""Test that bisect_dirblock() returns the expected values.
364
bisect_dirblock is intended to work like bisect.bisect_left() except it
365
knows it is working on dirblocks and that dirblocks are sorted by ('path',
366
'to', 'foo') chunks rather than by raw 'path/to/foo'.
368
This runs all the normal tests that TestBisectDirblock did, but uses the
372
_test_needs_features = [compiled_dirstate_helpers_feature]
374
def get_bisect_dirblock(self):
375
from bzrlib._dirstate_helpers_pyx import bisect_dirblock
376
return bisect_dirblock
379
class TestCmpByDirs(tests.TestCase):
380
"""Test an implementation of cmp_by_dirs()
382
cmp_by_dirs() compares 2 paths by their directory sections, rather than as
385
Child test cases can override ``get_cmp_by_dirs`` to test a specific
389
def get_cmp_by_dirs(self):
390
"""Get a specific implementation of cmp_by_dirs."""
391
from bzrlib._dirstate_helpers_py import cmp_by_dirs
394
def assertCmpByDirs(self, expected, str1, str2):
395
"""Compare the two strings, in both directions.
397
:param expected: The expected comparison value. -1 means str1 comes
398
first, 0 means they are equal, 1 means str2 comes first
399
:param str1: string to compare
400
:param str2: string to compare
402
cmp_by_dirs = self.get_cmp_by_dirs()
404
self.assertEqual(str1, str2)
405
self.assertEqual(0, cmp_by_dirs(str1, str2))
406
self.assertEqual(0, cmp_by_dirs(str2, str1))
408
self.assertPositive(cmp_by_dirs(str1, str2))
409
self.assertNegative(cmp_by_dirs(str2, str1))
411
self.assertNegative(cmp_by_dirs(str1, str2))
412
self.assertPositive(cmp_by_dirs(str2, str1))
414
def test_cmp_empty(self):
415
"""Compare against the empty string."""
416
self.assertCmpByDirs(0, '', '')
417
self.assertCmpByDirs(1, 'a', '')
418
self.assertCmpByDirs(1, 'ab', '')
419
self.assertCmpByDirs(1, 'abc', '')
420
self.assertCmpByDirs(1, 'abcd', '')
421
self.assertCmpByDirs(1, 'abcde', '')
422
self.assertCmpByDirs(1, 'abcdef', '')
423
self.assertCmpByDirs(1, 'abcdefg', '')
424
self.assertCmpByDirs(1, 'abcdefgh', '')
425
self.assertCmpByDirs(1, 'abcdefghi', '')
426
self.assertCmpByDirs(1, 'test/ing/a/path/', '')
428
def test_cmp_same_str(self):
429
"""Compare the same string"""
430
self.assertCmpByDirs(0, 'a', 'a')
431
self.assertCmpByDirs(0, 'ab', 'ab')
432
self.assertCmpByDirs(0, 'abc', 'abc')
433
self.assertCmpByDirs(0, 'abcd', 'abcd')
434
self.assertCmpByDirs(0, 'abcde', 'abcde')
435
self.assertCmpByDirs(0, 'abcdef', 'abcdef')
436
self.assertCmpByDirs(0, 'abcdefg', 'abcdefg')
437
self.assertCmpByDirs(0, 'abcdefgh', 'abcdefgh')
438
self.assertCmpByDirs(0, 'abcdefghi', 'abcdefghi')
439
self.assertCmpByDirs(0, 'testing a long string', 'testing a long string')
440
self.assertCmpByDirs(0, 'x'*10000, 'x'*10000)
441
self.assertCmpByDirs(0, 'a/b', 'a/b')
442
self.assertCmpByDirs(0, 'a/b/c', 'a/b/c')
443
self.assertCmpByDirs(0, 'a/b/c/d', 'a/b/c/d')
444
self.assertCmpByDirs(0, 'a/b/c/d/e', 'a/b/c/d/e')
446
def test_simple_paths(self):
447
"""Compare strings that act like normal string comparison"""
448
self.assertCmpByDirs(-1, 'a', 'b')
449
self.assertCmpByDirs(-1, 'aa', 'ab')
450
self.assertCmpByDirs(-1, 'ab', 'bb')
451
self.assertCmpByDirs(-1, 'aaa', 'aab')
452
self.assertCmpByDirs(-1, 'aab', 'abb')
453
self.assertCmpByDirs(-1, 'abb', 'bbb')
454
self.assertCmpByDirs(-1, 'aaaa', 'aaab')
455
self.assertCmpByDirs(-1, 'aaab', 'aabb')
456
self.assertCmpByDirs(-1, 'aabb', 'abbb')
457
self.assertCmpByDirs(-1, 'abbb', 'bbbb')
458
self.assertCmpByDirs(-1, 'aaaaa', 'aaaab')
459
self.assertCmpByDirs(-1, 'a/a', 'a/b')
460
self.assertCmpByDirs(-1, 'a/b', 'b/b')
461
self.assertCmpByDirs(-1, 'a/a/a', 'a/a/b')
462
self.assertCmpByDirs(-1, 'a/a/b', 'a/b/b')
463
self.assertCmpByDirs(-1, 'a/b/b', 'b/b/b')
464
self.assertCmpByDirs(-1, 'a/a/a/a', 'a/a/a/b')
465
self.assertCmpByDirs(-1, 'a/a/a/b', 'a/a/b/b')
466
self.assertCmpByDirs(-1, 'a/a/b/b', 'a/b/b/b')
467
self.assertCmpByDirs(-1, 'a/b/b/b', 'b/b/b/b')
468
self.assertCmpByDirs(-1, 'a/a/a/a/a', 'a/a/a/a/b')
470
def test_tricky_paths(self):
471
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/cc/ef')
472
self.assertCmpByDirs(1, 'ab/cd/ef', 'ab/c/ef')
473
self.assertCmpByDirs(-1, 'ab/cd/ef', 'ab/cd-ef')
474
self.assertCmpByDirs(-1, 'ab/cd', 'ab/cd-')
475
self.assertCmpByDirs(-1, 'ab/cd', 'ab-cd')
477
def test_cmp_unicode_not_allowed(self):
478
cmp_by_dirs = self.get_cmp_by_dirs()
479
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', 'str')
480
self.assertRaises(TypeError, cmp_by_dirs, 'str', u'Unicode')
481
self.assertRaises(TypeError, cmp_by_dirs, u'Unicode', u'Unicode')
483
def test_cmp_non_ascii(self):
484
self.assertCmpByDirs(-1, '\xc2\xb5', '\xc3\xa5') # u'\xb5', u'\xe5'
485
self.assertCmpByDirs(-1, 'a', '\xc3\xa5') # u'a', u'\xe5'
486
self.assertCmpByDirs(-1, 'b', '\xc2\xb5') # u'b', u'\xb5'
487
self.assertCmpByDirs(-1, 'a/b', 'a/\xc3\xa5') # u'a/b', u'a/\xe5'
488
self.assertCmpByDirs(-1, 'b/a', 'b/\xc2\xb5') # u'b/a', u'b/\xb5'
491
class TestCompiledCmpByDirs(TestCmpByDirs):
492
"""Test the pyrex implementation of cmp_by_dirs"""
494
_test_needs_features = [compiled_dirstate_helpers_feature]
496
def get_cmp_by_dirs(self):
497
from bzrlib._dirstate_helpers_pyx import cmp_by_dirs
501
class TestCmpPathByDirblock(tests.TestCase):
502
"""Test an implementation of _cmp_path_by_dirblock()
504
_cmp_path_by_dirblock() compares two paths using the sort order used by
505
DirState. All paths in the same directory are sorted together.
507
Child test cases can override ``get_cmp_path_by_dirblock`` to test a specific
511
def get_cmp_path_by_dirblock(self):
512
"""Get a specific implementation of _cmp_path_by_dirblock."""
513
from bzrlib._dirstate_helpers_py import _cmp_path_by_dirblock
514
return _cmp_path_by_dirblock
516
def assertCmpPathByDirblock(self, paths):
517
"""Compare all paths and make sure they evaluate to the correct order.
519
This does N^2 comparisons. It is assumed that ``paths`` is properly
522
:param paths: a sorted list of paths to compare
524
# First, make sure the paths being passed in are correct
526
dirname, basename = os.path.split(p)
527
return dirname.split('/'), basename
528
self.assertEqual(sorted(paths, key=_key), paths)
530
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
531
for idx1, path1 in enumerate(paths):
532
for idx2, path2 in enumerate(paths):
533
cmp_val = cmp_path_by_dirblock(path1, path2)
535
self.assertTrue(cmp_val < 0,
536
'%s did not state that %r came before %r, cmp=%s'
537
% (cmp_path_by_dirblock.__name__,
538
path1, path2, cmp_val))
540
self.assertTrue(cmp_val > 0,
541
'%s did not state that %r came after %r, cmp=%s'
542
% (cmp_path_by_dirblock.__name__,
543
path1, path2, cmp_val))
545
self.assertTrue(cmp_val == 0,
546
'%s did not state that %r == %r, cmp=%s'
547
% (cmp_path_by_dirblock.__name__,
548
path1, path2, cmp_val))
550
def test_cmp_simple_paths(self):
551
"""Compare against the empty string."""
552
self.assertCmpPathByDirblock(['', 'a', 'ab', 'abc', 'a/b/c', 'b/d/e'])
553
self.assertCmpPathByDirblock(['kl', 'ab/cd', 'ab/ef', 'gh/ij'])
555
def test_tricky_paths(self):
556
self.assertCmpPathByDirblock([
558
'', 'a', 'a-a', 'a=a', 'b',
560
'a/a', 'a/a-a', 'a/a=a', 'a/b',
562
'a/a/a', 'a/a/a-a', 'a/a/a=a',
563
# Contents of 'a/a/a'
564
'a/a/a/a', 'a/a/a/b',
565
# Contents of 'a/a/a-a',
566
'a/a/a-a/a', 'a/a/a-a/b',
567
# Contents of 'a/a/a=a',
568
'a/a/a=a/a', 'a/a/a=a/b',
569
# Contents of 'a/a-a'
571
# Contents of 'a/a-a/a'
572
'a/a-a/a/a', 'a/a-a/a/b',
573
# Contents of 'a/a=a'
584
self.assertCmpPathByDirblock([
586
'', 'a', 'a-a', 'a-z', 'a=a', 'a=z',
588
'a/a', 'a/a-a', 'a/a-z',
590
'a/z', 'a/z-a', 'a/z-z',
614
def test_unicode_not_allowed(self):
615
cmp_path_by_dirblock = self.get_cmp_path_by_dirblock()
616
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', 'str')
617
self.assertRaises(TypeError, cmp_path_by_dirblock, 'str', u'Uni')
618
self.assertRaises(TypeError, cmp_path_by_dirblock, u'Uni', u'Uni')
619
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', 'x/str')
620
self.assertRaises(TypeError, cmp_path_by_dirblock, 'x/str', u'x/Uni')
621
self.assertRaises(TypeError, cmp_path_by_dirblock, u'x/Uni', u'x/Uni')
623
def test_nonascii(self):
624
self.assertCmpPathByDirblock([
626
'', 'a', '\xc2\xb5', '\xc3\xa5',
628
'a/a', 'a/\xc2\xb5', 'a/\xc3\xa5',
630
'a/a/a', 'a/a/\xc2\xb5', 'a/a/\xc3\xa5',
631
# content of 'a/\xc2\xb5'
632
'a/\xc2\xb5/a', 'a/\xc2\xb5/\xc2\xb5', 'a/\xc2\xb5/\xc3\xa5',
633
# content of 'a/\xc3\xa5'
634
'a/\xc3\xa5/a', 'a/\xc3\xa5/\xc2\xb5', 'a/\xc3\xa5/\xc3\xa5',
635
# content of '\xc2\xb5'
636
'\xc2\xb5/a', '\xc2\xb5/\xc2\xb5', '\xc2\xb5/\xc3\xa5',
637
# content of '\xc2\xe5'
638
'\xc3\xa5/a', '\xc3\xa5/\xc2\xb5', '\xc3\xa5/\xc3\xa5',
642
class TestCompiledCmpPathByDirblock(TestCmpPathByDirblock):
643
"""Test the pyrex implementation of _cmp_path_by_dirblock"""
645
_test_needs_features = [compiled_dirstate_helpers_feature]
647
def get_cmp_by_dirs(self):
648
from bzrlib._dirstate_helpers_pyx import _cmp_path_by_dirblock
649
return _cmp_path_by_dirblock
652
class TestMemRChr(tests.TestCase):
653
"""Test memrchr functionality"""
655
_test_needs_features = [compiled_dirstate_helpers_feature]
657
def assertMemRChr(self, expected, s, c):
658
from bzrlib._dirstate_helpers_pyx import _py_memrchr
659
self.assertEqual(expected, _py_memrchr(s, c))
661
def test_missing(self):
662
self.assertMemRChr(None, '', 'a')
663
self.assertMemRChr(None, '', 'c')
664
self.assertMemRChr(None, 'abcdefghijklm', 'q')
665
self.assertMemRChr(None, 'aaaaaaaaaaaaaaaaaaaaaaa', 'b')
667
def test_single_entry(self):
668
self.assertMemRChr(0, 'abcdefghijklm', 'a')
669
self.assertMemRChr(1, 'abcdefghijklm', 'b')
670
self.assertMemRChr(2, 'abcdefghijklm', 'c')
671
self.assertMemRChr(10, 'abcdefghijklm', 'k')
672
self.assertMemRChr(11, 'abcdefghijklm', 'l')
673
self.assertMemRChr(12, 'abcdefghijklm', 'm')
675
def test_multiple(self):
676
self.assertMemRChr(10, 'abcdefjklmabcdefghijklm', 'a')
677
self.assertMemRChr(11, 'abcdefjklmabcdefghijklm', 'b')
678
self.assertMemRChr(12, 'abcdefjklmabcdefghijklm', 'c')
679
self.assertMemRChr(20, 'abcdefjklmabcdefghijklm', 'k')
680
self.assertMemRChr(21, 'abcdefjklmabcdefghijklm', 'l')
681
self.assertMemRChr(22, 'abcdefjklmabcdefghijklm', 'm')
682
self.assertMemRChr(22, 'aaaaaaaaaaaaaaaaaaaaaaa', 'a')
684
def test_with_nulls(self):
685
self.assertMemRChr(10, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'a')
686
self.assertMemRChr(11, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'b')
687
self.assertMemRChr(12, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'c')
688
self.assertMemRChr(20, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'k')
689
self.assertMemRChr(21, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'l')
690
self.assertMemRChr(22, 'abc\0\0\0jklmabc\0\0\0ghijklm', 'm')
691
self.assertMemRChr(22, 'aaa\0\0\0aaaaaaa\0\0\0aaaaaaa', 'a')
692
self.assertMemRChr(9, '\0\0\0\0\0\0\0\0\0\0', '\0')
695
class TestReadDirblocks(test_dirstate.TestCaseWithDirState):
696
"""Test an implementation of _read_dirblocks()
698
_read_dirblocks() reads in all of the dirblock information from the disk
701
Child test cases can override ``get_read_dirblocks`` to test a specific
705
# inherits scenarios from test_dirstate
707
def get_read_dirblocks(self):
708
from bzrlib._dirstate_helpers_py import _read_dirblocks
709
return _read_dirblocks
711
def test_smoketest(self):
712
"""Make sure that we can create and read back a simple file."""
713
tree, state, expected = self.create_basic_dirstate()
715
state._read_header_if_needed()
716
self.assertEqual(dirstate.DirState.NOT_IN_MEMORY,
717
state._dirblock_state)
718
read_dirblocks = self.get_read_dirblocks()
719
read_dirblocks(state)
720
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
721
state._dirblock_state)
723
def test_trailing_garbage(self):
724
tree, state, expected = self.create_basic_dirstate()
725
# On Unix, we can write extra data as long as we haven't read yet, but
726
# on Win32, if you've opened the file with FILE_SHARE_READ, trying to
727
# open it in append mode will fail.
729
f = open('dirstate', 'ab')
731
# Add bogus trailing garbage
736
e = self.assertRaises(errors.DirstateCorrupt,
737
state._read_dirblocks_if_needed)
738
# Make sure we mention the bogus characters in the error
739
self.assertContainsRe(str(e), 'bogus')
742
class TestCompiledReadDirblocks(TestReadDirblocks):
743
"""Test the pyrex implementation of _read_dirblocks"""
745
_test_needs_features = [compiled_dirstate_helpers_feature]
747
def get_read_dirblocks(self):
748
from bzrlib._dirstate_helpers_pyx import _read_dirblocks
749
return _read_dirblocks
752
class TestUsingCompiledIfAvailable(tests.TestCase):
753
"""Check that any compiled functions that are available are the default.
755
It is possible to have typos, etc in the import line, such that
756
_dirstate_helpers_pyx is actually available, but the compiled functions are
760
def test_bisect_dirblock(self):
761
if compiled_dirstate_helpers_feature.available():
762
from bzrlib._dirstate_helpers_pyx import bisect_dirblock
764
from bzrlib._dirstate_helpers_py import bisect_dirblock
765
self.assertIs(bisect_dirblock, dirstate.bisect_dirblock)
767
def test__bisect_path_left(self):
768
if compiled_dirstate_helpers_feature.available():
769
from bzrlib._dirstate_helpers_pyx import _bisect_path_left
771
from bzrlib._dirstate_helpers_py import _bisect_path_left
772
self.assertIs(_bisect_path_left, dirstate._bisect_path_left)
774
def test__bisect_path_right(self):
775
if compiled_dirstate_helpers_feature.available():
776
from bzrlib._dirstate_helpers_pyx import _bisect_path_right
778
from bzrlib._dirstate_helpers_py import _bisect_path_right
779
self.assertIs(_bisect_path_right, dirstate._bisect_path_right)
781
def test_cmp_by_dirs(self):
782
if compiled_dirstate_helpers_feature.available():
783
from bzrlib._dirstate_helpers_pyx import cmp_by_dirs
785
from bzrlib._dirstate_helpers_py import cmp_by_dirs
786
self.assertIs(cmp_by_dirs, dirstate.cmp_by_dirs)
788
def test__read_dirblocks(self):
789
if compiled_dirstate_helpers_feature.available():
790
from bzrlib._dirstate_helpers_pyx import _read_dirblocks
792
from bzrlib._dirstate_helpers_py import _read_dirblocks
793
self.assertIs(_read_dirblocks, dirstate._read_dirblocks)
795
def test_update_entry(self):
796
if compiled_dirstate_helpers_feature.available():
797
from bzrlib._dirstate_helpers_pyx import update_entry
799
from bzrlib.dirstate import update_entry
800
self.assertIs(update_entry, dirstate.update_entry)
802
def test_process_entry(self):
803
if compiled_dirstate_helpers_feature.available():
804
from bzrlib._dirstate_helpers_pyx import ProcessEntryC
805
self.assertIs(ProcessEntryC, dirstate._process_entry)
807
from bzrlib.dirstate import ProcessEntryPython
808
self.assertIs(ProcessEntryPython, dirstate._process_entry)
811
class TestUpdateEntry(test_dirstate.TestCaseWithDirState):
812
"""Test the DirState.update_entry functions"""
814
scenarios = multiply_scenarios(
815
dir_reader_scenarios(), ue_scenarios)
821
super(TestUpdateEntry, self).setUp()
822
self.overrideAttr(dirstate, 'update_entry', self.update_entry)
824
def get_state_with_a(self):
825
"""Create a DirState tracking a single object named 'a'"""
826
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
827
self.addCleanup(state.unlock)
828
state.add('a', 'a-id', 'file', None, '')
829
entry = state._get_entry(0, path_utf8='a')
832
def test_observed_sha1_cachable(self):
833
state, entry = self.get_state_with_a()
835
atime = time.time() - 10
836
self.build_tree(['a'])
837
statvalue = test_dirstate._FakeStat.from_stat(os.lstat('a'))
838
statvalue.st_mtime = statvalue.st_ctime = atime
839
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
840
state._dirblock_state)
841
state._observed_sha1(entry, "foo", statvalue)
842
self.assertEqual('foo', entry[1][0][1])
843
packed_stat = dirstate.pack_stat(statvalue)
844
self.assertEqual(packed_stat, entry[1][0][4])
845
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
846
state._dirblock_state)
848
def test_observed_sha1_not_cachable(self):
849
state, entry = self.get_state_with_a()
851
oldval = entry[1][0][1]
852
oldstat = entry[1][0][4]
853
self.build_tree(['a'])
854
statvalue = os.lstat('a')
855
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
856
state._dirblock_state)
857
state._observed_sha1(entry, "foo", statvalue)
858
self.assertEqual(oldval, entry[1][0][1])
859
self.assertEqual(oldstat, entry[1][0][4])
860
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
861
state._dirblock_state)
863
def test_update_entry(self):
864
state, _ = self.get_state_with_a()
865
tree = self.make_branch_and_tree('tree')
867
empty_revid = tree.commit('empty')
868
self.build_tree(['tree/a'])
869
tree.add(['a'], ['a-id'])
870
with_a_id = tree.commit('with_a')
871
self.addCleanup(tree.unlock)
872
state.set_parent_trees(
873
[(empty_revid, tree.branch.repository.revision_tree(empty_revid))],
875
entry = state._get_entry(0, path_utf8='a')
876
self.build_tree(['a'])
877
# Add one where we don't provide the stat or sha already
878
self.assertEqual(('', 'a', 'a-id'), entry[0])
879
self.assertEqual(('f', '', 0, False, dirstate.DirState.NULLSTAT),
881
# Flush the buffers to disk
883
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
884
state._dirblock_state)
886
stat_value = os.lstat('a')
887
packed_stat = dirstate.pack_stat(stat_value)
888
link_or_sha1 = self.update_entry(state, entry, abspath='a',
889
stat_value=stat_value)
890
self.assertEqual(None, link_or_sha1)
892
# The dirblock entry should not have computed or cached the file's
893
# sha1, but it did update the files' st_size. However, this is not
894
# worth writing a dirstate file for, so we leave the state UNMODIFIED
895
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
897
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
898
state._dirblock_state)
899
mode = stat_value.st_mode
900
self.assertEqual([('is_exec', mode, False)], state._log)
903
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
904
state._dirblock_state)
906
# Roll the clock back so the file is guaranteed to look too new. We
907
# should still not compute the sha1.
908
state.adjust_time(-10)
911
link_or_sha1 = self.update_entry(state, entry, abspath='a',
912
stat_value=stat_value)
913
self.assertEqual([('is_exec', mode, False)], state._log)
914
self.assertEqual(None, link_or_sha1)
915
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
916
state._dirblock_state)
917
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
921
# If it is cachable (the clock has moved forward) but new it still
922
# won't calculate the sha or cache it.
923
state.adjust_time(+20)
925
link_or_sha1 = dirstate.update_entry(state, entry, abspath='a',
926
stat_value=stat_value)
927
self.assertEqual(None, link_or_sha1)
928
self.assertEqual([('is_exec', mode, False)], state._log)
929
self.assertEqual(('f', '', 14, False, dirstate.DirState.NULLSTAT),
931
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
932
state._dirblock_state)
934
# If the file is no longer new, and the clock has been moved forward
935
# sufficiently, it will cache the sha.
937
state.set_parent_trees(
938
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
940
entry = state._get_entry(0, path_utf8='a')
942
link_or_sha1 = self.update_entry(state, entry, abspath='a',
943
stat_value=stat_value)
944
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
946
self.assertEqual([('is_exec', mode, False), ('sha1', 'a')],
948
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
951
# Subsequent calls will just return the cached value
953
link_or_sha1 = self.update_entry(state, entry, abspath='a',
954
stat_value=stat_value)
955
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
957
self.assertEqual([], state._log)
958
self.assertEqual(('f', link_or_sha1, 14, False, packed_stat),
961
def test_update_entry_symlink(self):
962
"""Update entry should read symlinks."""
963
self.requireFeature(features.SymlinkFeature)
964
state, entry = self.get_state_with_a()
966
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
967
state._dirblock_state)
968
os.symlink('target', 'a')
970
state.adjust_time(-10) # Make the symlink look new
971
stat_value = os.lstat('a')
972
packed_stat = dirstate.pack_stat(stat_value)
973
link_or_sha1 = self.update_entry(state, entry, abspath='a',
974
stat_value=stat_value)
975
self.assertEqual('target', link_or_sha1)
976
self.assertEqual([('read_link', 'a', '')], state._log)
977
# Dirblock is not updated (the link is too new)
978
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
980
# The file entry turned into a symlink, that is considered
981
# HASH modified worthy.
982
self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
983
state._dirblock_state)
985
# Because the stat_value looks new, we should re-read the target
987
link_or_sha1 = self.update_entry(state, entry, abspath='a',
988
stat_value=stat_value)
989
self.assertEqual('target', link_or_sha1)
990
self.assertEqual([('read_link', 'a', '')], state._log)
991
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
994
state.adjust_time(+20) # Skip into the future, all files look old
996
link_or_sha1 = self.update_entry(state, entry, abspath='a',
997
stat_value=stat_value)
998
# The symlink stayed a symlink. So while it is new enough to cache, we
999
# don't bother setting the flag, because it is not really worth saving
1000
# (when we stat the symlink, we'll have paged in the target.)
1001
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1002
state._dirblock_state)
1003
self.assertEqual('target', link_or_sha1)
1004
# We need to re-read the link because only now can we cache it
1005
self.assertEqual([('read_link', 'a', '')], state._log)
1006
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1010
# Another call won't re-read the link
1011
self.assertEqual([], state._log)
1012
link_or_sha1 = self.update_entry(state, entry, abspath='a',
1013
stat_value=stat_value)
1014
self.assertEqual('target', link_or_sha1)
1015
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1018
def do_update_entry(self, state, entry, abspath):
1019
stat_value = os.lstat(abspath)
1020
return self.update_entry(state, entry, abspath, stat_value)
1022
def test_update_entry_dir(self):
1023
state, entry = self.get_state_with_a()
1024
self.build_tree(['a/'])
1025
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1027
def test_update_entry_dir_unchanged(self):
1028
state, entry = self.get_state_with_a()
1029
self.build_tree(['a/'])
1030
state.adjust_time(+20)
1031
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1032
# a/ used to be a file, but is now a directory, worth saving
1033
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1034
state._dirblock_state)
1036
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1037
state._dirblock_state)
1038
# No changes to a/ means not worth saving.
1039
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1040
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1041
state._dirblock_state)
1042
# Change the last-modified time for the directory
1043
t = time.time() - 100.0
1045
os.utime('a', (t, t))
1047
# It looks like Win32 + FAT doesn't allow to change times on a dir.
1048
raise tests.TestSkipped("can't update mtime of a dir on FAT")
1049
saved_packed_stat = entry[1][0][-1]
1050
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1051
# We *do* go ahead and update the information in the dirblocks, but we
1052
# don't bother setting IN_MEMORY_MODIFIED because it is trivial to
1054
self.assertNotEqual(saved_packed_stat, entry[1][0][-1])
1055
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1056
state._dirblock_state)
1058
def test_update_entry_file_unchanged(self):
1059
state, _ = self.get_state_with_a()
1060
tree = self.make_branch_and_tree('tree')
1062
self.build_tree(['tree/a'])
1063
tree.add(['a'], ['a-id'])
1064
with_a_id = tree.commit('witha')
1065
self.addCleanup(tree.unlock)
1066
state.set_parent_trees(
1067
[(with_a_id, tree.branch.repository.revision_tree(with_a_id))],
1069
entry = state._get_entry(0, path_utf8='a')
1070
self.build_tree(['a'])
1071
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1072
state.adjust_time(+20)
1073
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1074
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1075
state._dirblock_state)
1077
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1078
state._dirblock_state)
1079
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1080
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1081
state._dirblock_state)
1083
def test_update_entry_tree_reference(self):
1084
state = test_dirstate.InstrumentedDirState.initialize('dirstate')
1085
self.addCleanup(state.unlock)
1086
state.add('r', 'r-id', 'tree-reference', None, '')
1087
self.build_tree(['r/'])
1088
entry = state._get_entry(0, path_utf8='r')
1089
self.do_update_entry(state, entry, 'r')
1090
entry = state._get_entry(0, path_utf8='r')
1091
self.assertEqual('t', entry[1][0][0])
1093
def create_and_test_file(self, state, entry):
1094
"""Create a file at 'a' and verify the state finds it during update.
1096
The state should already be versioning *something* at 'a'. This makes
1097
sure that state.update_entry recognizes it as a file.
1099
self.build_tree(['a'])
1100
stat_value = os.lstat('a')
1101
packed_stat = dirstate.pack_stat(stat_value)
1103
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1104
self.assertEqual(None, link_or_sha1)
1105
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1109
def create_and_test_dir(self, state, entry):
1110
"""Create a directory at 'a' and verify the state finds it.
1112
The state should already be versioning *something* at 'a'. This makes
1113
sure that state.update_entry recognizes it as a directory.
1115
self.build_tree(['a/'])
1116
stat_value = os.lstat('a')
1117
packed_stat = dirstate.pack_stat(stat_value)
1119
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1120
self.assertIs(None, link_or_sha1)
1121
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1125
# FIXME: Add unicode version
1126
def create_and_test_symlink(self, state, entry):
1127
"""Create a symlink at 'a' and verify the state finds it.
1129
The state should already be versioning *something* at 'a'. This makes
1130
sure that state.update_entry recognizes it as a symlink.
1132
This should not be called if this platform does not have symlink
1135
# caller should care about skipping test on platforms without symlinks
1136
os.symlink('path/to/foo', 'a')
1138
stat_value = os.lstat('a')
1139
packed_stat = dirstate.pack_stat(stat_value)
1141
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1142
self.assertEqual('path/to/foo', link_or_sha1)
1143
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1147
def test_update_file_to_dir(self):
1148
"""If a file changes to a directory we return None for the sha.
1149
We also update the inventory record.
1151
state, entry = self.get_state_with_a()
1152
# The file sha1 won't be cached unless the file is old
1153
state.adjust_time(+10)
1154
self.create_and_test_file(state, entry)
1156
self.create_and_test_dir(state, entry)
1158
def test_update_file_to_symlink(self):
1159
"""File becomes a symlink"""
1160
self.requireFeature(features.SymlinkFeature)
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_file(state, entry)
1166
self.create_and_test_symlink(state, entry)
1168
def test_update_dir_to_file(self):
1169
"""Directory becoming a file updates the entry."""
1170
state, entry = self.get_state_with_a()
1171
# The file sha1 won't be cached unless the file is old
1172
state.adjust_time(+10)
1173
self.create_and_test_dir(state, entry)
1175
self.create_and_test_file(state, entry)
1177
def test_update_dir_to_symlink(self):
1178
"""Directory becomes a symlink"""
1179
self.requireFeature(features.SymlinkFeature)
1180
state, entry = self.get_state_with_a()
1181
# The symlink target won't be cached if it isn't old
1182
state.adjust_time(+10)
1183
self.create_and_test_dir(state, entry)
1185
self.create_and_test_symlink(state, entry)
1187
def test_update_symlink_to_file(self):
1188
"""Symlink becomes a file"""
1189
self.requireFeature(features.SymlinkFeature)
1190
state, entry = self.get_state_with_a()
1191
# The symlink and file info won't be cached unless old
1192
state.adjust_time(+10)
1193
self.create_and_test_symlink(state, entry)
1195
self.create_and_test_file(state, entry)
1197
def test_update_symlink_to_dir(self):
1198
"""Symlink becomes a directory"""
1199
self.requireFeature(features.SymlinkFeature)
1200
state, entry = self.get_state_with_a()
1201
# The symlink target won't be cached if it isn't old
1202
state.adjust_time(+10)
1203
self.create_and_test_symlink(state, entry)
1205
self.create_and_test_dir(state, entry)
1207
def test__is_executable_win32(self):
1208
state, entry = self.get_state_with_a()
1209
self.build_tree(['a'])
1211
# Make sure we are using the win32 implementation of _is_executable
1212
state._is_executable = state._is_executable_win32
1214
# The file on disk is not executable, but we are marking it as though
1215
# it is. With _is_executable_win32 we ignore what is on disk.
1216
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1218
stat_value = os.lstat('a')
1219
packed_stat = dirstate.pack_stat(stat_value)
1221
state.adjust_time(-10) # Make sure everything is new
1222
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1224
# The row is updated, but the executable bit stays set.
1225
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1228
# Make the disk object look old enough to cache (but it won't cache the
1229
# sha as it is a new file).
1230
state.adjust_time(+20)
1231
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1232
self.update_entry(state, entry, abspath='a', stat_value=stat_value)
1233
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1236
def _prepare_tree(self):
1238
text = 'Hello World\n'
1239
tree = self.make_branch_and_tree('tree')
1240
self.build_tree_contents([('tree/a file', text)])
1241
tree.add('a file', 'a-file-id')
1242
# Note: dirstate does not sha prior to the first commit
1243
# so commit now in order for the test to work
1244
tree.commit('first')
1247
def test_sha1provider_sha1_used(self):
1248
tree, text = self._prepare_tree()
1249
state = dirstate.DirState.from_tree(tree, 'dirstate',
1250
UppercaseSHA1Provider())
1251
self.addCleanup(state.unlock)
1252
expected_sha = osutils.sha_string(text.upper() + "foo")
1253
entry = state._get_entry(0, path_utf8='a file')
1254
state._sha_cutoff_time()
1255
state._cutoff_time += 10
1256
sha1 = self.update_entry(state, entry, 'tree/a file',
1257
os.lstat('tree/a file'))
1258
self.assertEqual(expected_sha, sha1)
1260
def test_sha1provider_stat_and_sha1_used(self):
1261
tree, text = self._prepare_tree()
1263
self.addCleanup(tree.unlock)
1264
state = tree._current_dirstate()
1265
state._sha1_provider = UppercaseSHA1Provider()
1266
# If we used the standard provider, it would look like nothing has
1268
file_ids_changed = [change[0] for change
1269
in tree.iter_changes(tree.basis_tree())]
1270
self.assertEqual(['a-file-id'], file_ids_changed)
1273
class UppercaseSHA1Provider(dirstate.SHA1Provider):
1274
"""A custom SHA1Provider."""
1276
def sha1(self, abspath):
1277
return self.stat_and_sha1(abspath)[1]
1279
def stat_and_sha1(self, abspath):
1280
file_obj = file(abspath, 'rb')
1282
statvalue = os.fstat(file_obj.fileno())
1283
text = ''.join(file_obj.readlines())
1284
sha1 = osutils.sha_string(text.upper() + "foo")
1287
return statvalue, sha1
1290
class TestProcessEntry(test_dirstate.TestCaseWithDirState):
1292
scenarios = multiply_scenarios(dir_reader_scenarios(), pe_scenarios)
1295
_process_entry = None
1298
super(TestProcessEntry, self).setUp()
1299
self.overrideAttr(dirstate, '_process_entry', self._process_entry)
1301
def assertChangedFileIds(self, expected, tree):
1304
file_ids = [info[0] for info
1305
in tree.iter_changes(tree.basis_tree())]
1308
self.assertEqual(sorted(expected), sorted(file_ids))
1310
def test_exceptions_raised(self):
1311
# This is a direct test of bug #495023, it relies on osutils.is_inside
1312
# getting called in an inner function. Which makes it a bit brittle,
1313
# but at least it does reproduce the bug.
1314
tree = self.make_branch_and_tree('tree')
1315
self.build_tree(['tree/file', 'tree/dir/', 'tree/dir/sub',
1316
'tree/dir2/', 'tree/dir2/sub2'])
1317
tree.add(['file', 'dir', 'dir/sub', 'dir2', 'dir2/sub2'])
1318
tree.commit('first commit')
1320
self.addCleanup(tree.unlock)
1321
basis_tree = tree.basis_tree()
1322
def is_inside_raises(*args, **kwargs):
1323
raise RuntimeError('stop this')
1324
self.overrideAttr(osutils, 'is_inside', is_inside_raises)
1325
self.assertListRaises(RuntimeError, tree.iter_changes, basis_tree)
1327
def test_simple_changes(self):
1328
tree = self.make_branch_and_tree('tree')
1329
self.build_tree(['tree/file'])
1330
tree.add(['file'], ['file-id'])
1331
self.assertChangedFileIds([tree.get_root_id(), 'file-id'], tree)
1333
self.assertChangedFileIds([], tree)
1335
def test_sha1provider_stat_and_sha1_used(self):
1336
tree = self.make_branch_and_tree('tree')
1337
self.build_tree(['tree/file'])
1338
tree.add(['file'], ['file-id'])
1341
self.addCleanup(tree.unlock)
1342
state = tree._current_dirstate()
1343
state._sha1_provider = UppercaseSHA1Provider()
1344
self.assertChangedFileIds(['file-id'], tree)
1347
class TestPackStat(tests.TestCase):
1348
"""Check packed representaton of stat values is robust on all inputs"""
1350
scenarios = helper_scenarios
1352
def pack(self, statlike_tuple):
1353
return self.helpers.pack_stat(os.stat_result(statlike_tuple))
1356
def unpack_field(packed_string, stat_field):
1357
return _dirstate_helpers_py._unpack_stat(packed_string)[stat_field]
1359
def test_result(self):
1360
self.assertEqual("AAAQAAAAABAAAAARAAAAAgAAAAEAAIHk",
1361
self.pack((33252, 1, 2, 0, 0, 0, 4096, 15.5, 16.5, 17.5)))
1363
def test_giant_inode(self):
1364
packed = self.pack((33252, 0xF80000ABC, 0, 0, 0, 0, 0, 0, 0, 0))
1365
self.assertEqual(0x80000ABC, self.unpack_field(packed, "st_ino"))
1367
def test_giant_size(self):
1368
packed = self.pack((33252, 0, 0, 0, 0, 0, (1 << 33) + 4096, 0, 0, 0))
1369
self.assertEqual(4096, self.unpack_field(packed, "st_size"))
1371
def test_fractional_mtime(self):
1372
packed = self.pack((33252, 0, 0, 0, 0, 0, 0, 0, 16.9375, 0))
1373
self.assertEqual(16, self.unpack_field(packed, "st_mtime"))
1375
def test_ancient_mtime(self):
1376
packed = self.pack((33252, 0, 0, 0, 0, 0, 0, 0, -11644473600.0, 0))
1377
self.assertEqual(1240428288, self.unpack_field(packed, "st_mtime"))
1379
def test_distant_mtime(self):
1380
packed = self.pack((33252, 0, 0, 0, 0, 0, 0, 0, 64060588800.0, 0))
1381
self.assertEqual(3931046656, self.unpack_field(packed, "st_mtime"))
1383
def test_fractional_ctime(self):
1384
packed = self.pack((33252, 0, 0, 0, 0, 0, 0, 0, 0, 17.5625))
1385
self.assertEqual(17, self.unpack_field(packed, "st_ctime"))
1387
def test_ancient_ctime(self):
1388
packed = self.pack((33252, 0, 0, 0, 0, 0, 0, 0, 0, -11644473600.0))
1389
self.assertEqual(1240428288, self.unpack_field(packed, "st_ctime"))
1391
def test_distant_ctime(self):
1392
packed = self.pack((33252, 0, 0, 0, 0, 0, 0, 0, 0, 64060588800.0))
1393
self.assertEqual(3931046656, self.unpack_field(packed, "st_ctime"))
1395
def test_negative_dev(self):
1396
packed = self.pack((33252, 0, -0xFFFFFCDE, 0, 0, 0, 0, 0, 0, 0))
1397
self.assertEqual(0x322, self.unpack_field(packed, "st_dev"))