mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-05 23:09:32 +01:00
tests: introduce dvc.testing
This is a very early version of test utils for dvc plugins. This module consists of existing tests with minimal changes and is going to evolve a lot as our plugins are getting filled in and pytest-test-utils matures.
This commit is contained in:
@@ -1,26 +1,26 @@
|
||||
import locale
|
||||
import pathlib
|
||||
|
||||
from funcy import cached_property
|
||||
|
||||
from .path_info import URLInfo
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class Base(URLInfo):
|
||||
|
||||
class Cloud(ABC):
|
||||
IS_OBJECT_STORAGE = False
|
||||
|
||||
@abstractmethod
|
||||
def is_file(self):
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_dir(self):
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def exists(self):
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
def write_text(self, contents, encoding=None, errors=None):
|
||||
if not encoding:
|
||||
@@ -28,14 +28,19 @@ class Base(URLInfo):
|
||||
assert errors is None
|
||||
self.write_bytes(contents.encode(encoding))
|
||||
|
||||
@abstractmethod
|
||||
def write_bytes(self, contents):
|
||||
raise NotImplementedError
|
||||
|
||||
def read_text(self, encoding=None, errors=None):
|
||||
raise NotImplementedError
|
||||
if not encoding:
|
||||
encoding = locale.getpreferredencoding(False)
|
||||
assert errors is None
|
||||
return self.read_bytes().decode(encoding)
|
||||
|
||||
@abstractmethod
|
||||
def read_bytes(self):
|
||||
raise NotImplementedError
|
||||
pass
|
||||
|
||||
def _gen(self, struct, prefix=None):
|
||||
for name, contents in struct.items():
|
||||
@@ -71,6 +76,7 @@ class Base(URLInfo):
|
||||
def get_url():
|
||||
raise NotImplementedError
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
@abstractmethod
|
||||
def config(self):
|
||||
return {"url": self.url}
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
from .fixtures import * # noqa, pylint: disable=wildcard-import
|
||||
@@ -0,0 +1,207 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
__all__ = [
|
||||
"make_tmp_dir",
|
||||
"tmp_dir",
|
||||
"scm",
|
||||
"dvc",
|
||||
"make_cloud",
|
||||
"cloud",
|
||||
"make_remote",
|
||||
"remote",
|
||||
"workspace",
|
||||
"make_workspace",
|
||||
"docker",
|
||||
"docker_compose",
|
||||
"docker_compose_project_name",
|
||||
"docker_services",
|
||||
]
|
||||
|
||||
CACHE = {} # type: ignore
|
||||
|
||||
|
||||
def _fs_copy(src, dst, ignore=None):
|
||||
import shutil
|
||||
|
||||
if os.path.isdir(src):
|
||||
shutil.copytree(src, dst, ignore=ignore)
|
||||
else:
|
||||
shutil.copy2(src, dst)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def make_tmp_dir(tmp_path_factory, request, worker_id):
|
||||
def make(
|
||||
name, *, scm=False, dvc=False, subdir=False
|
||||
): # pylint: disable=W0621
|
||||
from shutil import ignore_patterns
|
||||
|
||||
from scmrepo.git import Git
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
from .tmp_dir import TmpDir
|
||||
|
||||
cache = CACHE.get((scm, dvc, subdir))
|
||||
if not cache:
|
||||
cache = tmp_path_factory.mktemp("dvc-test-cache" + worker_id)
|
||||
TmpDir(cache).init(scm=scm, dvc=dvc, subdir=subdir)
|
||||
CACHE[(scm, dvc, subdir)] = os.fspath(cache)
|
||||
path = tmp_path_factory.mktemp(name) if isinstance(name, str) else name
|
||||
|
||||
# ignore sqlite files from .dvc/tmp. We might not be closing the cache
|
||||
# connection resulting in PermissionErrors in Windows.
|
||||
ignore = ignore_patterns("cache.db*")
|
||||
for entry in os.listdir(cache):
|
||||
# shutil.copytree's dirs_exist_ok is only available in >=3.8
|
||||
_fs_copy(
|
||||
os.path.join(cache, entry),
|
||||
os.path.join(path, entry),
|
||||
ignore=ignore,
|
||||
)
|
||||
new_dir = TmpDir(path)
|
||||
str_path = os.fspath(new_dir)
|
||||
if dvc:
|
||||
new_dir.dvc = Repo(str_path)
|
||||
if scm:
|
||||
new_dir.scm = (
|
||||
new_dir.dvc.scm if hasattr(new_dir, "dvc") else Git(str_path)
|
||||
)
|
||||
request.addfinalizer(new_dir.close)
|
||||
return new_dir
|
||||
|
||||
return make
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_dir(tmp_path, make_tmp_dir, request, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
fixtures = request.fixturenames
|
||||
return make_tmp_dir(tmp_path, scm="scm" in fixtures, dvc="dvc" in fixtures)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def scm(tmp_dir):
|
||||
return tmp_dir.scm
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dvc(tmp_dir):
|
||||
with tmp_dir.dvc as _dvc:
|
||||
yield _dvc
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_cloud(request):
|
||||
def _make_cloud(typ):
|
||||
return request.getfixturevalue(f"make_{typ}")()
|
||||
|
||||
return _make_cloud
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cloud(make_cloud, request):
|
||||
if not hasattr(request, "param"):
|
||||
pytest.skip("no cloud specified")
|
||||
return make_cloud(request.param)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_remote(tmp_dir, dvc, make_cloud):
|
||||
def _make_remote(name, typ="local", **kwargs):
|
||||
cloud = make_cloud(typ) # pylint: disable=W0621
|
||||
tmp_dir.add_remote(name=name, config=cloud.config, **kwargs)
|
||||
return cloud
|
||||
|
||||
return _make_remote
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def remote(make_remote, request):
|
||||
if not hasattr(request, "param"):
|
||||
pytest.skip("no remote specified")
|
||||
return make_remote("upstream", typ=request.param)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_workspace(tmp_dir, dvc, make_cloud):
|
||||
def _make_workspace(name, typ="local"):
|
||||
from dvc.objects.db import ODBManager
|
||||
|
||||
cloud = make_cloud(typ) # pylint: disable=W0621
|
||||
|
||||
tmp_dir.add_remote(name=name, config=cloud.config, default=False)
|
||||
tmp_dir.add_remote(
|
||||
name=f"{name}-cache", url="remote://workspace/cache", default=False
|
||||
)
|
||||
|
||||
scheme = getattr(cloud, "scheme", "local")
|
||||
if scheme != "http":
|
||||
with dvc.config.edit() as conf:
|
||||
conf["cache"][scheme] = f"{name}-cache"
|
||||
|
||||
dvc.odb = ODBManager(dvc)
|
||||
|
||||
return cloud
|
||||
|
||||
return _make_workspace
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def workspace(make_workspace, request):
|
||||
if not hasattr(request, "param"):
|
||||
pytest.skip("no workspace specified")
|
||||
|
||||
return make_workspace("workspace", typ=request.param)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker():
|
||||
# See https://travis-ci.community/t/docker-linux-containers-on-windows/301
|
||||
if os.environ.get("CI") and os.name == "nt":
|
||||
pytest.skip("disabled for Windows on Github Actions")
|
||||
|
||||
try:
|
||||
subprocess.check_output("docker ps", shell=True)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
pytest.skip("no docker installed")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_compose(docker):
|
||||
try:
|
||||
subprocess.check_output("docker-compose version", shell=True)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
pytest.skip("no docker-compose installed")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_compose_project_name():
|
||||
return "pytest-dvc-test"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_services(
|
||||
docker_compose_file, docker_compose_project_name, tmp_path_factory
|
||||
):
|
||||
# overriding `docker_services` fixture from `pytest_docker` plugin to
|
||||
# only launch docker images once.
|
||||
|
||||
from filelock import FileLock
|
||||
from pytest_docker.plugin import DockerComposeExecutor, Services
|
||||
|
||||
executor = DockerComposeExecutor(
|
||||
docker_compose_file, docker_compose_project_name
|
||||
)
|
||||
|
||||
# making sure we don't accidentally launch docker-compose in parallel,
|
||||
# as it might result in network conflicts. Inspired by:
|
||||
# https://github.com/pytest-dev/pytest-xdist#making-session-scoped-fixtures-execute-only-once
|
||||
lockfile = tmp_path_factory.getbasetemp().parent / "docker-compose.lock"
|
||||
with FileLock(str(lockfile)): # pylint:disable=abstract-class-instantiated
|
||||
executor.execute("up --build -d")
|
||||
|
||||
return Services(executor)
|
||||
@@ -0,0 +1,20 @@
|
||||
from dvc import api
|
||||
from dvc.utils.fs import remove
|
||||
|
||||
|
||||
class TestAPI:
|
||||
def test_get_url(self, tmp_dir, dvc, remote):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
|
||||
expected_url = (remote / "ac/bd18db4cc2f85cedef654fccc4a4d8").url
|
||||
assert api.get_url("foo") == expected_url
|
||||
|
||||
def test_open(self, tmp_dir, dvc, remote):
|
||||
tmp_dir.dvc_gen("foo", "foo-text")
|
||||
dvc.push()
|
||||
|
||||
# Remove cache to force download
|
||||
remove(dvc.odb.local.cache_dir)
|
||||
|
||||
with api.open("foo") as fd:
|
||||
assert fd.read() == "foo-text"
|
||||
@@ -0,0 +1,92 @@
|
||||
import os
|
||||
|
||||
from dvc.utils.fs import move, remove
|
||||
|
||||
|
||||
class TestRemote:
|
||||
def test(self, tmp_dir, dvc, remote): # pylint: disable=W0613
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
out = stage.outs[0]
|
||||
cache = out.cache_path
|
||||
foo_hash = out.hash_info
|
||||
foo_hashes = out.get_used_objs().get(None, set())
|
||||
|
||||
(stage_dir,) = tmp_dir.dvc_gen(
|
||||
{
|
||||
"data_dir": {
|
||||
"data_sub_dir": {"data_sub": "data_sub"},
|
||||
"data": "data",
|
||||
"empty": "",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
out_dir = stage_dir.outs[0]
|
||||
cache_dir = out_dir.cache_path
|
||||
dir_hash = out_dir.hash_info
|
||||
dir_hashes = {dir_hash} | {oid for _, _, oid in out_dir.obj}
|
||||
|
||||
def _check_status(status, **kwargs):
|
||||
for key in ("ok", "missing", "new", "deleted"):
|
||||
expected = kwargs.get(key, set())
|
||||
assert expected == set(getattr(status, key))
|
||||
|
||||
# Check status
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, new={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, new=dir_hashes)
|
||||
|
||||
# Move cache and check status
|
||||
# See issue https://github.com/iterative/dvc/issues/4383 for details
|
||||
backup_dir = dvc.odb.local.cache_dir + ".backup"
|
||||
move(dvc.odb.local.cache_dir, backup_dir)
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, missing={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, missing=dir_hashes)
|
||||
|
||||
# Restore original cache:
|
||||
remove(dvc.odb.local.cache_dir)
|
||||
move(backup_dir, dvc.odb.local.cache_dir)
|
||||
|
||||
# Push and check status
|
||||
dvc.cloud.push(foo_hashes)
|
||||
assert os.path.exists(cache)
|
||||
assert os.path.isfile(cache)
|
||||
|
||||
dvc.cloud.push(dir_hashes)
|
||||
assert os.path.isfile(cache_dir)
|
||||
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, ok={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, ok=dir_hashes)
|
||||
|
||||
# Remove and check status
|
||||
remove(dvc.odb.local.cache_dir)
|
||||
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, deleted={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, deleted=dir_hashes)
|
||||
|
||||
# Pull and check status
|
||||
dvc.cloud.pull(foo_hashes)
|
||||
assert os.path.exists(cache)
|
||||
assert os.path.isfile(cache)
|
||||
with open(cache, encoding="utf-8") as fd:
|
||||
assert fd.read() == "foo"
|
||||
|
||||
dvc.cloud.pull(dir_hashes)
|
||||
assert os.path.isfile(cache_dir)
|
||||
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, ok={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, ok=dir_hashes)
|
||||
@@ -0,0 +1,112 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestImport:
|
||||
def test_import(self, tmp_dir, dvc, workspace):
|
||||
workspace.gen("file", "file")
|
||||
assert not (tmp_dir / "file").exists() # sanity check
|
||||
dvc.imp_url("remote://workspace/file")
|
||||
assert (tmp_dir / "file").read_text() == "file"
|
||||
assert dvc.status() == {}
|
||||
|
||||
def test_import_dir(self, tmp_dir, dvc, workspace, stage_md5, dir_md5):
|
||||
from dvc.objects.db import ODBManager
|
||||
|
||||
workspace.gen(
|
||||
{"dir": {"file": "file", "subdir": {"subfile": "subfile"}}}
|
||||
)
|
||||
|
||||
# remove external cache to make sure that we don't need it
|
||||
# to import dirs
|
||||
with dvc.config.edit() as conf:
|
||||
del conf["cache"]
|
||||
dvc.odb = ODBManager(dvc)
|
||||
|
||||
assert not (tmp_dir / "dir").exists() # sanity check
|
||||
dvc.imp_url("remote://workspace/dir")
|
||||
assert set(os.listdir(tmp_dir / "dir")) == {"file", "subdir"}
|
||||
assert (tmp_dir / "dir" / "file").read_text() == "file"
|
||||
assert list(os.listdir(tmp_dir / "dir" / "subdir")) == ["subfile"]
|
||||
assert (
|
||||
tmp_dir / "dir" / "subdir" / "subfile"
|
||||
).read_text() == "subfile"
|
||||
|
||||
assert dvc.status() == {}
|
||||
|
||||
if stage_md5 is not None and dir_md5 is not None:
|
||||
assert (tmp_dir / "dir.dvc").read_text() == (
|
||||
f"md5: {stage_md5}\n"
|
||||
"frozen: true\n"
|
||||
"deps:\n"
|
||||
f"- md5: {dir_md5}\n"
|
||||
" size: 11\n"
|
||||
" nfiles: 2\n"
|
||||
" path: remote://workspace/dir\n"
|
||||
"outs:\n"
|
||||
"- md5: b6dcab6ccd17ca0a8bf4a215a37d14cc.dir\n"
|
||||
" size: 11\n"
|
||||
" nfiles: 2\n"
|
||||
" path: dir\n"
|
||||
)
|
||||
|
||||
def test_import_empty_dir(
|
||||
self, tmp_dir, dvc, workspace, is_object_storage
|
||||
):
|
||||
# prefix based storage services (e.g s3) doesn't have the real concept
|
||||
# of directories. So instead we create an empty file that ends with a
|
||||
# trailing slash in order to actually support this operation
|
||||
if is_object_storage:
|
||||
contents = ""
|
||||
else:
|
||||
contents = {}
|
||||
|
||||
workspace.gen({"empty_dir/": contents})
|
||||
|
||||
dvc.imp_url("remote://workspace/empty_dir/")
|
||||
|
||||
empty_dir = tmp_dir / "empty_dir"
|
||||
assert empty_dir.is_dir()
|
||||
assert tuple(empty_dir.iterdir()) == ()
|
||||
|
||||
|
||||
class TestAdd:
|
||||
def test_add(self, tmp_dir, dvc, workspace, hash_name, hash_value):
|
||||
from dvc.stage.exceptions import StageExternalOutputsError
|
||||
|
||||
workspace.gen("file", "file")
|
||||
|
||||
with pytest.raises(StageExternalOutputsError):
|
||||
dvc.add(workspace.url)
|
||||
|
||||
dvc.add("remote://workspace/file")
|
||||
assert (tmp_dir / "file.dvc").read_text() == (
|
||||
"outs:\n"
|
||||
f"- {hash_name}: {hash_value}\n"
|
||||
" size: 4\n"
|
||||
" path: remote://workspace/file\n"
|
||||
)
|
||||
assert (workspace / "file").read_text() == "file"
|
||||
assert (
|
||||
workspace / "cache" / hash_value[:2] / hash_value[2:]
|
||||
).read_text() == "file"
|
||||
|
||||
assert dvc.status() == {}
|
||||
|
||||
def test_add_dir(self, tmp_dir, dvc, workspace, hash_name, dir_hash_value):
|
||||
workspace.gen(
|
||||
{"dir": {"file": "file", "subdir": {"subfile": "subfile"}}}
|
||||
)
|
||||
|
||||
dvc.add("remote://workspace/dir")
|
||||
assert (tmp_dir / "dir.dvc").read_text() == (
|
||||
"outs:\n"
|
||||
f"- {hash_name}: {dir_hash_value}\n"
|
||||
" size: 11\n"
|
||||
" nfiles: 2\n"
|
||||
" path: remote://workspace/dir\n"
|
||||
)
|
||||
assert (
|
||||
workspace / "cache" / dir_hash_value[:2] / dir_hash_value[2:]
|
||||
).is_file()
|
||||
@@ -0,0 +1,288 @@
|
||||
"""
|
||||
The goal of this module is making dvc functional tests setup a breeze. This
|
||||
includes a temporary dir, initializing git and DVC repos and bootstrapping some
|
||||
file structure.
|
||||
|
||||
The cornerstone of these fixtures is `tmp_dir`, which creates a temporary dir
|
||||
and changes path to it, it might be combined with `scm` and `dvc` to initialize
|
||||
empty git and DVC repos. `tmp_dir` returns a Path instance, which should save
|
||||
you from using `open()`, `os` and `os.path` utils many times:
|
||||
|
||||
(tmp_dir / "some_file").write_text("some text")
|
||||
# ...
|
||||
assert "some text" == (tmp_dir / "some_file").read_text()
|
||||
assert (tmp_dir / "some_file").exists()
|
||||
|
||||
Additionally it provides `.gen()`, `.scm_gen()` and `.dvc_gen()` methods to
|
||||
bootstrap a required file structure in a single call:
|
||||
|
||||
# Generate a dir with files
|
||||
tmp_dir.gen({"dir": {"file": "file text", "second_file": "..."}})
|
||||
|
||||
# Generate a single file, dirs will be created along the way
|
||||
tmp_dir.gen("dir/file", "file text")
|
||||
|
||||
# Generate + git add
|
||||
tmp_dir.scm_gen({"file1": "...", ...})
|
||||
|
||||
# Generate + git add + git commit
|
||||
tmp_dir.scm_gen({"file1": "...", ...}, commit="add files")
|
||||
|
||||
# Generate + dvc add
|
||||
tmp_dir.dvc_gen({"file1": "...", ...})
|
||||
|
||||
# Generate + dvc add + git commit -am "..."
|
||||
# This commits stages to git not the generated files.
|
||||
tmp_dir.dvc_gen({"file1": "...", ...}, commit="add files")
|
||||
|
||||
Making it easier to bootstrap things has a supergoal of incentivizing a move
|
||||
from global repo template to creating everything inplace, which:
|
||||
|
||||
- makes all path references local to test, enhancing readability
|
||||
- allows using telling filenames, e.g. "git_tracked_file" instead of "foo"
|
||||
- does not create unnecessary files
|
||||
"""
|
||||
|
||||
# pylint: disable=redefined-outer-name, attribute-defined-outside-init
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from functools import partialmethod
|
||||
|
||||
from funcy import lmap, retry
|
||||
|
||||
from dvc.utils import serialize
|
||||
from dvc.utils.fs import makedirs
|
||||
|
||||
|
||||
class TmpDir(pathlib.Path):
|
||||
scheme = "local"
|
||||
|
||||
@property
|
||||
def fs_path(self):
|
||||
return str(self)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self.fs_path
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return {"url": self.url}
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is TmpDir:
|
||||
cls = ( # pylint: disable=self-cls-assignment
|
||||
WindowsTmpDir if os.name == "nt" else PosixTmpDir
|
||||
)
|
||||
# init parameter and `_init` method has been removed in Python 3.10.
|
||||
kw = {"init": False} if sys.version_info < (3, 10) else {}
|
||||
self = cls._from_parts( # pylint: disable=unexpected-keyword-arg
|
||||
args, **kw
|
||||
)
|
||||
if not self._flavour.is_supported:
|
||||
raise NotImplementedError(
|
||||
f"cannot instantiate {cls.__name__!r} on your system"
|
||||
)
|
||||
if sys.version_info < (3, 10):
|
||||
self._init() # pylint: disable=no-member
|
||||
return self
|
||||
|
||||
def init(self, *, scm=False, dvc=False, subdir=False):
|
||||
from scmrepo.git import Git
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
assert not scm or not hasattr(self, "scm")
|
||||
assert not dvc or not hasattr(self, "dvc")
|
||||
|
||||
str_path = os.fspath(self)
|
||||
|
||||
if scm:
|
||||
git_init(str_path)
|
||||
if dvc:
|
||||
self.dvc = Repo.init(
|
||||
str_path,
|
||||
no_scm=not scm and not hasattr(self, "scm"),
|
||||
subdir=subdir,
|
||||
)
|
||||
if scm:
|
||||
self.scm = self.dvc.scm if hasattr(self, "dvc") else Git(str_path)
|
||||
if dvc and hasattr(self, "scm"):
|
||||
self.scm.commit("init dvc")
|
||||
|
||||
def close(self):
|
||||
if hasattr(self, "scm"):
|
||||
self.scm.close()
|
||||
if hasattr(self, "dvc"):
|
||||
self.dvc.close()
|
||||
|
||||
def _require(self, name):
|
||||
if not hasattr(self, name):
|
||||
raise TypeError(
|
||||
"Can't use {name} for this temporary dir. "
|
||||
'Did you forget to use "{name}" fixture?'.format(name=name)
|
||||
)
|
||||
|
||||
# Bootstrapping methods
|
||||
def gen(self, struct, text=""):
|
||||
if isinstance(struct, (str, bytes, pathlib.PurePath)):
|
||||
struct = {struct: text}
|
||||
|
||||
return self._gen(struct)
|
||||
|
||||
def _gen(self, struct, prefix=None):
|
||||
paths = []
|
||||
for name, contents in struct.items():
|
||||
path = (prefix or self) / name
|
||||
|
||||
if isinstance(contents, dict):
|
||||
if not contents:
|
||||
makedirs(path, exist_ok=True)
|
||||
else:
|
||||
self._gen(contents, prefix=path)
|
||||
else:
|
||||
makedirs(path.parent, exist_ok=True)
|
||||
if isinstance(contents, bytes):
|
||||
path.write_bytes(contents)
|
||||
else:
|
||||
path.write_text(contents, encoding="utf-8")
|
||||
paths.append(path)
|
||||
return paths
|
||||
|
||||
def dvc_gen(self, struct, text="", commit=None):
|
||||
paths = self.gen(struct, text)
|
||||
return self.dvc_add(paths, commit=commit)
|
||||
|
||||
def scm_gen(self, struct, text="", commit=None):
|
||||
paths = self.gen(struct, text)
|
||||
return self.scm_add(paths, commit=commit)
|
||||
|
||||
def commit(self, output_paths, msg):
|
||||
def to_gitignore(stage_path):
|
||||
from scmrepo.git import Git
|
||||
|
||||
return os.path.join(os.path.dirname(stage_path), Git.GITIGNORE)
|
||||
|
||||
gitignores = [
|
||||
to_gitignore(s)
|
||||
for s in output_paths
|
||||
if os.path.exists(to_gitignore(s))
|
||||
]
|
||||
return self.scm_add(output_paths + gitignores, commit=msg)
|
||||
|
||||
def dvc_add(self, filenames, commit=None):
|
||||
self._require("dvc")
|
||||
filenames = _coerce_filenames(filenames)
|
||||
|
||||
stages = self.dvc.add(filenames)
|
||||
if commit:
|
||||
self.commit([s.path for s in stages], msg=commit)
|
||||
return stages
|
||||
|
||||
def scm_add(self, filenames, commit=None):
|
||||
self._require("scm")
|
||||
filenames = _coerce_filenames(filenames)
|
||||
self.scm.add(filenames)
|
||||
if commit:
|
||||
self.scm.commit(commit)
|
||||
|
||||
def add_remote(
|
||||
self, *, url=None, config=None, name="upstream", default=True
|
||||
):
|
||||
self._require("dvc")
|
||||
|
||||
assert bool(url) ^ bool(config)
|
||||
|
||||
if url:
|
||||
config = {"url": url}
|
||||
|
||||
with self.dvc.config.edit() as conf:
|
||||
conf["remote"][name] = config
|
||||
if default:
|
||||
conf["core"]["remote"] = name
|
||||
|
||||
if hasattr(self, "scm"):
|
||||
self.scm.add(self.dvc.config.files["repo"])
|
||||
self.scm.commit(f"add '{name}' remote")
|
||||
|
||||
return url or config["url"]
|
||||
|
||||
# contexts
|
||||
@contextmanager
|
||||
def chdir(self):
|
||||
old = os.getcwd()
|
||||
try:
|
||||
os.chdir(self)
|
||||
yield
|
||||
finally:
|
||||
os.chdir(old)
|
||||
|
||||
@contextmanager
|
||||
def branch(self, name, new=False):
|
||||
self._require("scm")
|
||||
old = self.scm.active_branch()
|
||||
try:
|
||||
self.scm.checkout(name, create_new=new)
|
||||
yield
|
||||
finally:
|
||||
self.scm.checkout(old)
|
||||
|
||||
def read_text(self, *args, **kwargs): # pylint: disable=signature-differs
|
||||
# NOTE: on windows we'll get PermissionError instead of
|
||||
# IsADirectoryError when we try to `open` a directory, so we can't
|
||||
# rely on exception flow control
|
||||
if self.is_dir():
|
||||
return {
|
||||
path.name: path.read_text(*args, **kwargs)
|
||||
for path in self.iterdir()
|
||||
}
|
||||
return super().read_text(*args, encoding="utf-8", **kwargs)
|
||||
|
||||
def hash_to_path(self, hash_):
|
||||
return str(self / hash_[0:2] / hash_[2:])
|
||||
|
||||
def dump(self, *args, **kwargs):
|
||||
return serialize.DUMPERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
def parse(self, *args, **kwargs):
|
||||
return serialize.LOADERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
def modify(self, *args, **kwargs):
|
||||
return serialize.MODIFIERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
load_yaml = partialmethod(serialize.load_yaml)
|
||||
dump_yaml = partialmethod(serialize.dump_yaml)
|
||||
load_json = partialmethod(serialize.load_json)
|
||||
dump_json = partialmethod(serialize.dump_json)
|
||||
load_toml = partialmethod(serialize.load_toml)
|
||||
dump_toml = partialmethod(serialize.dump_toml)
|
||||
|
||||
|
||||
def _coerce_filenames(filenames):
|
||||
if isinstance(filenames, (str, bytes, pathlib.PurePath)):
|
||||
filenames = [filenames]
|
||||
return lmap(os.fspath, filenames)
|
||||
|
||||
|
||||
class WindowsTmpDir(TmpDir, pathlib.PureWindowsPath):
|
||||
pass
|
||||
|
||||
|
||||
class PosixTmpDir(TmpDir, pathlib.PurePosixPath):
|
||||
pass
|
||||
|
||||
|
||||
def git_init(path):
|
||||
from git import Repo
|
||||
from git.exc import GitCommandNotFound
|
||||
|
||||
# NOTE: handles EAGAIN error on BSD systems (osx in our case).
|
||||
# Otherwise when running tests you might get this exception:
|
||||
#
|
||||
# GitCommandNotFound: Cmd('git') not found due to:
|
||||
# OSError('[Errno 35] Resource temporarily unavailable')
|
||||
git = retry(5, GitCommandNotFound)(Repo.init)(path)
|
||||
git.close()
|
||||
@@ -158,6 +158,8 @@ tests =
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
dvc.testing
|
||||
dvc.testing.*
|
||||
tests
|
||||
tests.*
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ from contextlib import suppress
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.testing.fixtures import * # noqa, pylint: disable=wildcard-import
|
||||
|
||||
from .dir_helpers import * # noqa, pylint: disable=wildcard-import
|
||||
from .remotes import * # noqa, pylint: disable=wildcard-import
|
||||
from .utils.scriptify import scriptify
|
||||
|
||||
+10
-309
@@ -47,23 +47,14 @@ from global repo template to creating everything inplace, which:
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from functools import partialmethod
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
from funcy import lmap, retry
|
||||
|
||||
from dvc.logger import disable_other_loggers
|
||||
from dvc.utils import serialize
|
||||
from dvc.utils.fs import makedirs
|
||||
from dvc.testing.tmp_dir import git_init
|
||||
|
||||
__all__ = [
|
||||
"make_tmp_dir",
|
||||
"tmp_dir",
|
||||
"scm",
|
||||
"dvc",
|
||||
"run_copy",
|
||||
"run_head",
|
||||
"erepo_dir",
|
||||
@@ -78,299 +69,6 @@ __all__ = [
|
||||
disable_other_loggers()
|
||||
|
||||
|
||||
class TmpDir(pathlib.Path):
|
||||
scheme = "local"
|
||||
|
||||
@property
|
||||
def fs_path(self):
|
||||
return str(self)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is TmpDir:
|
||||
cls = ( # pylint: disable=self-cls-assignment
|
||||
WindowsTmpDir if os.name == "nt" else PosixTmpDir
|
||||
)
|
||||
# init parameter and `_init` method has been removed in Python 3.10.
|
||||
kw = {"init": False} if sys.version_info < (3, 10) else {}
|
||||
self = cls._from_parts( # pylint: disable=unexpected-keyword-arg
|
||||
args, **kw
|
||||
)
|
||||
if not self._flavour.is_supported:
|
||||
raise NotImplementedError(
|
||||
f"cannot instantiate {cls.__name__!r} on your system"
|
||||
)
|
||||
if sys.version_info < (3, 10):
|
||||
self._init() # pylint: disable=no-member
|
||||
return self
|
||||
|
||||
def init(self, *, scm=False, dvc=False, subdir=False):
|
||||
from scmrepo.git import Git
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
assert not scm or not hasattr(self, "scm")
|
||||
assert not dvc or not hasattr(self, "dvc")
|
||||
|
||||
str_path = os.fspath(self)
|
||||
|
||||
if scm:
|
||||
git_init(str_path)
|
||||
if dvc:
|
||||
self.dvc = Repo.init(
|
||||
str_path,
|
||||
no_scm=not scm and not hasattr(self, "scm"),
|
||||
subdir=subdir,
|
||||
)
|
||||
if scm:
|
||||
self.scm = self.dvc.scm if hasattr(self, "dvc") else Git(str_path)
|
||||
if dvc and hasattr(self, "scm"):
|
||||
self.scm.commit("init dvc")
|
||||
|
||||
def close(self):
|
||||
if hasattr(self, "scm"):
|
||||
self.scm.close()
|
||||
if hasattr(self, "dvc"):
|
||||
self.dvc.close()
|
||||
|
||||
def _require(self, name):
|
||||
if not hasattr(self, name):
|
||||
raise TypeError(
|
||||
"Can't use {name} for this temporary dir. "
|
||||
'Did you forget to use "{name}" fixture?'.format(name=name)
|
||||
)
|
||||
|
||||
# Bootstrapping methods
|
||||
def gen(self, struct, text=""):
|
||||
if isinstance(struct, (str, bytes, pathlib.PurePath)):
|
||||
struct = {struct: text}
|
||||
|
||||
return self._gen(struct)
|
||||
|
||||
def _gen(self, struct, prefix=None):
|
||||
paths = []
|
||||
for name, contents in struct.items():
|
||||
path = (prefix or self) / name
|
||||
|
||||
if isinstance(contents, dict):
|
||||
if not contents:
|
||||
makedirs(path, exist_ok=True)
|
||||
else:
|
||||
self._gen(contents, prefix=path)
|
||||
else:
|
||||
makedirs(path.parent, exist_ok=True)
|
||||
if isinstance(contents, bytes):
|
||||
path.write_bytes(contents)
|
||||
else:
|
||||
path.write_text(contents, encoding="utf-8")
|
||||
paths.append(path)
|
||||
return paths
|
||||
|
||||
def dvc_gen(self, struct, text="", commit=None):
|
||||
paths = self.gen(struct, text)
|
||||
return self.dvc_add(paths, commit=commit)
|
||||
|
||||
def scm_gen(self, struct, text="", commit=None):
|
||||
paths = self.gen(struct, text)
|
||||
return self.scm_add(paths, commit=commit)
|
||||
|
||||
def commit(self, output_paths, msg):
|
||||
def to_gitignore(stage_path):
|
||||
from dvc.scm import Git
|
||||
|
||||
return os.path.join(os.path.dirname(stage_path), Git.GITIGNORE)
|
||||
|
||||
gitignores = [
|
||||
to_gitignore(s)
|
||||
for s in output_paths
|
||||
if os.path.exists(to_gitignore(s))
|
||||
]
|
||||
return self.scm_add(output_paths + gitignores, commit=msg)
|
||||
|
||||
def dvc_add(self, filenames, commit=None):
|
||||
self._require("dvc")
|
||||
filenames = _coerce_filenames(filenames)
|
||||
|
||||
stages = self.dvc.add(filenames)
|
||||
if commit:
|
||||
self.commit([s.path for s in stages], msg=commit)
|
||||
return stages
|
||||
|
||||
def scm_add(self, filenames, commit=None):
|
||||
self._require("scm")
|
||||
filenames = _coerce_filenames(filenames)
|
||||
self.scm.add(filenames)
|
||||
if commit:
|
||||
self.scm.commit(commit)
|
||||
|
||||
def add_remote(
|
||||
self, *, url=None, config=None, name="upstream", default=True
|
||||
):
|
||||
self._require("dvc")
|
||||
|
||||
assert bool(url) ^ bool(config)
|
||||
|
||||
if url:
|
||||
config = {"url": url}
|
||||
|
||||
with self.dvc.config.edit() as conf:
|
||||
conf["remote"][name] = config
|
||||
if default:
|
||||
conf["core"]["remote"] = name
|
||||
|
||||
if hasattr(self, "scm"):
|
||||
self.scm.add(self.dvc.config.files["repo"])
|
||||
self.scm.commit(f"add '{name}' remote")
|
||||
|
||||
return url or config["url"]
|
||||
|
||||
# contexts
|
||||
@contextmanager
|
||||
def chdir(self):
|
||||
old = os.getcwd()
|
||||
try:
|
||||
os.chdir(self)
|
||||
yield
|
||||
finally:
|
||||
os.chdir(old)
|
||||
|
||||
@contextmanager
|
||||
def branch(self, name, new=False):
|
||||
self._require("scm")
|
||||
old = self.scm.active_branch()
|
||||
try:
|
||||
self.scm.checkout(name, create_new=new)
|
||||
yield
|
||||
finally:
|
||||
self.scm.checkout(old)
|
||||
|
||||
def read_text(self, *args, **kwargs): # pylint: disable=signature-differs
|
||||
# NOTE: on windows we'll get PermissionError instead of
|
||||
# IsADirectoryError when we try to `open` a directory, so we can't
|
||||
# rely on exception flow control
|
||||
if self.is_dir():
|
||||
return {
|
||||
path.name: path.read_text(*args, **kwargs)
|
||||
for path in self.iterdir()
|
||||
}
|
||||
return super().read_text(*args, encoding="utf-8", **kwargs)
|
||||
|
||||
def hash_to_path(self, hash_):
|
||||
return str(self / hash_[0:2] / hash_[2:])
|
||||
|
||||
def dump(self, *args, **kwargs):
|
||||
return serialize.DUMPERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
def parse(self, *args, **kwargs):
|
||||
return serialize.LOADERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
def modify(self, *args, **kwargs):
|
||||
return serialize.MODIFIERS[self.suffix](self, *args, **kwargs)
|
||||
|
||||
load_yaml = partialmethod(serialize.load_yaml)
|
||||
dump_yaml = partialmethod(serialize.dump_yaml)
|
||||
load_json = partialmethod(serialize.load_json)
|
||||
dump_json = partialmethod(serialize.dump_json)
|
||||
load_toml = partialmethod(serialize.load_toml)
|
||||
dump_toml = partialmethod(serialize.dump_toml)
|
||||
|
||||
|
||||
def _coerce_filenames(filenames):
|
||||
if isinstance(filenames, (str, bytes, pathlib.PurePath)):
|
||||
filenames = [filenames]
|
||||
return lmap(os.fspath, filenames)
|
||||
|
||||
|
||||
class WindowsTmpDir(TmpDir, pathlib.PureWindowsPath):
|
||||
pass
|
||||
|
||||
|
||||
class PosixTmpDir(TmpDir, pathlib.PurePosixPath):
|
||||
pass
|
||||
|
||||
|
||||
CACHE = {}
|
||||
|
||||
|
||||
def _fs_copy(src, dst, ignore=None):
|
||||
import shutil
|
||||
|
||||
if os.path.isdir(src):
|
||||
shutil.copytree(src, dst, ignore=ignore)
|
||||
else:
|
||||
shutil.copy2(src, dst)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def make_tmp_dir(tmp_path_factory, request, worker_id):
|
||||
def make(name, *, scm=False, dvc=False, subdir=False):
|
||||
from shutil import ignore_patterns
|
||||
|
||||
from scmrepo.git import Git
|
||||
|
||||
from dvc.repo import Repo
|
||||
|
||||
cache = CACHE.get((scm, dvc, subdir))
|
||||
if not cache:
|
||||
cache = tmp_path_factory.mktemp("dvc-test-cache" + worker_id)
|
||||
TmpDir(cache).init(scm=scm, dvc=dvc, subdir=subdir)
|
||||
CACHE[(scm, dvc, subdir)] = os.fspath(cache)
|
||||
path = tmp_path_factory.mktemp(name) if isinstance(name, str) else name
|
||||
|
||||
# ignore sqlite files from .dvc/tmp. We might not be closing the cache
|
||||
# connection resulting in PermissionErrors in Windows.
|
||||
ignore = ignore_patterns("cache.db*")
|
||||
for entry in os.listdir(cache):
|
||||
# shutil.copytree's dirs_exist_ok is only available in >=3.8
|
||||
_fs_copy(
|
||||
os.path.join(cache, entry),
|
||||
os.path.join(path, entry),
|
||||
ignore=ignore,
|
||||
)
|
||||
new_dir = TmpDir(path)
|
||||
str_path = os.fspath(new_dir)
|
||||
if dvc:
|
||||
new_dir.dvc = Repo(str_path)
|
||||
if scm:
|
||||
new_dir.scm = (
|
||||
new_dir.dvc.scm if hasattr(new_dir, "dvc") else Git(str_path)
|
||||
)
|
||||
request.addfinalizer(new_dir.close)
|
||||
return new_dir
|
||||
|
||||
return make
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_dir(tmp_path, make_tmp_dir, request, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
fixtures = request.fixturenames
|
||||
return make_tmp_dir(tmp_path, scm="scm" in fixtures, dvc="dvc" in fixtures)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def scm(tmp_dir):
|
||||
return tmp_dir.scm
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dvc(tmp_dir):
|
||||
with tmp_dir.dvc as _dvc:
|
||||
yield _dvc
|
||||
|
||||
|
||||
def git_init(path):
|
||||
from git import Repo
|
||||
from git.exc import GitCommandNotFound
|
||||
|
||||
# NOTE: handles EAGAIN error on BSD systems (osx in our case).
|
||||
# Otherwise when running tests you might get this exception:
|
||||
#
|
||||
# GitCommandNotFound: Cmd('git') not found due to:
|
||||
# OSError('[Errno 35] Resource temporarily unavailable')
|
||||
git = retry(5, GitCommandNotFound)(Repo.init)(path)
|
||||
git.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_copy(tmp_dir, dvc):
|
||||
tmp_dir.gen(
|
||||
@@ -443,14 +141,19 @@ def git_dir(make_tmp_dir):
|
||||
return path
|
||||
|
||||
|
||||
class GitRemote:
|
||||
def __init__(self, tmp_dir, name, url):
|
||||
self.tmp_dir = tmp_dir
|
||||
self.remote = name
|
||||
self.url = url
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def git_upstream(tmp_dir, erepo_dir, git_dir, request):
|
||||
remote = erepo_dir if "dvc" in request.fixturenames else git_dir
|
||||
url = "file://{}".format(remote.resolve().as_posix())
|
||||
tmp_dir.scm.gitpython.repo.create_remote("upstream", url)
|
||||
remote.remote = "upstream"
|
||||
remote.url = url
|
||||
return remote
|
||||
return GitRemote(remote, "upstream", url)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -458,6 +161,4 @@ def git_downstream(tmp_dir, erepo_dir, git_dir, request):
|
||||
remote = erepo_dir if "dvc" in request.fixturenames else git_dir
|
||||
url = "file://{}".format(tmp_dir.resolve().as_posix())
|
||||
remote.scm.gitpython.repo.create_remote("upstream", url)
|
||||
remote.remote = "upstream"
|
||||
remote.url = url
|
||||
return remote
|
||||
return GitRemote(remote, "upstream", url)
|
||||
|
||||
@@ -219,7 +219,7 @@ def test_auto_push_during_iterations(
|
||||
):
|
||||
# set up remote repo
|
||||
remote = git_upstream.url if use_url else git_upstream.remote
|
||||
git_upstream.scm.fetch_refspecs(str(tmp_dir), ["master:master"])
|
||||
git_upstream.tmp_dir.scm.fetch_refspecs(str(tmp_dir), ["master:master"])
|
||||
monkeypatch.setenv(DVC_EXP_GIT_REMOTE, remote)
|
||||
auto_push_spy = mocker.spy(BaseExecutor, "_auto_push")
|
||||
|
||||
@@ -233,7 +233,7 @@ def test_auto_push_during_iterations(
|
||||
assert (tmp_dir / "foo").read_text() == "4"
|
||||
exp = first(results)
|
||||
ref_info = first(exp_refs_by_rev(scm, exp))
|
||||
assert git_upstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
assert auto_push_spy.call_count == 2
|
||||
assert auto_push_spy.call_args[0][2] == remote
|
||||
|
||||
@@ -20,30 +20,30 @@ def test_push(tmp_dir, scm, dvc, git_upstream, exp_stage, use_url):
|
||||
ref_info = first(exp_refs_by_rev(scm, exp))
|
||||
|
||||
dvc.experiments.push(remote, ref_info.name)
|
||||
assert git_upstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
git_upstream.scm.remove_ref(str(ref_info))
|
||||
git_upstream.tmp_dir.scm.remove_ref(str(ref_info))
|
||||
|
||||
dvc.experiments.push(remote, str(ref_info))
|
||||
assert git_upstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
|
||||
def test_push_diverged(tmp_dir, scm, dvc, git_upstream, exp_stage):
|
||||
git_upstream.scm_gen("foo", "foo", commit="init")
|
||||
remote_rev = git_upstream.scm.get_rev()
|
||||
git_upstream.tmp_dir.scm_gen("foo", "foo", commit="init")
|
||||
remote_rev = git_upstream.tmp_dir.scm.get_rev()
|
||||
|
||||
results = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
|
||||
exp = first(results)
|
||||
ref_info = first(exp_refs_by_rev(scm, exp))
|
||||
|
||||
git_upstream.scm.set_ref(str(ref_info), remote_rev)
|
||||
git_upstream.tmp_dir.scm.set_ref(str(ref_info), remote_rev)
|
||||
|
||||
with pytest.raises(DvcException):
|
||||
dvc.experiments.push(git_upstream.remote, ref_info.name)
|
||||
assert git_upstream.scm.get_ref(str(ref_info)) == remote_rev
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info)) == remote_rev
|
||||
|
||||
dvc.experiments.push(git_upstream.remote, ref_info.name, force=True)
|
||||
assert git_upstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
|
||||
def test_push_checkpoint(tmp_dir, scm, dvc, git_upstream, checkpoint_stage):
|
||||
@@ -54,7 +54,7 @@ def test_push_checkpoint(tmp_dir, scm, dvc, git_upstream, checkpoint_stage):
|
||||
ref_info_a = first(exp_refs_by_rev(scm, exp_a))
|
||||
|
||||
dvc.experiments.push(git_upstream.remote, ref_info_a.name, force=True)
|
||||
assert git_upstream.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
|
||||
results = dvc.experiments.run(
|
||||
checkpoint_stage.addressing, checkpoint_resume=exp_a
|
||||
@@ -65,7 +65,7 @@ def test_push_checkpoint(tmp_dir, scm, dvc, git_upstream, checkpoint_stage):
|
||||
tmp_dir.scm_gen("new", "new", commit="new")
|
||||
|
||||
dvc.experiments.push(git_upstream.remote, ref_info_b.name, force=True)
|
||||
assert git_upstream.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
|
||||
|
||||
def test_push_ambiguous_name(tmp_dir, scm, dvc, git_upstream, exp_stage):
|
||||
@@ -87,7 +87,7 @@ def test_push_ambiguous_name(tmp_dir, scm, dvc, git_upstream, exp_stage):
|
||||
ref_info_b = first(exp_refs_by_rev(scm, exp_b))
|
||||
|
||||
dvc.experiments.push(remote, "foo")
|
||||
assert git_upstream.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
|
||||
tmp_dir.scm_gen("new", "new 2", commit="new 2")
|
||||
|
||||
@@ -95,7 +95,7 @@ def test_push_ambiguous_name(tmp_dir, scm, dvc, git_upstream, exp_stage):
|
||||
dvc.experiments.push(remote, "foo")
|
||||
|
||||
dvc.experiments.push(remote, str(ref_info_a))
|
||||
assert git_upstream.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
|
||||
|
||||
@pytest.mark.parametrize("use_url", [True, False])
|
||||
@@ -117,8 +117,8 @@ def test_list_remote(tmp_dir, scm, dvc, git_downstream, exp_stage, use_url):
|
||||
|
||||
remote = git_downstream.url if use_url else git_downstream.remote
|
||||
|
||||
assert git_downstream.scm.get_ref("HEAD") != scm.get_ref("HEAD")
|
||||
downstream_exp = git_downstream.dvc.experiments
|
||||
assert git_downstream.tmp_dir.scm.get_ref("HEAD") != scm.get_ref("HEAD")
|
||||
downstream_exp = git_downstream.tmp_dir.dvc.experiments
|
||||
assert downstream_exp.ls(git_remote=remote) == {}
|
||||
|
||||
exp_list = downstream_exp.ls(rev=baseline_a, git_remote=remote)
|
||||
@@ -138,7 +138,7 @@ def test_pull(tmp_dir, scm, dvc, git_downstream, exp_stage, use_url):
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
|
||||
remote = git_downstream.url if use_url else git_downstream.remote
|
||||
downstream_exp = git_downstream.dvc.experiments
|
||||
downstream_exp = git_downstream.tmp_dir.dvc.experiments
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
downstream_exp.pull(remote, "foo")
|
||||
|
||||
@@ -147,31 +147,31 @@ def test_pull(tmp_dir, scm, dvc, git_downstream, exp_stage, use_url):
|
||||
ref_info = first(exp_refs_by_rev(scm, exp))
|
||||
|
||||
downstream_exp.pull(remote, ref_info.name)
|
||||
assert git_downstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
git_downstream.scm.remove_ref(str(ref_info))
|
||||
git_downstream.tmp_dir.scm.remove_ref(str(ref_info))
|
||||
|
||||
downstream_exp.pull(remote, str(ref_info))
|
||||
assert git_downstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
|
||||
def test_pull_diverged(tmp_dir, scm, dvc, git_downstream, exp_stage):
|
||||
git_downstream.scm_gen("foo", "foo", commit="init")
|
||||
remote_rev = git_downstream.scm.get_rev()
|
||||
git_downstream.tmp_dir.scm_gen("foo", "foo", commit="init")
|
||||
remote_rev = git_downstream.tmp_dir.scm.get_rev()
|
||||
|
||||
results = dvc.experiments.run(exp_stage.addressing, params=["foo=2"])
|
||||
exp = first(results)
|
||||
ref_info = first(exp_refs_by_rev(scm, exp))
|
||||
|
||||
git_downstream.scm.set_ref(str(ref_info), remote_rev)
|
||||
git_downstream.tmp_dir.scm.set_ref(str(ref_info), remote_rev)
|
||||
|
||||
downstream_exp = git_downstream.dvc.experiments
|
||||
downstream_exp = git_downstream.tmp_dir.dvc.experiments
|
||||
with pytest.raises(DvcException):
|
||||
downstream_exp.pull(git_downstream.remote, ref_info.name)
|
||||
assert git_downstream.scm.get_ref(str(ref_info)) == remote_rev
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info)) == remote_rev
|
||||
|
||||
downstream_exp.pull(git_downstream.remote, ref_info.name, force=True)
|
||||
assert git_downstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
|
||||
def test_pull_checkpoint(tmp_dir, scm, dvc, git_downstream, checkpoint_stage):
|
||||
@@ -181,9 +181,9 @@ def test_pull_checkpoint(tmp_dir, scm, dvc, git_downstream, checkpoint_stage):
|
||||
exp_a = first(results)
|
||||
ref_info_a = first(exp_refs_by_rev(scm, exp_a))
|
||||
|
||||
downstream_exp = git_downstream.dvc.experiments
|
||||
downstream_exp = git_downstream.tmp_dir.dvc.experiments
|
||||
downstream_exp.pull(git_downstream.remote, ref_info_a.name, force=True)
|
||||
assert git_downstream.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
|
||||
results = dvc.experiments.run(
|
||||
checkpoint_stage.addressing, checkpoint_resume=exp_a
|
||||
@@ -192,7 +192,7 @@ def test_pull_checkpoint(tmp_dir, scm, dvc, git_downstream, checkpoint_stage):
|
||||
ref_info_b = first(exp_refs_by_rev(scm, exp_b))
|
||||
|
||||
downstream_exp.pull(git_downstream.remote, ref_info_b.name, force=True)
|
||||
assert git_downstream.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
|
||||
|
||||
def test_pull_ambiguous_name(tmp_dir, scm, dvc, git_downstream, exp_stage):
|
||||
@@ -212,16 +212,16 @@ def test_pull_ambiguous_name(tmp_dir, scm, dvc, git_downstream, exp_stage):
|
||||
ref_info_b = first(exp_refs_by_rev(scm, exp_b))
|
||||
|
||||
remote = git_downstream.remote
|
||||
downstream_exp = git_downstream.dvc.experiments
|
||||
downstream_exp = git_downstream.tmp_dir.dvc.experiments
|
||||
with pytest.raises(InvalidArgumentError):
|
||||
downstream_exp.pull(remote, "foo")
|
||||
|
||||
downstream_exp.pull(remote, str(ref_info_b))
|
||||
assert git_downstream.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info_b)) == exp_b
|
||||
|
||||
with git_downstream.scm.detach_head(ref_info_a.baseline_sha):
|
||||
with git_downstream.tmp_dir.scm.detach_head(ref_info_a.baseline_sha):
|
||||
downstream_exp.pull(remote, "foo")
|
||||
assert git_downstream.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
assert git_downstream.tmp_dir.scm.get_ref(str(ref_info_a)) == exp_a
|
||||
|
||||
|
||||
def test_push_pull_cache(
|
||||
|
||||
@@ -107,12 +107,14 @@ def test_remove_remote(tmp_dir, scm, dvc, exp_stage, git_upstream, use_url):
|
||||
ref_info = first(exp_refs_by_rev(scm, exp))
|
||||
ref_info_list.append(ref_info)
|
||||
dvc.experiments.push(remote, ref_info.name)
|
||||
assert git_upstream.scm.get_ref(str(ref_info)) == exp
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info)) == exp
|
||||
|
||||
dvc.experiments.remove(
|
||||
remote=remote, exp_names=[str(ref_info_list[0]), ref_info_list[1].name]
|
||||
)
|
||||
|
||||
assert git_upstream.scm.get_ref(str(ref_info_list[0])) is None
|
||||
assert git_upstream.scm.get_ref(str(ref_info_list[1])) is None
|
||||
assert git_upstream.scm.get_ref(str(ref_info_list[2])) == exp_list[2]
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info_list[0])) is None
|
||||
assert git_upstream.tmp_dir.scm.get_ref(str(ref_info_list[1])) is None
|
||||
assert (
|
||||
git_upstream.tmp_dir.scm.get_ref(str(ref_info_list[2])) == exp_list[2]
|
||||
)
|
||||
|
||||
+70
-96
@@ -34,6 +34,7 @@ from dvc.stage.exceptions import (
|
||||
StagePathNotFoundError,
|
||||
)
|
||||
from dvc.system import System
|
||||
from dvc.testing.test_workspace import TestAdd
|
||||
from dvc.utils import LARGE_DIR_SIZE, file_md5, relpath
|
||||
from dvc.utils.fs import path_isin
|
||||
from dvc.utils.serialize import YAMLFileCorruptedError, load_yaml
|
||||
@@ -291,59 +292,72 @@ def test_add_filtered_files_in_dir(
|
||||
assert stage.outs[0].def_path in expected_def_paths
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"workspace, hash_name, hash_value",
|
||||
[
|
||||
(
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
"md5",
|
||||
"8c7dd922ad47494fc02c388e12c00eac",
|
||||
),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"md5",
|
||||
"8c7dd922ad47494fc02c388e12c00eac",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
class TestAddExternal(TestAdd):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace, hash_name, hash_value",
|
||||
[
|
||||
(
|
||||
"local",
|
||||
"md5",
|
||||
"8c7dd922ad47494fc02c388e12c00eac",
|
||||
),
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("s3"),
|
||||
"etag",
|
||||
"8c7dd922ad47494fc02c388e12c00eac",
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
"checksum",
|
||||
"000002000000000000000000a86fe4d846edc1bf4c355cb6112f141e",
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("webhdfs"),
|
||||
"checksum",
|
||||
"000002000000000000000000a86fe4d846edc1bf4c355cb6112f141e00000000",
|
||||
),
|
||||
],
|
||||
indirect=["workspace"],
|
||||
)
|
||||
def test_add_external_file(tmp_dir, dvc, workspace, hash_name, hash_value):
|
||||
workspace.gen("file", "file")
|
||||
|
||||
with pytest.raises(StageExternalOutputsError):
|
||||
dvc.add(workspace.url)
|
||||
|
||||
dvc.add("remote://workspace/file")
|
||||
assert (tmp_dir / "file.dvc").read_text() == (
|
||||
"outs:\n"
|
||||
f"- {hash_name}: {hash_value}\n"
|
||||
" size: 4\n"
|
||||
" path: remote://workspace/file\n"
|
||||
pytest.param(
|
||||
"ssh",
|
||||
"md5",
|
||||
"8c7dd922ad47494fc02c388e12c00eac",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
(
|
||||
"s3",
|
||||
"etag",
|
||||
"8c7dd922ad47494fc02c388e12c00eac",
|
||||
),
|
||||
(
|
||||
"hdfs",
|
||||
"checksum",
|
||||
"000002000000000000000000a86fe4d846edc1bf4c355cb6112f141e",
|
||||
),
|
||||
(
|
||||
"webhdfs",
|
||||
"checksum",
|
||||
"000002000000000000000000a86fe4d846edc1bf4c355cb6112f141e00000000", # noqa: E501
|
||||
),
|
||||
],
|
||||
indirect=["workspace"],
|
||||
)
|
||||
assert (workspace / "file").read_text() == "file"
|
||||
assert (
|
||||
workspace / "cache" / hash_value[:2] / hash_value[2:]
|
||||
).read_text() == "file"
|
||||
def test_add(self, tmp_dir, dvc, workspace, hash_name, hash_value):
|
||||
super().test_add(tmp_dir, dvc, workspace, hash_name, hash_value)
|
||||
|
||||
assert dvc.status() == {}
|
||||
@pytest.mark.parametrize(
|
||||
"workspace, hash_name, dir_hash_value",
|
||||
[
|
||||
(
|
||||
"local",
|
||||
"md5",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
),
|
||||
pytest.param(
|
||||
"ssh",
|
||||
"md5",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
(
|
||||
"s3",
|
||||
"etag",
|
||||
"ec602a6ba97b2dd07bd6d2cd89674a60.dir",
|
||||
),
|
||||
],
|
||||
indirect=["workspace"],
|
||||
)
|
||||
def test_add_dir(self, tmp_dir, dvc, workspace, hash_name, dir_hash_value):
|
||||
super().test_add_dir(
|
||||
tmp_dir, dvc, workspace, hash_name, dir_hash_value
|
||||
)
|
||||
|
||||
|
||||
def test_add_external_relpath(tmp_dir, dvc, local_cloud):
|
||||
@@ -364,44 +378,6 @@ def test_add_external_relpath(tmp_dir, dvc, local_cloud):
|
||||
assert dvc.status() == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"workspace, hash_name, hash_value",
|
||||
[
|
||||
(
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
"md5",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"md5",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("s3"),
|
||||
"etag",
|
||||
"ec602a6ba97b2dd07bd6d2cd89674a60.dir",
|
||||
),
|
||||
],
|
||||
indirect=["workspace"],
|
||||
)
|
||||
def test_add_external_dir(tmp_dir, dvc, workspace, hash_name, hash_value):
|
||||
workspace.gen({"dir": {"file": "file", "subdir": {"subfile": "subfile"}}})
|
||||
|
||||
dvc.add("remote://workspace/dir")
|
||||
assert (tmp_dir / "dir.dvc").read_text() == (
|
||||
"outs:\n"
|
||||
f"- {hash_name}: {hash_value}\n"
|
||||
" size: 11\n"
|
||||
" nfiles: 2\n"
|
||||
" path: remote://workspace/dir\n"
|
||||
)
|
||||
assert (workspace / "cache" / hash_value[:2] / hash_value[2:]).is_file()
|
||||
|
||||
|
||||
class TestAddLocalRemoteFile(TestDvc):
|
||||
def test(self):
|
||||
"""
|
||||
@@ -1159,19 +1135,17 @@ def test_add_to_cache_invalid_combinations(dvc, invalid_opt, kwargs):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
"local",
|
||||
"s3",
|
||||
pytest.param("gs", marks=pytest.mark.needs_internet),
|
||||
"hdfs",
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("gs"), marks=pytest.mark.needs_internet
|
||||
),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"ssh",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
pytest.lazy_fixture("http"),
|
||||
"http",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
|
||||
+26
-47
@@ -5,10 +5,11 @@ from funcy import first, get_in
|
||||
|
||||
from dvc import api
|
||||
from dvc.exceptions import FileMissingError, OutputNotFoundError
|
||||
from dvc.testing.test_api import TestAPI as _TestAPI
|
||||
from dvc.utils.fs import remove
|
||||
from tests.unit.fs.test_repo import make_subrepo
|
||||
|
||||
cloud_names = [
|
||||
clouds = [
|
||||
"s3",
|
||||
"gs",
|
||||
"azure",
|
||||
@@ -20,23 +21,19 @@ cloud_names = [
|
||||
"webdav",
|
||||
"webhdfs",
|
||||
]
|
||||
clouds = [pytest.lazy_fixture(cloud) for cloud in cloud_names]
|
||||
all_clouds = [pytest.lazy_fixture("local_cloud")] + clouds
|
||||
all_clouds = ["local"] + clouds
|
||||
|
||||
# `lazy_fixture` is confusing pylint, pylint: disable=unused-argument
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize("remote", clouds, indirect=True)
|
||||
def test_get_url(tmp_dir, dvc, remote):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
|
||||
expected_url = (remote / "ac/bd18db4cc2f85cedef654fccc4a4d8").url
|
||||
assert api.get_url("foo") == expected_url
|
||||
class TestAPI(_TestAPI):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize("cloud", clouds)
|
||||
@pytest.mark.parametrize("cloud", clouds, indirect=True)
|
||||
def test_get_url_external(tmp_dir, erepo_dir, cloud):
|
||||
erepo_dir.add_remote(config=cloud.config)
|
||||
with erepo_dir.chdir():
|
||||
@@ -58,36 +55,21 @@ def test_get_url_requires_dvc(tmp_dir, scm):
|
||||
api.get_url("foo", repo=f"file://{tmp_dir}")
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize("remote", all_clouds, indirect=True)
|
||||
def test_open(tmp_dir, dvc, remote):
|
||||
tmp_dir.dvc_gen("foo", "foo-text")
|
||||
dvc.push()
|
||||
|
||||
# Remove cache to force download
|
||||
remove(dvc.odb.local.cache_dir)
|
||||
|
||||
with api.open("foo") as fd:
|
||||
assert fd.read() == "foo-text"
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize(
|
||||
"cloud",
|
||||
[
|
||||
pytest.lazy_fixture(cloud)
|
||||
for cloud in [
|
||||
"s3",
|
||||
"gs",
|
||||
"azure",
|
||||
"gdrive",
|
||||
"oss",
|
||||
"http",
|
||||
"hdfs",
|
||||
"ssh",
|
||||
"webdav",
|
||||
]
|
||||
"s3",
|
||||
"gs",
|
||||
"azure",
|
||||
"gdrive",
|
||||
"oss",
|
||||
"http",
|
||||
"hdfs",
|
||||
"ssh",
|
||||
"webdav",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_open_external(tmp_dir, erepo_dir, cloud):
|
||||
erepo_dir.add_remote(config=cloud.config)
|
||||
@@ -129,18 +111,15 @@ def test_open_granular(tmp_dir, dvc, remote):
|
||||
@pytest.mark.parametrize(
|
||||
"remote",
|
||||
[
|
||||
pytest.lazy_fixture(cloud)
|
||||
for cloud in [
|
||||
"s3",
|
||||
"gs",
|
||||
"azure",
|
||||
"gdrive",
|
||||
"oss",
|
||||
"ssh",
|
||||
"http",
|
||||
"hdfs",
|
||||
"webdav",
|
||||
]
|
||||
"s3",
|
||||
"gs",
|
||||
"azure",
|
||||
"gdrive",
|
||||
"oss",
|
||||
"ssh",
|
||||
"http",
|
||||
"hdfs",
|
||||
"webdav",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@@ -195,7 +174,7 @@ def test_open_rev(tmp_dir, scm, dvc):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("as_external", [True, False])
|
||||
@pytest.mark.parametrize("remote", [pytest.lazy_fixture("ssh")], indirect=True)
|
||||
@pytest.mark.parametrize("remote", ["ssh"], indirect=True)
|
||||
@pytest.mark.parametrize(
|
||||
"files, to_read",
|
||||
[
|
||||
|
||||
+16
-105
@@ -9,120 +9,31 @@ import dvc as dvc_module
|
||||
from dvc.external_repo import clean_repos
|
||||
from dvc.main import main
|
||||
from dvc.stage.exceptions import StageNotFound
|
||||
from dvc.utils.fs import move, remove
|
||||
from dvc.testing.test_remote import TestRemote as _TestRemote
|
||||
from dvc.utils.fs import remove
|
||||
|
||||
all_clouds = [
|
||||
pytest.lazy_fixture(cloud)
|
||||
for cloud in [
|
||||
"s3",
|
||||
"gs",
|
||||
"azure",
|
||||
"ssh",
|
||||
"http",
|
||||
"hdfs",
|
||||
"webdav",
|
||||
"webhdfs",
|
||||
"oss",
|
||||
"gdrive",
|
||||
]
|
||||
"s3",
|
||||
"gs",
|
||||
"azure",
|
||||
"ssh",
|
||||
"http",
|
||||
"hdfs",
|
||||
"webdav",
|
||||
"webhdfs",
|
||||
"oss",
|
||||
"gdrive",
|
||||
]
|
||||
|
||||
# Clouds that implement the general methods that can be tested
|
||||
# for functional tests that require extensive apis (e.g traversing
|
||||
# via walk_files())
|
||||
full_clouds = [
|
||||
pytest.lazy_fixture(cloud)
|
||||
for cloud in ["s3", "gs", "azure", "ssh", "hdfs"]
|
||||
]
|
||||
full_clouds = ["s3", "gs", "azure", "ssh", "hdfs"]
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@pytest.mark.parametrize("remote", all_clouds, indirect=True)
|
||||
def test_cloud(tmp_dir, dvc, remote): # pylint:disable=unused-argument
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
out = stage.outs[0]
|
||||
cache = out.cache_path
|
||||
foo_hash = out.hash_info
|
||||
foo_hashes = out.get_used_objs().get(None, set())
|
||||
|
||||
(stage_dir,) = tmp_dir.dvc_gen(
|
||||
{
|
||||
"data_dir": {
|
||||
"data_sub_dir": {"data_sub": "data_sub"},
|
||||
"data": "data",
|
||||
"empty": "",
|
||||
}
|
||||
}
|
||||
)
|
||||
out_dir = stage_dir.outs[0]
|
||||
cache_dir = out_dir.cache_path
|
||||
dir_hash = out_dir.hash_info
|
||||
dir_hashes = {dir_hash} | {oid for _, _, oid in out_dir.obj}
|
||||
|
||||
def _check_status(status, **kwargs):
|
||||
for key in ("ok", "missing", "new", "deleted"):
|
||||
expected = kwargs.get(key, set())
|
||||
assert expected == set(getattr(status, key))
|
||||
|
||||
# Check status
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, new={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, new=dir_hashes)
|
||||
|
||||
# Move cache and check status
|
||||
# See issue https://github.com/iterative/dvc/issues/4383 for details
|
||||
backup_dir = dvc.odb.local.cache_dir + ".backup"
|
||||
move(dvc.odb.local.cache_dir, backup_dir)
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, missing={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, missing=dir_hashes)
|
||||
|
||||
# Restore original cache:
|
||||
remove(dvc.odb.local.cache_dir)
|
||||
move(backup_dir, dvc.odb.local.cache_dir)
|
||||
|
||||
# Push and check status
|
||||
dvc.cloud.push(foo_hashes)
|
||||
assert os.path.exists(cache)
|
||||
assert os.path.isfile(cache)
|
||||
|
||||
dvc.cloud.push(dir_hashes)
|
||||
assert os.path.isfile(cache_dir)
|
||||
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, ok={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, ok=dir_hashes)
|
||||
|
||||
# Remove and check status
|
||||
remove(dvc.odb.local.cache_dir)
|
||||
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, deleted={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, deleted=dir_hashes)
|
||||
|
||||
# Pull and check status
|
||||
dvc.cloud.pull(foo_hashes)
|
||||
assert os.path.exists(cache)
|
||||
assert os.path.isfile(cache)
|
||||
with open(cache, encoding="utf-8") as fd:
|
||||
assert fd.read() == "foo"
|
||||
|
||||
dvc.cloud.pull(dir_hashes)
|
||||
assert os.path.isfile(cache_dir)
|
||||
|
||||
status = dvc.cloud.status(foo_hashes)
|
||||
_check_status(status, ok={foo_hash})
|
||||
|
||||
status_dir = dvc.cloud.status(dir_hashes)
|
||||
_check_status(status_dir, ok=dir_hashes)
|
||||
class TestRemote(_TestRemote):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.needs_internet
|
||||
@@ -448,7 +359,7 @@ def test_pipeline_file_target_ops(tmp_dir, dvc, run_copy, local_remote):
|
||||
assert set(dvc.pull()["added"]) == set(outs)
|
||||
|
||||
# clean everything in remote and push
|
||||
from tests.dir_helpers import TmpDir
|
||||
from dvc.testing.tmp_dir import TmpDir
|
||||
|
||||
clean(TmpDir(path).iterdir())
|
||||
dvc.push(["dvc.yaml:copy-ipsum-baz"])
|
||||
|
||||
@@ -346,12 +346,12 @@ def test_gc_not_collect_pipeline_tracked_files(tmp_dir, dvc, run_copy):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.lazy_fixture("webhdfs"),
|
||||
"local",
|
||||
"s3",
|
||||
"hdfs",
|
||||
"webhdfs",
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"ssh",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
|
||||
@@ -9,9 +9,9 @@ from dvc.ignore import DvcIgnore, DvcIgnorePatterns
|
||||
from dvc.output import OutputIsIgnoredError
|
||||
from dvc.pathspec_math import PatternInfo, merge_patterns
|
||||
from dvc.repo import Repo
|
||||
from dvc.testing.tmp_dir import TmpDir
|
||||
from dvc.types import List
|
||||
from dvc.utils.fs import get_mtime_and_size
|
||||
from tests.dir_helpers import TmpDir
|
||||
|
||||
|
||||
def _to_pattern_info_list(str_list: List):
|
||||
|
||||
+110
-142
@@ -8,8 +8,8 @@ import pytest
|
||||
from dvc.dependency.base import DependencyDoesNotExistError
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.main import main
|
||||
from dvc.objects.db import ODBManager
|
||||
from dvc.stage import Stage
|
||||
from dvc.testing.test_workspace import TestImport as _TestImport
|
||||
from dvc.utils.fs import makedirs
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
@@ -118,102 +118,108 @@ def test_import_url_with_no_exec(tmp_dir, dvc, erepo_dir):
|
||||
assert not dst.exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
pytest.lazy_fixture("azure"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("gs"), marks=pytest.mark.needs_internet
|
||||
),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.lazy_fixture("webhdfs"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
class TestImport(_TestImport):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
"local",
|
||||
"s3",
|
||||
"azure",
|
||||
pytest.param("gs", marks=pytest.mark.needs_internet),
|
||||
"hdfs",
|
||||
"webhdfs",
|
||||
pytest.param(
|
||||
"ssh",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
),
|
||||
pytest.lazy_fixture("http"),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_import_url(tmp_dir, dvc, workspace):
|
||||
workspace.gen("file", "file")
|
||||
assert not (tmp_dir / "file").exists() # sanity check
|
||||
dvc.imp_url("remote://workspace/file")
|
||||
assert (tmp_dir / "file").read_text() == "file"
|
||||
"http",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_import(self, tmp_dir, dvc, workspace):
|
||||
super().test_import(tmp_dir, dvc, workspace)
|
||||
|
||||
assert dvc.status() == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"workspace, stage_md5, dir_md5",
|
||||
[
|
||||
(
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
"dc24e1271084ee317ac3c2656fb8812b",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("s3"),
|
||||
"2aa17f8daa26996b3f7a4cf8888ac9ac",
|
||||
"ec602a6ba97b2dd07bd6d2cd89674a60.dir",
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("azure"),
|
||||
None, # ETags for azure are not consistent with the actual
|
||||
None, # content of the object, so they will change every time
|
||||
# we create new objects. We'll skip stage content check for Azure.
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
"ec0943f83357f702033c98e70b853c8c",
|
||||
"e6dcd267966dc628d732874f94ef4280.dir",
|
||||
),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"dc24e1271084ee317ac3c2656fb8812b",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
@pytest.mark.parametrize(
|
||||
"workspace, stage_md5, dir_md5",
|
||||
[
|
||||
(
|
||||
"local",
|
||||
"dc24e1271084ee317ac3c2656fb8812b",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
),
|
||||
),
|
||||
],
|
||||
indirect=["workspace"],
|
||||
)
|
||||
def test_import_url_dir(tmp_dir, dvc, workspace, stage_md5, dir_md5):
|
||||
workspace.gen({"dir": {"file": "file", "subdir": {"subfile": "subfile"}}})
|
||||
(
|
||||
"s3",
|
||||
"2aa17f8daa26996b3f7a4cf8888ac9ac",
|
||||
"ec602a6ba97b2dd07bd6d2cd89674a60.dir",
|
||||
),
|
||||
(
|
||||
"azure",
|
||||
None, # ETags for azure are not consistent with the actual
|
||||
None, # content of the object, so they will change every time
|
||||
# we create new objects. We'll skip stage content
|
||||
# check for Azure.
|
||||
),
|
||||
(
|
||||
"hdfs",
|
||||
"ec0943f83357f702033c98e70b853c8c",
|
||||
"e6dcd267966dc628d732874f94ef4280.dir",
|
||||
),
|
||||
pytest.param(
|
||||
"ssh",
|
||||
"dc24e1271084ee317ac3c2656fb8812b",
|
||||
"b6dcab6ccd17ca0a8bf4a215a37d14cc.dir",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
],
|
||||
indirect=["workspace"],
|
||||
)
|
||||
def test_import_dir(self, tmp_dir, dvc, workspace, stage_md5, dir_md5):
|
||||
super().test_import_dir(tmp_dir, dvc, workspace, stage_md5, dir_md5)
|
||||
|
||||
# remove external cache to make sure that we don't need it to import dirs
|
||||
with dvc.config.edit() as conf:
|
||||
del conf["cache"]
|
||||
dvc.odb = ODBManager(dvc)
|
||||
|
||||
assert not (tmp_dir / "dir").exists() # sanity check
|
||||
dvc.imp_url("remote://workspace/dir")
|
||||
assert set(os.listdir(tmp_dir / "dir")) == {"file", "subdir"}
|
||||
assert (tmp_dir / "dir" / "file").read_text() == "file"
|
||||
assert list(os.listdir(tmp_dir / "dir" / "subdir")) == ["subfile"]
|
||||
assert (tmp_dir / "dir" / "subdir" / "subfile").read_text() == "subfile"
|
||||
|
||||
assert dvc.status() == {}
|
||||
|
||||
if stage_md5 is not None and dir_md5 is not None:
|
||||
assert (tmp_dir / "dir.dvc").read_text() == (
|
||||
f"md5: {stage_md5}\n"
|
||||
"frozen: true\n"
|
||||
"deps:\n"
|
||||
f"- md5: {dir_md5}\n"
|
||||
" size: 11\n"
|
||||
" nfiles: 2\n"
|
||||
" path: remote://workspace/dir\n"
|
||||
"outs:\n"
|
||||
"- md5: b6dcab6ccd17ca0a8bf4a215a37d14cc.dir\n"
|
||||
" size: 11\n"
|
||||
" nfiles: 2\n"
|
||||
" path: dir\n"
|
||||
@pytest.mark.parametrize(
|
||||
"workspace, is_object_storage",
|
||||
[
|
||||
("hdfs", False),
|
||||
pytest.param(
|
||||
"s3",
|
||||
True,
|
||||
marks=pytest.mark.xfail(
|
||||
reason="https://github.com/iterative/dvc/issues/5521",
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"gs",
|
||||
True,
|
||||
marks=pytest.mark.xfail(
|
||||
reason="https://github.com/iterative/dvc/issues/5521",
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"azure",
|
||||
True,
|
||||
marks=pytest.mark.xfail(
|
||||
reason="https://github.com/iterative/dvc/issues/5521",
|
||||
),
|
||||
),
|
||||
pytest.param(
|
||||
"ssh",
|
||||
False,
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
],
|
||||
indirect=["workspace"],
|
||||
)
|
||||
def test_import_empty_dir(
|
||||
self, tmp_dir, dvc, workspace, is_object_storage
|
||||
):
|
||||
super().test_import_empty_dir(
|
||||
tmp_dir, dvc, workspace, is_object_storage
|
||||
)
|
||||
|
||||
|
||||
@@ -257,19 +263,17 @@ def test_import_url_preserve_meta(tmp_dir, dvc):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
"local",
|
||||
"s3",
|
||||
pytest.param("gs", marks=pytest.mark.needs_internet),
|
||||
"hdfs",
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("gs"), marks=pytest.mark.needs_internet
|
||||
),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"ssh",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
pytest.lazy_fixture("http"),
|
||||
"http",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@@ -300,14 +304,12 @@ def test_import_url_to_remote_single_file(
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
"local",
|
||||
"s3",
|
||||
pytest.param("gs", marks=pytest.mark.needs_internet),
|
||||
"hdfs",
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("gs"), marks=pytest.mark.needs_internet
|
||||
),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"ssh",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
@@ -381,40 +383,6 @@ empty_xfail = pytest.mark.xfail(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.param(pytest.lazy_fixture("s3"), marks=empty_xfail),
|
||||
pytest.param(pytest.lazy_fixture("gs"), marks=empty_xfail),
|
||||
pytest.param(pytest.lazy_fixture("azure"), marks=empty_xfail),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_import_url_empty_directory(tmp_dir, dvc, workspace):
|
||||
# prefix based storage services (e.g s3) doesn't have the real concept
|
||||
# of directories. So instead we create an empty file that ends with a
|
||||
# trailing slash in order to actually support this operation
|
||||
if workspace.IS_OBJECT_STORAGE:
|
||||
contents = ""
|
||||
else:
|
||||
contents = {}
|
||||
|
||||
workspace.gen({"empty_dir/": contents})
|
||||
|
||||
dvc.imp_url("remote://workspace/empty_dir/")
|
||||
|
||||
empty_dir = tmp_dir / "empty_dir"
|
||||
assert empty_dir.is_dir()
|
||||
assert tuple(empty_dir.iterdir()) == ()
|
||||
|
||||
|
||||
def test_import_url_to_remote_status(tmp_dir, dvc, local_cloud, local_remote):
|
||||
local_cloud.gen("foo", "foo")
|
||||
|
||||
|
||||
@@ -114,9 +114,7 @@ def test_merge_conflict(tmp_dir, dvc, ancestor, our, their, error, caplog):
|
||||
assert error in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"workspace", [pytest.lazy_fixture("ssh")], indirect=True
|
||||
)
|
||||
@pytest.mark.parametrize("workspace", ["ssh"], indirect=True)
|
||||
def test_merge_different_output_types(tmp_dir, dvc, caplog, workspace):
|
||||
(tmp_dir / "ancestor").touch()
|
||||
|
||||
|
||||
@@ -354,13 +354,13 @@ def test_run_overwrite_preserves_meta_and_comment(tmp_dir, dvc, run_copy):
|
||||
"workspace, hash_name, foo_hash, bar_hash",
|
||||
[
|
||||
(
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
"local",
|
||||
"md5",
|
||||
"acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
"37b51d194a7513e45b56f6524f2d51f2",
|
||||
),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"ssh",
|
||||
"md5",
|
||||
"acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
"37b51d194a7513e45b56f6524f2d51f2",
|
||||
@@ -369,13 +369,13 @@ def test_run_overwrite_preserves_meta_and_comment(tmp_dir, dvc, run_copy):
|
||||
),
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("s3"),
|
||||
"s3",
|
||||
"etag",
|
||||
"acbd18db4cc2f85cedef654fccc4a4d8",
|
||||
"37b51d194a7513e45b56f6524f2d51f2",
|
||||
),
|
||||
(
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
"hdfs",
|
||||
"checksum",
|
||||
"0000020000000000000000003dba826b9be9c6a8e2f8310a770555c4",
|
||||
"00000200000000000000000075433c81259d3c38e364b348af52e84d",
|
||||
|
||||
+14
-20
@@ -159,15 +159,13 @@ def test_update_before_and_after_dvc_init(tmp_dir, dvc, git_dir):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
"local",
|
||||
"s3",
|
||||
pytest.param("gs", marks=pytest.mark.needs_internet),
|
||||
"hdfs",
|
||||
"webhdfs",
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("gs"), marks=pytest.mark.needs_internet
|
||||
),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
pytest.lazy_fixture("webhdfs"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("ssh"),
|
||||
"ssh",
|
||||
marks=pytest.mark.skipif(
|
||||
os.name == "nt", reason="disabled on windows"
|
||||
),
|
||||
@@ -326,12 +324,10 @@ def test_update_import_to_remote(tmp_dir, dvc, erepo_dir, local_remote):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("local_cloud"),
|
||||
pytest.lazy_fixture("s3"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("gs"), marks=pytest.mark.needs_internet
|
||||
),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
"local",
|
||||
"s3",
|
||||
pytest.param("gs", marks=pytest.mark.needs_internet),
|
||||
"hdfs",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@@ -349,11 +345,9 @@ def test_update_import_url_to_remote(tmp_dir, dvc, workspace, local_remote):
|
||||
@pytest.mark.parametrize(
|
||||
"workspace",
|
||||
[
|
||||
pytest.lazy_fixture("s3"),
|
||||
pytest.param(
|
||||
pytest.lazy_fixture("gs"), marks=pytest.mark.needs_internet
|
||||
),
|
||||
pytest.lazy_fixture("hdfs"),
|
||||
"s3",
|
||||
pytest.param("gs", marks=pytest.mark.needs_internet),
|
||||
"hdfs",
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@@ -378,7 +372,7 @@ def test_update_import_url_to_remote_directory(
|
||||
)
|
||||
|
||||
download_file_mock = mocker.spy(
|
||||
type(dvc.cloud.get_remote_odb("cache").fs), "download_file"
|
||||
type(dvc.cloud.get_remote_odb("workspace-cache").fs), "download_file"
|
||||
)
|
||||
stage = dvc.update(stage.path, to_remote=True)
|
||||
|
||||
|
||||
+23
-110
@@ -1,12 +1,9 @@
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
from .azure import Azure, azure, azure_server # noqa: F401
|
||||
from .azure import Azure, azure, azure_server, make_azure # noqa: F401
|
||||
from .gdrive import ( # noqa: F401; noqa: F401
|
||||
TEST_GDRIVE_REPO_BUCKET,
|
||||
GDrive,
|
||||
gdrive,
|
||||
make_gdrive,
|
||||
)
|
||||
from .git_server import git_server, git_ssh # noqa: F401
|
||||
from .gs import ( # noqa: F401; noqa: F401
|
||||
@@ -14,13 +11,22 @@ from .gs import ( # noqa: F401; noqa: F401
|
||||
TEST_GCP_CREDS_FILE,
|
||||
TEST_GCP_REPO_BUCKET,
|
||||
gs,
|
||||
make_gs,
|
||||
)
|
||||
from .hdfs import HDFS, hadoop, hdfs, hdfs_server, real_hdfs # noqa: F401
|
||||
from .http import HTTP, http, http_server # noqa: F401
|
||||
from .hdfs import ( # noqa: F401
|
||||
HDFS,
|
||||
hadoop,
|
||||
hdfs,
|
||||
hdfs_server,
|
||||
make_hdfs,
|
||||
real_hdfs,
|
||||
)
|
||||
from .http import HTTP, http, http_server, make_http # noqa: F401
|
||||
from .local import Local, local_cloud, local_remote, make_local # noqa: F401
|
||||
from .oss import ( # noqa: F401
|
||||
OSS,
|
||||
TEST_OSS_REPO_BUCKET,
|
||||
make_oss,
|
||||
oss,
|
||||
oss_server,
|
||||
real_oss,
|
||||
@@ -28,14 +34,21 @@ from .oss import ( # noqa: F401
|
||||
from .s3 import ( # noqa: F401
|
||||
S3,
|
||||
TEST_AWS_REPO_BUCKET,
|
||||
make_s3,
|
||||
real_s3,
|
||||
s3,
|
||||
s3_fake_creds_file,
|
||||
s3_server,
|
||||
)
|
||||
from .ssh import SSH, ssh, ssh_connection, ssh_server # noqa: F401; noqa: F401
|
||||
from .webdav import Webdav, webdav, webdav_server # noqa: F401
|
||||
from .webhdfs import WebHDFS, webhdfs # noqa: F401
|
||||
from .ssh import ( # noqa: F401; noqa: F401
|
||||
SSH,
|
||||
make_ssh,
|
||||
ssh,
|
||||
ssh_connection,
|
||||
ssh_server,
|
||||
)
|
||||
from .webdav import Webdav, make_webdav, webdav, webdav_server # noqa: F401
|
||||
from .webhdfs import WebHDFS, make_webhdfs, webhdfs # noqa: F401
|
||||
|
||||
TEST_REMOTE = "upstream"
|
||||
TEST_CONFIG = {
|
||||
@@ -43,103 +56,3 @@ TEST_CONFIG = {
|
||||
"core": {"remote": TEST_REMOTE},
|
||||
"remote": {TEST_REMOTE: {"url": ""}},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker():
|
||||
import os
|
||||
|
||||
# See https://travis-ci.community/t/docker-linux-containers-on-windows/301
|
||||
if os.environ.get("CI") and os.name == "nt":
|
||||
pytest.skip("disabled for Windows on Github Actions")
|
||||
|
||||
try:
|
||||
subprocess.check_output("docker ps", shell=True)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
pytest.skip("no docker installed")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_compose(docker):
|
||||
try:
|
||||
subprocess.check_output("docker-compose version", shell=True)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
pytest.skip("no docker-compose installed")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_compose_project_name():
|
||||
return "pytest-dvc-test"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def docker_services(
|
||||
docker_compose_file, docker_compose_project_name, tmp_path_factory
|
||||
):
|
||||
# overriding `docker_services` fixture from `pytest_docker` plugin to
|
||||
# only launch docker images once.
|
||||
|
||||
from filelock import FileLock
|
||||
from pytest_docker.plugin import DockerComposeExecutor, Services
|
||||
|
||||
executor = DockerComposeExecutor(
|
||||
docker_compose_file, docker_compose_project_name
|
||||
)
|
||||
|
||||
# making sure we don't accidentally launch docker-compose in parallel,
|
||||
# as it might result in network conflicts. Inspired by:
|
||||
# https://github.com/pytest-dev/pytest-xdist#making-session-scoped-fixtures-execute-only-once
|
||||
lockfile = tmp_path_factory.getbasetemp().parent / "docker-compose.lock"
|
||||
with FileLock(str(lockfile)): # pylint:disable=abstract-class-instantiated
|
||||
executor.execute("up --build -d")
|
||||
|
||||
return Services(executor)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_cloud(request):
|
||||
def _make_cloud(typ):
|
||||
return request.getfixturevalue(f"make_{typ}")()
|
||||
|
||||
return _make_cloud
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_remote(tmp_dir, dvc, make_cloud):
|
||||
def _make_remote(name, typ="local", **kwargs):
|
||||
cloud = make_cloud(typ)
|
||||
tmp_dir.add_remote(name=name, config=cloud.config, **kwargs)
|
||||
return cloud
|
||||
|
||||
return _make_remote
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def remote(tmp_dir, dvc, request):
|
||||
cloud = request.param
|
||||
assert cloud
|
||||
tmp_dir.add_remote(config=cloud.config)
|
||||
yield cloud
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def workspace(tmp_dir, dvc, request):
|
||||
from dvc.objects.db import ODBManager
|
||||
|
||||
cloud = request.param
|
||||
|
||||
assert cloud
|
||||
|
||||
tmp_dir.add_remote(name="workspace", config=cloud.config, default=False)
|
||||
tmp_dir.add_remote(
|
||||
name="cache", url="remote://workspace/cache", default=False
|
||||
)
|
||||
|
||||
scheme = getattr(cloud, "scheme", "local")
|
||||
if scheme != "http":
|
||||
with dvc.config.edit() as conf:
|
||||
conf["cache"][scheme] = "cache"
|
||||
|
||||
dvc.odb = ODBManager(dvc)
|
||||
|
||||
return cloud
|
||||
|
||||
+40
-14
@@ -6,8 +6,8 @@ import uuid
|
||||
import pytest
|
||||
from funcy import cached_property
|
||||
|
||||
from .base import Base
|
||||
from .path_info import CloudURLInfo
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import CloudURLInfo
|
||||
|
||||
TEST_AZURE_CONTAINER = "tests"
|
||||
TEST_AZURE_CONNECTION_STRING = (
|
||||
@@ -19,11 +19,15 @@ TEST_AZURE_CONNECTION_STRING = (
|
||||
)
|
||||
|
||||
|
||||
class Azure(Base, CloudURLInfo):
|
||||
class Azure(Cloud, CloudURLInfo):
|
||||
|
||||
IS_OBJECT_STORAGE = True
|
||||
CONNECTION_STRING = None
|
||||
|
||||
def __init__(self, url, **kwargs):
|
||||
super().__init__(url)
|
||||
self.opts = kwargs
|
||||
|
||||
@cached_property
|
||||
def service_client(self):
|
||||
# pylint: disable=no-name-in-module
|
||||
@@ -62,6 +66,19 @@ class Azure(Base, CloudURLInfo):
|
||||
path = self.path.lstrip("/")
|
||||
return f"{bucket}/{path}"
|
||||
|
||||
def is_file(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def is_dir(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def exists(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return {"url": self.url, **self.opts}
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def azure_server(test_config, docker_compose, docker_services):
|
||||
@@ -95,17 +112,26 @@ def azure_server(test_config, docker_compose, docker_services):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def azure(azure_server):
|
||||
from azure.core.exceptions import ResourceNotFoundError
|
||||
def make_azure(azure_server):
|
||||
def _make_azure():
|
||||
from azure.core.exceptions import ResourceNotFoundError
|
||||
|
||||
url = f"azure://{TEST_AZURE_CONTAINER}/{uuid.uuid4()}"
|
||||
ret = Azure(url)
|
||||
ret.config = {"url": url, "connection_string": azure_server}
|
||||
url = f"azure://{TEST_AZURE_CONTAINER}/{uuid.uuid4()}"
|
||||
ret = Azure(url, connection_string=azure_server)
|
||||
|
||||
container = ret.service_client.get_container_client(TEST_AZURE_CONTAINER)
|
||||
try: # verify that container exists
|
||||
container.get_container_properties()
|
||||
except ResourceNotFoundError:
|
||||
container.create_container()
|
||||
container = ret.service_client.get_container_client(
|
||||
TEST_AZURE_CONTAINER
|
||||
)
|
||||
try: # verify that container exists
|
||||
container.get_container_properties()
|
||||
except ResourceNotFoundError:
|
||||
container.create_container()
|
||||
|
||||
return ret
|
||||
return ret
|
||||
|
||||
return _make_azure
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def azure(make_azure):
|
||||
return make_azure()
|
||||
|
||||
+31
-14
@@ -9,11 +9,10 @@ import pytest
|
||||
from funcy import cached_property, retry
|
||||
|
||||
from dvc.fs.gdrive import GDriveFileSystem
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import CloudURLInfo
|
||||
from dvc.utils import tmp_fname
|
||||
|
||||
from .base import Base
|
||||
from .path_info import CloudURLInfo
|
||||
|
||||
TEST_GDRIVE_REPO_BUCKET = "root"
|
||||
|
||||
|
||||
@@ -62,12 +61,12 @@ def _gdrive_retry(func):
|
||||
)(func)
|
||||
|
||||
|
||||
class GDrive(Base, GDriveURLInfo):
|
||||
class GDrive(Cloud, GDriveURLInfo):
|
||||
@staticmethod
|
||||
def should_test():
|
||||
return bool(os.getenv(GDriveFileSystem.GDRIVE_CREDENTIALS_DATA))
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def config(self):
|
||||
tmp_path = tmp_fname()
|
||||
with open(tmp_path, "w", encoding="utf-8") as stream:
|
||||
@@ -110,6 +109,15 @@ class GDrive(Base, GDriveURLInfo):
|
||||
service_account=self.config["gdrive_use_service_account"],
|
||||
)
|
||||
|
||||
def is_file(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def is_dir(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def exists(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@_gdrive_retry
|
||||
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
||||
try:
|
||||
@@ -135,17 +143,26 @@ class GDrive(Base, GDriveURLInfo):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gdrive(test_config, make_tmp_dir):
|
||||
def make_gdrive(test_config, make_tmp_dir):
|
||||
test_config.requires("gdrive")
|
||||
|
||||
if not GDrive.should_test():
|
||||
pytest.skip("no gdrive")
|
||||
|
||||
# NOTE: temporary workaround
|
||||
tmp_dir = make_tmp_dir("gdrive", dvc=True)
|
||||
def _make_gdrive():
|
||||
# NOTE: temporary workaround
|
||||
tmp_dir = make_tmp_dir("gdrive", dvc=True)
|
||||
|
||||
ret = GDrive(GDrive.get_url())
|
||||
fs = GDriveFileSystem(
|
||||
gdrive_credentials_tmp_dir=tmp_dir.dvc.tmp_dir, **ret.config
|
||||
)
|
||||
fs.fs._gdrive_create_dir("root", fs.url)
|
||||
yield ret
|
||||
ret = GDrive(GDrive.get_url())
|
||||
fs = GDriveFileSystem(
|
||||
gdrive_credentials_tmp_dir=tmp_dir.dvc.tmp_dir, **ret.config
|
||||
)
|
||||
fs.fs._gdrive_create_dir("root", fs.url)
|
||||
yield ret
|
||||
|
||||
return _make_gdrive
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gdrive(make_gdrive):
|
||||
return make_gdrive()
|
||||
|
||||
+10
-7
@@ -3,13 +3,11 @@ import os
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
from funcy import cached_property
|
||||
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import CloudURLInfo
|
||||
from dvc.utils import env2bool
|
||||
|
||||
from .base import Base
|
||||
from .path_info import CloudURLInfo
|
||||
|
||||
TEST_GCP_REPO_BUCKET = os.environ.get("DVC_TEST_GCP_REPO_BUCKET", "dvc-test")
|
||||
|
||||
TEST_GCP_CREDS_FILE = os.path.abspath(
|
||||
@@ -22,7 +20,7 @@ TEST_GCP_CREDS_FILE = os.path.abspath(
|
||||
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = TEST_GCP_CREDS_FILE
|
||||
|
||||
|
||||
class GCP(Base, CloudURLInfo):
|
||||
class GCP(Cloud, CloudURLInfo):
|
||||
|
||||
IS_OBJECT_STORAGE = True
|
||||
|
||||
@@ -37,7 +35,7 @@ class GCP(Base, CloudURLInfo):
|
||||
|
||||
return True
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def config(self):
|
||||
return {"url": self.url, "credentialpath": TEST_GCP_CREDS_FILE}
|
||||
|
||||
@@ -95,8 +93,13 @@ class GCP(Base, CloudURLInfo):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gs(test_config):
|
||||
def make_gs(test_config):
|
||||
test_config.requires("gs")
|
||||
if not GCP.should_test():
|
||||
pytest.skip("no gs")
|
||||
yield GCP(GCP.get_url())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gs(make_gs):
|
||||
return make_gs()
|
||||
|
||||
+18
-6
@@ -10,13 +10,13 @@ from urllib.parse import urlparse
|
||||
|
||||
import pytest
|
||||
|
||||
from .base import Base
|
||||
from .path_info import URLInfo
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import URLInfo
|
||||
|
||||
_hdfs_root = TemporaryDirectory()
|
||||
|
||||
|
||||
class HDFS(Base, URLInfo): # pylint: disable=abstract-method
|
||||
class HDFS(Cloud, URLInfo): # pylint: disable=abstract-method
|
||||
@contextmanager
|
||||
def _hdfs(self):
|
||||
import pyarrow.fs
|
||||
@@ -24,6 +24,10 @@ class HDFS(Base, URLInfo): # pylint: disable=abstract-method
|
||||
conn = pyarrow.fs.HadoopFileSystem(self.host, self.port)
|
||||
yield conn
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return {"url": self.url}
|
||||
|
||||
def is_file(self):
|
||||
with self._hdfs() as _hdfs:
|
||||
import pyarrow.fs
|
||||
@@ -280,7 +284,7 @@ class FakeHadoopFileSystem:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hdfs(test_config, mocker):
|
||||
def make_hdfs(test_config, mocker):
|
||||
# Windows might not have Visual C++ Redistributable for Visual Studio
|
||||
# 2015 installed, which will result in the following error:
|
||||
# "The pyarrow installation is not built with support for
|
||||
@@ -295,5 +299,13 @@ def hdfs(test_config, mocker):
|
||||
|
||||
mocker.patch("dvc.fs.hdfs.HDFSFileSystem._checksum", hadoop_fs_checksum)
|
||||
|
||||
url = f"hdfs://example.com:12345/{uuid.uuid4()}"
|
||||
yield HDFS(url)
|
||||
def _make_hdfs():
|
||||
url = f"hdfs://example.com:12345/{uuid.uuid4()}"
|
||||
return HDFS(url)
|
||||
|
||||
return _make_hdfs
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def hdfs(make_hdfs):
|
||||
return make_hdfs()
|
||||
|
||||
+29
-5
@@ -5,11 +5,11 @@ import uuid
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from .base import Base
|
||||
from .path_info import HTTPURLInfo
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import HTTPURLInfo
|
||||
|
||||
|
||||
class HTTP(Base, HTTPURLInfo):
|
||||
class HTTP(Cloud, HTTPURLInfo):
|
||||
@staticmethod
|
||||
def get_url(port): # pylint: disable=arguments-differ
|
||||
dname = str(uuid.uuid4())
|
||||
@@ -24,10 +24,26 @@ class HTTP(Base, HTTPURLInfo):
|
||||
response = requests.post(self.url, data=contents)
|
||||
assert response.status_code == 200
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return {"url": self.url}
|
||||
|
||||
@property
|
||||
def fs_path(self):
|
||||
return self.url
|
||||
|
||||
def exists(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def is_dir(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def is_file(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def read_bytes(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def http_server(test_config, tmp_path_factory):
|
||||
@@ -40,5 +56,13 @@ def http_server(test_config, tmp_path_factory):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def http(http_server):
|
||||
yield HTTP(HTTP.get_url(http_server.server_port))
|
||||
def make_http(http_server):
|
||||
def _make_http():
|
||||
return HTTP(HTTP.get_url(http_server.server_port))
|
||||
|
||||
return _make_http
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def http(make_http):
|
||||
return make_http()
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# pylint: disable=cyclic-import,disable=abstract-method
|
||||
import pytest
|
||||
|
||||
from dvc.testing.cloud import Cloud
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Local(Base):
|
||||
class Local(Cloud):
|
||||
@staticmethod
|
||||
def get_storagepath():
|
||||
return TestDvc.mkdtemp()
|
||||
@@ -19,10 +18,7 @@ class Local(Base):
|
||||
@pytest.fixture
|
||||
def make_local(make_tmp_dir):
|
||||
def _make_local():
|
||||
ret = make_tmp_dir("local-cloud")
|
||||
ret.url = str(ret)
|
||||
ret.config = {"url": ret.url}
|
||||
return ret
|
||||
return make_tmp_dir("local-cloud")
|
||||
|
||||
return _make_local
|
||||
|
||||
|
||||
+57
-26
@@ -4,18 +4,17 @@ import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import CloudURLInfo
|
||||
from dvc.utils import env2bool
|
||||
|
||||
from .base import Base
|
||||
from .path_info import CloudURLInfo
|
||||
|
||||
TEST_OSS_REPO_BUCKET = "dvc-test-github"
|
||||
EMULATOR_OSS_ENDPOINT = "127.0.0.1:{port}"
|
||||
EMULATOR_OSS_ACCESS_KEY_ID = "AccessKeyID"
|
||||
EMULATOR_OSS_ACCESS_KEY_SECRET = "AccessKeySecret"
|
||||
|
||||
|
||||
class OSS(Base, CloudURLInfo):
|
||||
class OSS(Cloud, CloudURLInfo):
|
||||
|
||||
IS_OBJECT_STORAGE = True
|
||||
|
||||
@@ -36,6 +35,33 @@ class OSS(Base, CloudURLInfo):
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return {
|
||||
"url": self.url,
|
||||
"oss_key_id": os.environ.get("OSS_ACCESS_KEY_ID"),
|
||||
"oss_key_secret": os.environ.get("OSS_ACCESS_KEY_SECRET"),
|
||||
"oss_endpoint": os.environ.get("OSS_ENDPOINT"),
|
||||
}
|
||||
|
||||
def is_file(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def is_dir(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def exists(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
|
||||
raise NotImplementedError
|
||||
|
||||
def write_bytes(self, contents):
|
||||
raise NotImplementedError
|
||||
|
||||
def read_bytes(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def oss_server(test_config, docker_compose, docker_services):
|
||||
@@ -63,24 +89,36 @@ def oss_server(test_config, docker_compose, docker_services):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def oss(real_oss):
|
||||
import oss2
|
||||
def make_oss(real_oss):
|
||||
def _make_oss():
|
||||
import oss2
|
||||
|
||||
ret = real_oss
|
||||
ret = real_oss
|
||||
|
||||
auth = oss2.Auth(ret.config["oss_key_id"], ret.config["oss_key_secret"])
|
||||
bucket = oss2.Bucket(
|
||||
auth, ret.config["oss_endpoint"], TEST_OSS_REPO_BUCKET
|
||||
)
|
||||
try:
|
||||
bucket.get_bucket_info()
|
||||
except oss2.exceptions.NoSuchBucket:
|
||||
bucket.create_bucket(
|
||||
oss2.BUCKET_ACL_PUBLIC_READ,
|
||||
oss2.models.BucketCreateConfig(oss2.BUCKET_STORAGE_CLASS_STANDARD),
|
||||
auth = oss2.Auth(
|
||||
ret.config["oss_key_id"], ret.config["oss_key_secret"]
|
||||
)
|
||||
bucket = oss2.Bucket(
|
||||
auth, ret.config["oss_endpoint"], TEST_OSS_REPO_BUCKET
|
||||
)
|
||||
try:
|
||||
bucket.get_bucket_info()
|
||||
except oss2.exceptions.NoSuchBucket:
|
||||
bucket.create_bucket(
|
||||
oss2.BUCKET_ACL_PUBLIC_READ,
|
||||
oss2.models.BucketCreateConfig(
|
||||
oss2.BUCKET_STORAGE_CLASS_STANDARD
|
||||
),
|
||||
)
|
||||
|
||||
return ret
|
||||
return ret
|
||||
|
||||
return _make_oss
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def oss(make_oss):
|
||||
return make_oss()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -90,11 +128,4 @@ def real_oss(test_config):
|
||||
pytest.skip("no real OSS")
|
||||
|
||||
url = OSS.get_url()
|
||||
ret = OSS(url)
|
||||
ret.config = {
|
||||
"url": url,
|
||||
"oss_key_id": os.environ.get("OSS_ACCESS_KEY_ID"),
|
||||
"oss_key_secret": os.environ.get("OSS_ACCESS_KEY_SECRET"),
|
||||
"oss_endpoint": os.environ.get("OSS_ENDPOINT"),
|
||||
}
|
||||
return ret
|
||||
return OSS(url)
|
||||
|
||||
+17
-9
@@ -5,21 +5,20 @@ import uuid
|
||||
import pytest
|
||||
from funcy import cached_property
|
||||
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import CloudURLInfo
|
||||
from dvc.utils import env2bool
|
||||
|
||||
from .base import Base
|
||||
from .path_info import CloudURLInfo
|
||||
|
||||
TEST_AWS_REPO_BUCKET = os.environ.get("DVC_TEST_AWS_REPO_BUCKET", "dvc-temp")
|
||||
TEST_AWS_ENDPOINT_URL = "http://127.0.0.1:{port}/"
|
||||
|
||||
|
||||
class S3(Base, CloudURLInfo):
|
||||
class S3(Cloud, CloudURLInfo):
|
||||
|
||||
IS_OBJECT_STORAGE = True
|
||||
TEST_AWS_ENDPOINT_URL = None
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def config(self):
|
||||
return {"url": self.url, "endpointurl": self.TEST_AWS_ENDPOINT_URL}
|
||||
|
||||
@@ -151,11 +150,20 @@ def s3_server(test_config, docker_compose, docker_services):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def s3(test_config, s3_server, s3_fake_creds_file):
|
||||
def make_s3(test_config, s3_server, s3_fake_creds_file):
|
||||
test_config.requires("s3")
|
||||
workspace = S3(S3.get_url())
|
||||
workspace._s3.create_bucket(Bucket=TEST_AWS_REPO_BUCKET)
|
||||
yield workspace
|
||||
|
||||
def _make_s3():
|
||||
cloud = S3(S3.get_url())
|
||||
cloud._s3.create_bucket(Bucket=TEST_AWS_REPO_BUCKET)
|
||||
return cloud
|
||||
|
||||
return _make_s3
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def s3(make_s3):
|
||||
return make_s3()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
+19
-11
@@ -5,8 +5,8 @@ import uuid
|
||||
import pytest
|
||||
from funcy import cached_property
|
||||
|
||||
from .base import Base
|
||||
from .path_info import URLInfo
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import URLInfo
|
||||
|
||||
TEST_SSH_USER = "user"
|
||||
TEST_SSH_KEY_PATH = os.path.join(
|
||||
@@ -14,12 +14,12 @@ TEST_SSH_KEY_PATH = os.path.join(
|
||||
)
|
||||
|
||||
|
||||
class SSH(Base, URLInfo):
|
||||
class SSH(Cloud, URLInfo):
|
||||
@staticmethod
|
||||
def get_url(host, port): # pylint: disable=arguments-differ
|
||||
return f"ssh://{host}:{port}/tmp/data/{uuid.uuid4()}"
|
||||
|
||||
@cached_property
|
||||
@property
|
||||
def config(self):
|
||||
return {
|
||||
"url": self.url,
|
||||
@@ -116,12 +116,20 @@ def ssh_connection(ssh_server):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ssh(ssh_server, monkeypatch):
|
||||
from dvc.fs.ssh import SSHFileSystem
|
||||
def make_ssh(ssh_server, monkeypatch):
|
||||
def _make_ssh():
|
||||
from dvc.fs.ssh import SSHFileSystem
|
||||
|
||||
# NOTE: see http://github.com/iterative/dvc/pull/3501
|
||||
monkeypatch.setattr(SSHFileSystem, "CAN_TRAVERSE", False)
|
||||
# NOTE: see http://github.com/iterative/dvc/pull/3501
|
||||
monkeypatch.setattr(SSHFileSystem, "CAN_TRAVERSE", False)
|
||||
|
||||
url = SSH(SSH.get_url(**ssh_server))
|
||||
url.mkdir(exist_ok=True, parents=True)
|
||||
return url
|
||||
url = SSH(SSH.get_url(**ssh_server))
|
||||
url.mkdir(exist_ok=True, parents=True)
|
||||
return url
|
||||
|
||||
return _make_ssh
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ssh(make_ssh):
|
||||
return make_ssh()
|
||||
|
||||
+32
-10
@@ -6,19 +6,24 @@ import pytest
|
||||
from funcy import cached_property, first
|
||||
from wsgidav.wsgidav_app import WsgiDAVApp
|
||||
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import WebDAVURLInfo
|
||||
from tests.utils.httpd import run_server_on_thread
|
||||
|
||||
from .base import Base
|
||||
from .path_info import WebDAVURLInfo
|
||||
|
||||
AUTH = {"user1": {"password": "password1"}}
|
||||
|
||||
|
||||
class Webdav(Base, WebDAVURLInfo):
|
||||
class Webdav(Cloud, WebDAVURLInfo):
|
||||
@staticmethod
|
||||
def get_url(port): # pylint: disable=arguments-differ
|
||||
return f"webdav://localhost:{port}"
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
user, secrets = first(AUTH.items())
|
||||
url = f"webdav://{self.netloc}{self._spath}"
|
||||
return {"url": url, "user": user, **secrets}
|
||||
|
||||
@cached_property
|
||||
def client(self):
|
||||
from webdav4.client import Client
|
||||
@@ -45,6 +50,18 @@ class Webdav(Base, WebDAVURLInfo):
|
||||
def fs_path(self):
|
||||
return self.path.lstrip("/")
|
||||
|
||||
def exists(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def is_dir(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def is_file(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def read_bytes(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webdav_server(test_config, tmp_path_factory):
|
||||
@@ -68,9 +85,14 @@ def webdav_server(test_config, tmp_path_factory):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webdav(webdav_server):
|
||||
url = Webdav.get_url(webdav_server.server_port)
|
||||
ret = Webdav(url)
|
||||
user, secrets = first(AUTH.items())
|
||||
ret.config = {"url": url, "user": user, **secrets}
|
||||
yield ret
|
||||
def make_webdav(webdav_server):
|
||||
def _make_webdav():
|
||||
url = Webdav.get_url(webdav_server.server_port)
|
||||
return Webdav(url)
|
||||
|
||||
return _make_webdav
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webdav(make_webdav):
|
||||
return make_webdav()
|
||||
|
||||
@@ -4,11 +4,11 @@ from contextlib import contextmanager
|
||||
|
||||
import pytest
|
||||
|
||||
from .base import Base
|
||||
from .path_info import CloudURLInfo
|
||||
from dvc.testing.cloud import Cloud
|
||||
from dvc.testing.path_info import CloudURLInfo
|
||||
|
||||
|
||||
class WebHDFS(Base, CloudURLInfo): # pylint: disable=abstract-method
|
||||
class WebHDFS(Cloud, CloudURLInfo): # pylint: disable=abstract-method
|
||||
@contextmanager
|
||||
def _webhdfs(self):
|
||||
from hdfs import InsecureClient
|
||||
@@ -54,13 +54,25 @@ class WebHDFS(Base, CloudURLInfo): # pylint: disable=abstract-method
|
||||
assert errors is None
|
||||
return self.read_bytes().decode(encoding)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return {"url": self.url}
|
||||
|
||||
@property
|
||||
def fs_path(self):
|
||||
return "/" + self.path.lstrip("/")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webhdfs(hdfs_server):
|
||||
port = hdfs_server["webhdfs"]
|
||||
url = f"webhdfs://127.0.0.1:{port}/{uuid.uuid4()}"
|
||||
yield WebHDFS(url)
|
||||
def make_webhdfs(hdfs_server):
|
||||
def _make_webhdfs():
|
||||
port = hdfs_server["webhdfs"]
|
||||
url = f"webhdfs://127.0.0.1:{port}/{uuid.uuid4()}"
|
||||
yield WebHDFS(url)
|
||||
|
||||
return _make_webhdfs
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def webhdfs(make_webhdfs):
|
||||
return make_webhdfs()
|
||||
|
||||
Reference in New Issue
Block a user