Beispiel #1
0
    def setUp(self):
        self.data = {
            'path/to/somefile':
            '1366207797;1024;6cf9224c0ced0affde6832a101676ff656a7cd6f',
            'path/to/anotherfile':
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        }

        def data_delitem(key):
            del self.data[key]

        def data_getitem(key):
            return self.data[key]

        def data_setitem(key, value):
            self.data[key] = value

        def data_nextkey(key):
            key_iter = iter(self.data)
            while key_iter.next() != key:
                pass
            return key_iter.next()

        self.dbmock = MagicMock()
        self.dbmock.__delitem__.side_effect = data_delitem
        self.dbmock.__getitem__.side_effect = data_getitem
        self.dbmock.__setitem__.side_effect = data_setitem
        self.dbmock.__len__.side_effect = lambda: len(self.data)
        self.dbmock.firstkey.side_effect = lambda: self.data.iterkeys().next()
        self.dbmock.nextkey.side_effect = data_nextkey

        self.gdbm_mock = MagicMock(spec_set=gdbm)
        self.gdbm_mock.open.return_value = self.dbmock
        self.hashdb = HashDb('<filename>', self.gdbm_mock)
Beispiel #2
0
    def setUp(self):
        self.data = {
            'path/to/somefile':
            '1366207797;1024;6cf9224c0ced0affde6832a101676ff656a7cd6f',
            'path/to/anotherfile':
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        }

        def data_delitem(key):
            del self.data[key]

        def data_getitem(key):
            return self.data[key]

        def data_setitem(key, value):
            self.data[key] = value

        def data_nextkey(key):
            key_iter = iter(self.data)
            while key_iter.next() != key:
                pass
            return key_iter.next()

        self.dbmock = MagicMock()
        self.dbmock.__delitem__.side_effect = data_delitem
        self.dbmock.__getitem__.side_effect = data_getitem
        self.dbmock.__setitem__.side_effect = data_setitem
        self.dbmock.__len__.side_effect = lambda: len(self.data)
        self.dbmock.firstkey.side_effect = lambda: self.data.iterkeys().next()
        self.dbmock.nextkey.side_effect = data_nextkey

        self.gdbm_mock = MagicMock(spec_set=gdbm)
        self.gdbm_mock.open.return_value = self.dbmock
        self.hashdb = HashDb('<filename>', self.gdbm_mock)
Beispiel #3
0
    def test_does_not_update_hash_if_modification_and_size_unchanged(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 1024)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(1366207797, 1024,
                                '6cf9224c0ced0affde6832a101676ff656a7cd6f')
        self.assertEqual(self.hashdb['path/to/somefile'], expected)
Beispiel #4
0
    def test_update_uses_relative_path(self):
        with FilesMock() as files:
            files.add_file('path/to/somefile', 1366207797, 42)
            self.hashdb.update_path('path', 'to/somefile')

        expected = HashDb.Entry(
            1366207797, 42,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['to/somefile'], expected)
Beispiel #5
0
    def test_updates_hash_if_size_changed(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 42)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(
            1366207797, 42,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['path/to/somefile'], expected)
Beispiel #6
0
    def test_updates_hash_if_modification_time_changed(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 123, 1024)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(
            123, 1024,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['path/to/somefile'], expected)
Beispiel #7
0
    def test_inserts_hash_for_new_file(self):
        with FilesMock() as files:
            files.add_file('./newfile', 123, 42)
            self.hashdb.update_path('.', 'newfile')

        expected = HashDb.Entry(
            123, 42,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['newfile'], expected)
Beispiel #8
0
    def test_provides_dictionary_interface(self):
        entry = self.hashdb['path/to/somefile']
        self.assertEqual(entry.modification, 1366207797)
        self.assertEqual(entry.size, 1024)
        self.assertEqual(entry.sha1,
                         '6cf9224c0ced0affde6832a101676ff656a7cd6f')

        with self.assertRaises(KeyError):
            entry = self.hashdb['newpath']

        self.hashdb['newpath'] = HashDb.Entry(
            12345, 256, '07d307d64e062a0ba2ed725571aecd89f2214232')
        self.assertEqual(self.data['newpath'],
                         '12345;256;07d307d64e062a0ba2ed725571aecd89f2214232')
Beispiel #9
0
    def test_reads_complete_file(self):
        with FilesMock() as files:
            with patch('__builtin__.open', mock_open()) as open_patch:
                chunks = ['con', 'tent', '']

                def read_chunk(size):
                    return chunks.pop(0)

                file_mock = MagicMock()
                file_mock.__enter__.return_value = file_mock
                file_mock.read.side_effect = read_chunk
                open_patch.return_value = file_mock

                files.add_file('./newfile', 123, 42)
                self.hashdb.update_path('.', 'newfile')

        expected = HashDb.Entry(123, 42, hashlib.sha1('content').hexdigest())
        self.assertEqual(self.hashdb['newfile'], expected)
Beispiel #10
0
class HashDbTest(unittest.TestCase):
    def setUp(self):
        self.data = {
            'path/to/somefile':
            '1366207797;1024;6cf9224c0ced0affde6832a101676ff656a7cd6f',
            'path/to/anotherfile':
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        }

        def data_delitem(key):
            del self.data[key]

        def data_getitem(key):
            return self.data[key]

        def data_setitem(key, value):
            self.data[key] = value

        def data_nextkey(key):
            key_iter = iter(self.data)
            while key_iter.next() != key:
                pass
            return key_iter.next()

        self.dbmock = MagicMock()
        self.dbmock.__delitem__.side_effect = data_delitem
        self.dbmock.__getitem__.side_effect = data_getitem
        self.dbmock.__setitem__.side_effect = data_setitem
        self.dbmock.__len__.side_effect = lambda: len(self.data)
        self.dbmock.firstkey.side_effect = lambda: self.data.iterkeys().next()
        self.dbmock.nextkey.side_effect = data_nextkey

        self.gdbm_mock = MagicMock(spec_set=gdbm)
        self.gdbm_mock.open.return_value = self.dbmock
        self.hashdb = HashDb('<filename>', self.gdbm_mock)

    def test_can_be_used_in_with(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 1024)
            with HashDb('<filename>', self.gdbm_mock) as db:
                db.update_path('.', 'path/to/somefile')

    def test_allows_iteration(self):
        keys = ['0', '1', '2', None]
        self.dbmock.firstkey.side_effect = lambda: keys[0]
        self.dbmock.nextkey.side_effect = lambda key: keys[keys.index(key) + 1]

        for key in self.hashdb:
            self.assertTrue(key in keys, 'key = %s' % key)

    def test_has_length(self):
        self.assertEqual(len(self.hashdb), len(self.data))

    def test_provides_dictionary_interface(self):
        entry = self.hashdb['path/to/somefile']
        self.assertEqual(entry.modification, 1366207797)
        self.assertEqual(entry.size, 1024)
        self.assertEqual(entry.sha1,
                         '6cf9224c0ced0affde6832a101676ff656a7cd6f')

        with self.assertRaises(KeyError):
            entry = self.hashdb['newpath']

        self.hashdb['newpath'] = HashDb.Entry(
            12345, 256, '07d307d64e062a0ba2ed725571aecd89f2214232')
        self.assertEqual(self.data['newpath'],
                         '12345;256;07d307d64e062a0ba2ed725571aecd89f2214232')

    def test_allows_deletion_of_entries(self):
        del self.hashdb['path/to/somefile']
        self.assertFalse('path/to/somefile' in self.hashdb)

    def test_inserts_hash_for_new_file(self):
        with FilesMock() as files:
            files.add_file('./newfile', 123, 42)
            self.hashdb.update_path('.', 'newfile')

        expected = HashDb.Entry(
            123, 42,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['newfile'], expected)

    def test_reads_complete_file(self):
        with FilesMock() as files:
            with patch('__builtin__.open', mock_open()) as open_patch:
                chunks = ['con', 'tent', '']

                def read_chunk(size):
                    return chunks.pop(0)

                file_mock = MagicMock()
                file_mock.__enter__.return_value = file_mock
                file_mock.read.side_effect = read_chunk
                open_patch.return_value = file_mock

                files.add_file('./newfile', 123, 42)
                self.hashdb.update_path('.', 'newfile')

        expected = HashDb.Entry(123, 42, hashlib.sha1('content').hexdigest())
        self.assertEqual(self.hashdb['newfile'], expected)

    def test_updates_hash_if_modification_time_changed(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 123, 1024)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(
            123, 1024,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['path/to/somefile'], expected)

    def test_updates_hash_if_size_changed(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 42)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(
            1366207797, 42,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['path/to/somefile'], expected)

    def test_update_uses_relative_path(self):
        with FilesMock() as files:
            files.add_file('path/to/somefile', 1366207797, 42)
            self.hashdb.update_path('path', 'to/somefile')

        expected = HashDb.Entry(
            1366207797, 42,
            hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['to/somefile'], expected)

    def test_does_not_update_hash_if_modification_and_size_unchanged(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 1024)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(1366207797, 1024,
                                '6cf9224c0ced0affde6832a101676ff656a7cd6f')
        self.assertEqual(self.hashdb['path/to/somefile'], expected)

    def test_update_path_throws_exception_for_non_existing_files(self):
        with FilesMock() as files:
            files.add_file('./existent', 1, 1)
            with self.assertRaises(OSError) as cm:
                self.hashdb.update_path('.', 'nonexistent')

        self.assertEqual(cm.exception.errno, errno.ENOENT)

    def test_can_update_all_paths_in_tree(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [(dirpath, ['a', 'b'], ['file0', 'file1']),
                                 (os.path.join(dirpath, 'a'), [], ['file2']),
                                 (os.path.join(dirpath,
                                               'b'), [], ['file3', 'file4'])]
            with patch.object(self.hashdb, 'update_path') as update_path:
                self.hashdb.update_tree(dirpath)

                expected = [
                    call(dirpath, f) for f in [
                        'file0', 'file1',
                        os.path.join('a', 'file2'),
                        os.path.join('b', 'file3'),
                        os.path.join('b', 'file4')
                    ]
                ]
                self.assertEqual(len(update_path.call_args_list),
                                 len(expected))
                for c in update_path.call_args_list:
                    self.assertIn(c, expected)

    def test_can_exclude_patterns_in_update_tree(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [(dirpath, ['a', 'b'], ['file0', 'exclude1']),
                                 (os.path.join(dirpath,
                                               'a'), [], ['exclude2']),
                                 (os.path.join(dirpath,
                                               'b'), [], ['file3', 'file4'])]
            with patch.object(self.hashdb, 'update_path') as update_path:
                self.hashdb.update_tree(dirpath, exclude=r'exclude\d')

                expected = [
                    call(dirpath, f) for f in [
                        'file0',
                        os.path.join('b', 'file3'),
                        os.path.join('b', 'file4')
                    ]
                ]
                self.assertEqual(len(update_path.call_args_list),
                                 len(expected))
                for c in update_path.call_args_list:
                    self.assertIn(c, expected)

    def test_update_all_prints_walk_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'

            def test_error_handler(path, onerror):
                with patch('sys.stderr') as stderr:
                    buffer = StringIO()
                    stderr.write = buffer.write
                    onerror(
                        OSError(errno.EPERM, 'Permission denied.', 'somedir'))
                    self.assertEqual(
                        buffer.getvalue(),
                        sys.argv[0] + ": somedir: Permission denied.\n")
                return [(dirpath, [], [])]

            walk.side_effect = test_error_handler
            self.hashdb.update_tree(dirpath)

    def test_update_all_prints_update_path_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [(dirpath, [], ['file'])]
            with patch('sys.stderr') as stderr:
                buffer = StringIO()
                stderr.write = buffer.write
                with patch.object(self.hashdb, 'update_path') as update_path:
                    update_path.side_effect = OSError(errno.EPERM,
                                                      'Permission denied.',
                                                      'file')
                    self.hashdb.update_tree(dirpath)

                self.assertEqual(buffer.getvalue(),
                                 sys.argv[0] + ": file: Permission denied.\n")

    def test_strip_deletes_hashes_for_nonexistent_files(self):
        with FilesMock() as files:
            files.add_file('path/to/somefile', 123, 1024)
            self.hashdb.strip()
        self.assertIn('path/to/somefile', self.hashdb)
        self.assertNotIn('path/to/anotherfile', self.hashdb)

    def test_verify_tree(self):
        self.data['path/to/missingOnDisk'] = \
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [
                (dirpath, ['path'], []),
                (os.path.join(dirpath, 'path'), ['to'], ['missingInDb']),
                (os.path.join(dirpath, 'path', 'to'), [], ['somefile']),
                (os.path.join(dirpath, 'path', 'to'), [], ['anotherfile'])
            ]
            with FilesMock() as files:
                files.add_file('dir/path/to/somefile', 1, 1)
                files.add_file('dir/path/to/anotherfile', 1, 1)
                changed, missing_in_db, missing_on_disk = \
                    self.hashdb.verify_tree(dirpath)
        self.assertEqual(changed, ['path/to/somefile'])
        self.assertEqual(missing_in_db, ['path/missingInDb'])
        self.assertEqual(missing_on_disk, ['path/to/missingOnDisk'])

    def test_verify_tree_can_exclude_patterns(self):
        self.data['path/to/exclude3'] = \
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [(dirpath, ['path'], []),
                                 (os.path.join(dirpath,
                                               'path'), ['to'], ['exclude1']),
                                 (os.path.join(dirpath, 'path',
                                               'to'), [], ['exclude2']),
                                 (os.path.join(dirpath, 'path',
                                               'to'), [], ['anotherfile'])]
            with FilesMock() as files:
                files.add_file('dir/path/to/exclude2', 1, 1)
                files.add_file('dir/path/to/anotherfile', 1, 1)
                changed, missing_in_db, missing_on_disk = \
                    self.hashdb.verify_tree(
                        dirpath, exclude=r'exclude\d|somefile')
        self.assertEqual(changed, [])
        self.assertEqual(missing_in_db, [])
        self.assertEqual(missing_on_disk, [])

    def test_verify_tree_prints_walk_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'

            def test_error_handler(path, onerror):
                with patch('sys.stderr') as stderr:
                    buffer = StringIO()
                    stderr.write = buffer.write
                    onerror(
                        OSError(errno.EPERM, 'Permission denied.', 'somedir'))
                    self.assertEqual(
                        buffer.getvalue(),
                        sys.argv[0] + ": somedir: Permission denied.\n")
                return [(dirpath, [], [])]

            walk.side_effect = test_error_handler
            self.hashdb.verify_tree(dirpath)

    def test_verify_tree_prints_raised_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [(dirpath, [], ['file'])]
            with patch('sys.stderr') as stderr:
                buffer = StringIO()
                stderr.write = buffer.write
                with patch('os.path.join') as path_join:
                    path_join.side_effect = OSError(errno.EPERM,
                                                    'Permission denied.',
                                                    'file')
                    self.hashdb.verify_tree(dirpath)

                self.assertTrue(buffer.getvalue().startswith(
                    sys.argv[0] + ": file: Permission denied.\n"))
Beispiel #11
0
 def test_entries_differing_in_sha1_are_unequal(self):
     a = HashDb.Entry(123, 456, '6cf9224c0ced0affde6832a101676ff656a7cd6f')
     b = HashDb.Entry(123, 456, '07d307d64e062a0ba2ed725571aecd89f2214232')
     self.assertNotEqual(a, b)
Beispiel #12
0
 def test_entries_differing_in_size_are_unequal(self):
     a = HashDb.Entry(123, 456, '6cf9224c0ced0affde6832a101676ff656a7cd6f')
     b = HashDb.Entry(123, 23, '6cf9224c0ced0affde6832a101676ff656a7cd6f')
     self.assertNotEqual(a, b)
Beispiel #13
0
 def test_equal_entries_considered_equal(self):
     a = HashDb.Entry(123, 456, '6cf9224c0ced0affde6832a101676ff656a7cd6f')
     b = HashDb.Entry(123, 456, '6cf9224c0ced0affde6832a101676ff656a7cd6f')
     self.assertEqual(a, b)
Beispiel #14
0
 def test_can_be_used_in_with(self):
     with FilesMock() as files:
         files.add_file('./path/to/somefile', 1366207797, 1024)
         with HashDb('<filename>', self.gdbm_mock) as db:
             db.update_path('.', 'path/to/somefile')
Beispiel #15
0
class HashDbTest(unittest.TestCase):
    def setUp(self):
        self.data = {
            'path/to/somefile':
            '1366207797;1024;6cf9224c0ced0affde6832a101676ff656a7cd6f',
            'path/to/anotherfile':
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        }

        def data_delitem(key):
            del self.data[key]

        def data_getitem(key):
            return self.data[key]

        def data_setitem(key, value):
            self.data[key] = value

        def data_nextkey(key):
            key_iter = iter(self.data)
            while key_iter.next() != key:
                pass
            return key_iter.next()

        self.dbmock = MagicMock()
        self.dbmock.__delitem__.side_effect = data_delitem
        self.dbmock.__getitem__.side_effect = data_getitem
        self.dbmock.__setitem__.side_effect = data_setitem
        self.dbmock.__len__.side_effect = lambda: len(self.data)
        self.dbmock.firstkey.side_effect = lambda: self.data.iterkeys().next()
        self.dbmock.nextkey.side_effect = data_nextkey

        self.gdbm_mock = MagicMock(spec_set=gdbm)
        self.gdbm_mock.open.return_value = self.dbmock
        self.hashdb = HashDb('<filename>', self.gdbm_mock)

    def test_can_be_used_in_with(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 1024)
            with HashDb('<filename>', self.gdbm_mock) as db:
                db.update_path('.', 'path/to/somefile')

    def test_allows_iteration(self):
        keys = ['0', '1', '2', None]
        self.dbmock.firstkey.side_effect = lambda: keys[0]
        self.dbmock.nextkey.side_effect = lambda key: keys[keys.index(key) + 1]

        for key in self.hashdb:
            self.assertTrue(key in keys, 'key = %s' % key)

    def test_has_length(self):
        self.assertEqual(len(self.hashdb), len(self.data))

    def test_provides_dictionary_interface(self):
        entry = self.hashdb['path/to/somefile']
        self.assertEqual(entry.modification, 1366207797)
        self.assertEqual(entry.size, 1024)
        self.assertEqual(
            entry.sha1, '6cf9224c0ced0affde6832a101676ff656a7cd6f')

        with self.assertRaises(KeyError):
            entry = self.hashdb['newpath']

        self.hashdb['newpath'] = HashDb.Entry(
            12345, 256, '07d307d64e062a0ba2ed725571aecd89f2214232')
        self.assertEqual(
            self.data['newpath'],
            '12345;256;07d307d64e062a0ba2ed725571aecd89f2214232')

    def test_allows_deletion_of_entries(self):
        del self.hashdb['path/to/somefile']
        self.assertFalse('path/to/somefile' in self.hashdb)

    def test_inserts_hash_for_new_file(self):
        with FilesMock() as files:
            files.add_file('./newfile', 123, 42)
            self.hashdb.update_path('.', 'newfile')

        expected = HashDb.Entry(
            123, 42, hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['newfile'], expected)

    def test_reads_complete_file(self):
        with FilesMock() as files:
            with patch('__builtin__.open', mock_open()) as open_patch:
                chunks = ['con', 'tent', '']

                def read_chunk(size):
                    return chunks.pop(0)

                file_mock = MagicMock()
                file_mock.__enter__.return_value = file_mock
                file_mock.read.side_effect = read_chunk
                open_patch.return_value = file_mock

                files.add_file('./newfile', 123, 42)
                self.hashdb.update_path('.', 'newfile')

        expected = HashDb.Entry(
            123, 42, hashlib.sha1('content').hexdigest())
        self.assertEqual(self.hashdb['newfile'], expected)

    def test_updates_hash_if_modification_time_changed(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 123, 1024)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(
            123, 1024, hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['path/to/somefile'], expected)

    def test_updates_hash_if_size_changed(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 42)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(
            1366207797, 42, hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['path/to/somefile'], expected)

    def test_update_uses_relative_path(self):
        with FilesMock() as files:
            files.add_file('path/to/somefile', 1366207797, 42)
            self.hashdb.update_path('path', 'to/somefile')

        expected = HashDb.Entry(
            1366207797, 42, hashlib.sha1(FilesMock.File.content).hexdigest())
        self.assertEqual(self.hashdb['to/somefile'], expected)

    def test_does_not_update_hash_if_modification_and_size_unchanged(self):
        with FilesMock() as files:
            files.add_file('./path/to/somefile', 1366207797, 1024)
            self.hashdb.update_path('.', 'path/to/somefile')

        expected = HashDb.Entry(
            1366207797, 1024, '6cf9224c0ced0affde6832a101676ff656a7cd6f')
        self.assertEqual(self.hashdb['path/to/somefile'], expected)

    def test_update_path_throws_exception_for_non_existing_files(self):
        with FilesMock() as files:
            files.add_file('./existent', 1, 1)
            with self.assertRaises(OSError) as cm:
                self.hashdb.update_path('.', 'nonexistent')

        self.assertEqual(cm.exception.errno, errno.ENOENT)

    def test_can_update_all_paths_in_tree(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [
                (dirpath, ['a', 'b'], ['file0', 'file1']),
                (os.path.join(dirpath, 'a'), [], ['file2']),
                (os.path.join(dirpath, 'b'), [], ['file3', 'file4'])]
            with patch.object(self.hashdb, 'update_path') as update_path:
                self.hashdb.update_tree(dirpath)

                expected = [call(dirpath, f) for f in [
                    'file0', 'file1', os.path.join('a', 'file2'),
                    os.path.join('b', 'file3'), os.path.join('b', 'file4')]]
                self.assertEqual(
                    len(update_path.call_args_list), len(expected))
                for c in update_path.call_args_list:
                    self.assertIn(c, expected)

    def test_can_exclude_patterns_in_update_tree(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [
                (dirpath, ['a', 'b'], ['file0', 'exclude1']),
                (os.path.join(dirpath, 'a'), [], ['exclude2']),
                (os.path.join(dirpath, 'b'), [], ['file3', 'file4'])]
            with patch.object(self.hashdb, 'update_path') as update_path:
                self.hashdb.update_tree(dirpath, exclude=r'exclude\d')

                expected = [call(dirpath, f) for f in [
                    'file0', os.path.join('b', 'file3'),
                    os.path.join('b', 'file4')]]
                self.assertEqual(
                    len(update_path.call_args_list), len(expected))
                for c in update_path.call_args_list:
                    self.assertIn(c, expected)

    def test_update_all_prints_walk_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'

            def test_error_handler(path, onerror):
                with patch('sys.stderr') as stderr:
                    buffer = StringIO()
                    stderr.write = buffer.write
                    onerror(OSError(
                        errno.EPERM, 'Permission denied.', 'somedir'))
                    self.assertEqual(
                        buffer.getvalue(),
                        sys.argv[0] + ": somedir: Permission denied.\n")
                return [(dirpath, [], [])]

            walk.side_effect = test_error_handler
            self.hashdb.update_tree(dirpath)

    def test_update_all_prints_update_path_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [(dirpath, [], ['file'])]
            with patch('sys.stderr') as stderr:
                buffer = StringIO()
                stderr.write = buffer.write
                with patch.object(self.hashdb, 'update_path') as update_path:
                    update_path.side_effect = OSError(
                        errno.EPERM, 'Permission denied.', 'file')
                    self.hashdb.update_tree(dirpath)

                self.assertEqual(
                    buffer.getvalue(),
                    sys.argv[0] + ": file: Permission denied.\n")

    def test_strip_deletes_hashes_for_nonexistent_files(self):
        with FilesMock() as files:
            files.add_file('path/to/somefile', 123, 1024)
            self.hashdb.strip()
        self.assertIn('path/to/somefile', self.hashdb)
        self.assertNotIn('path/to/anotherfile', self.hashdb)

    def test_verify_tree(self):
        self.data['path/to/missingOnDisk'] = \
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [
                (dirpath, ['path'], []),
                (os.path.join(dirpath, 'path'), ['to'], ['missingInDb']),
                (os.path.join(dirpath, 'path', 'to'), [], ['somefile']),
                (os.path.join(dirpath, 'path', 'to'), [], ['anotherfile'])]
            with FilesMock() as files:
                files.add_file('dir/path/to/somefile', 1, 1)
                files.add_file('dir/path/to/anotherfile', 1, 1)
                changed, missing_in_db, missing_on_disk = \
                    self.hashdb.verify_tree(dirpath)
        self.assertEqual(changed, ['path/to/somefile'])
        self.assertEqual(missing_in_db, ['path/missingInDb'])
        self.assertEqual(missing_on_disk, ['path/to/missingOnDisk'])

    def test_verify_tree_can_exclude_patterns(self):
        self.data['path/to/exclude3'] = \
            '1366207797;1024;040f06fd774092478d450774f5ba30c5da78acc8'
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [
                (dirpath, ['path'], []),
                (os.path.join(dirpath, 'path'), ['to'], ['exclude1']),
                (os.path.join(dirpath, 'path', 'to'), [], ['exclude2']),
                (os.path.join(dirpath, 'path', 'to'), [], ['anotherfile'])]
            with FilesMock() as files:
                files.add_file('dir/path/to/exclude2', 1, 1)
                files.add_file('dir/path/to/anotherfile', 1, 1)
                changed, missing_in_db, missing_on_disk = \
                    self.hashdb.verify_tree(
                        dirpath, exclude=r'exclude\d|somefile')
        self.assertEqual(changed, [])
        self.assertEqual(missing_in_db, [])
        self.assertEqual(missing_on_disk, [])

    def test_verify_tree_prints_walk_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'

            def test_error_handler(path, onerror):
                with patch('sys.stderr') as stderr:
                    buffer = StringIO()
                    stderr.write = buffer.write
                    onerror(OSError(
                        errno.EPERM, 'Permission denied.', 'somedir'))
                    self.assertEqual(
                        buffer.getvalue(),
                        sys.argv[0] + ": somedir: Permission denied.\n")
                return [(dirpath, [], [])]

            walk.side_effect = test_error_handler
            self.hashdb.verify_tree(dirpath)

    def test_verify_tree_prints_raised_errors_and_continues(self):
        with patch('os.walk') as walk:
            dirpath = 'dir'
            walk.return_value = [(dirpath, [], ['file'])]
            with patch('sys.stderr') as stderr:
                buffer = StringIO()
                stderr.write = buffer.write
                with patch('os.path.join') as path_join:
                    path_join.side_effect = OSError(
                        errno.EPERM, 'Permission denied.', 'file')
                    self.hashdb.verify_tree(dirpath)

                self.assertTrue(buffer.getvalue().startswith(
                    sys.argv[0] + ": file: Permission denied.\n"))