Files
dvc/tests/unit/remote/test_base.py
T
Peter Rowlands (변기호) b04964f4b7 remote: locally index list of checksums available on cloud remotes (#3634)
* remote: locally index list of checksums available on cloud remotes

* fix review issues

- use pytest fixtures
- use sha256 digest
- s/fd/fobj/
- check more specific IO errors

* remote: use .dir checksums to validate index

* command: add --drop-index option for push/pull/status

* tests: --drop-index unit tests

* remote: make index threadsafe

* remote: force re-index after gc

* only save/dump index once per command

* remote: add helper index functions update_all/replace_all

* remote: store index as sqlite3 database

* fix deepsource warnings

* rename --drop-index to --clear-index

* rename index.invalidate to index.clear

* functional tests for remote.index

* fix index not being updated on status -c

* remote: add index.intersection()

* remote: use index when making assumptions about remote .dir contents

* add missing func tests

* do not index standalone files

* cleanup index test fixture

* fix deepsource warnings

* update autocompletion scripts/cli help message for --clear-index

* tests: remove unused mocker

* push: only index successfully pushed dirs

- do not index files on partial push/upload

* remote: revert behavior to trust .dir files on remote

- skip unnecessary index check for dir contents if .dir file exists on
  the remote

* fix missing RemoteIndexNoop functions

* separate index validation from status dir exists query

* bugfix: include indexed checksums for all cache_exists cases

* review fix: cleanup debug messages

* tests: fix DS warning

* remove --clear-index

* remove unused index functions
2020-04-21 15:13:45 +03:00

140 lines
4.0 KiB
Python

import math
import mock
import pytest
from dvc.path_info import PathInfo
from dvc.remote.base import RemoteBASE
from dvc.remote.base import RemoteCmdError
from dvc.remote.base import RemoteMissingDepsError
class _CallableOrNone(object):
"""Helper for testing if object is callable() or None."""
def __eq__(self, other):
return other is None or callable(other)
CallableOrNone = _CallableOrNone()
REMOTE_CLS = RemoteBASE
def test_missing_deps(dvc):
requires = {"missing": "missing"}
with mock.patch.object(REMOTE_CLS, "REQUIRES", requires):
with pytest.raises(RemoteMissingDepsError):
REMOTE_CLS(dvc, {})
def test_cmd_error(dvc):
config = {}
cmd = "sed 'hello'"
ret = "1"
err = "sed: expression #1, char 2: extra characters after command"
with mock.patch.object(
REMOTE_CLS,
"remove",
side_effect=RemoteCmdError("base", cmd, ret, err),
):
with pytest.raises(RemoteCmdError):
REMOTE_CLS(dvc, config).remove("file")
@mock.patch.object(RemoteBASE, "_cache_checksums_traverse")
@mock.patch.object(RemoteBASE, "_cache_object_exists")
def test_cache_exists(object_exists, traverse, dvc):
remote = RemoteBASE(dvc, {})
# remote does not support traverse
remote.CAN_TRAVERSE = False
with mock.patch.object(
remote, "cache_checksums", return_value=list(range(256))
):
checksums = set(range(1000))
remote.cache_exists(checksums)
object_exists.assert_called_with(checksums, None, None)
traverse.assert_not_called()
remote.CAN_TRAVERSE = True
# large remote, small local
object_exists.reset_mock()
traverse.reset_mock()
with mock.patch.object(
remote, "cache_checksums", return_value=list(range(256))
):
checksums = list(range(1000))
remote.cache_exists(checksums)
# verify that _cache_paths_with_max() short circuits
# before returning all 256 remote checksums
max_checksums = math.ceil(
remote._max_estimation_size(checksums)
/ pow(16, remote.TRAVERSE_PREFIX_LEN)
)
assert max_checksums < 256
object_exists.assert_called_with(
frozenset(range(max_checksums, 1000)), None, None
)
traverse.assert_not_called()
# large remote, large local
object_exists.reset_mock()
traverse.reset_mock()
remote.JOBS = 16
with mock.patch.object(
remote, "cache_checksums", return_value=list(range(256))
):
checksums = list(range(1000000))
remote.cache_exists(checksums)
object_exists.assert_not_called()
traverse.assert_called_with(
256 * pow(16, remote.TRAVERSE_PREFIX_LEN),
set(range(256)),
None,
None,
)
@mock.patch.object(
RemoteBASE, "cache_checksums", return_value=[],
)
@mock.patch.object(
RemoteBASE, "path_to_checksum", side_effect=lambda x: x,
)
def test_cache_checksums_traverse(path_to_checksum, cache_checksums, dvc):
remote = RemoteBASE(dvc, {})
remote.path_info = PathInfo("foo")
# parallel traverse
size = 256 / remote.JOBS * remote.LIST_OBJECT_PAGE_SIZE
list(remote._cache_checksums_traverse(size, {0}))
for i in range(1, 16):
cache_checksums.assert_any_call(
prefix="{:03x}".format(i), progress_callback=CallableOrNone
)
for i in range(1, 256):
cache_checksums.assert_any_call(
prefix="{:02x}".format(i), progress_callback=CallableOrNone
)
# default traverse (small remote)
size -= 1
cache_checksums.reset_mock()
list(remote._cache_checksums_traverse(size - 1, {0}))
cache_checksums.assert_called_with(
prefix=None, progress_callback=CallableOrNone
)
def test_cache_checksums(dvc):
remote = RemoteBASE(dvc, {})
remote.path_info = PathInfo("foo")
with mock.patch.object(
remote, "list_cache_paths", return_value=["12/3456", "bar"]
):
checksums = list(remote.cache_checksums())
assert checksums == ["123456"]