From 1b90683e02def9738b6b8607b2d24d7f14150f6e Mon Sep 17 00:00:00 2001 From: skshetry Date: Thu, 15 Sep 2022 18:42:50 +0545 Subject: [PATCH] dvcfs: add fsspec-compliance tests (#8296) dvcfs: add tests --- dvc/fs/dvc.py | 6 +++ dvc/testing/test_api.py | 103 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/dvc/fs/dvc.py b/dvc/fs/dvc.py index 5b17e5b33..f2459f0d4 100644 --- a/dvc/fs/dvc.py +++ b/dvc/fs/dvc.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple, Type, Union from fsspec.spec import AbstractFileSystem from funcy import cached_property, wrap_prop, wrap_with +from dvc.utils.fs import makedirs from dvc_objects.fs.base import FileSystem from dvc_objects.fs.callbacks import DEFAULT_CALLBACK from dvc_objects.fs.path import Path @@ -296,6 +297,11 @@ class _DvcFileSystem(AbstractFileSystem): # pylint:disable=abstract-method key = self._get_key_from_relative(rpath) fs_path = self._from_key(key) fs = self.repo.fs + + if self.isdir(rpath): + makedirs(lpath, exist_ok=True) + return None + try: fs.get_file(fs_path, lpath, callback=callback, **kwargs) return diff --git a/dvc/testing/test_api.py b/dvc/testing/test_api.py index f0c21fee4..f482d26e2 100644 --- a/dvc/testing/test_api.py +++ b/dvc/testing/test_api.py @@ -1,4 +1,7 @@ +import pytest + from dvc import api +from dvc.api import DvcFileSystem from dvc.utils.fs import remove @@ -18,3 +21,103 @@ class TestAPI: with api.open("foo") as fd: assert fd.read() == "foo-text" + + @pytest.mark.parametrize( + "clear_cache", [True, False], ids=["cache", "no_cache"] + ) + @pytest.mark.parametrize( + "fs_kwargs", + [ + {}, + {"url": "{path}"}, + {"url": "{path}", "rev": "master"}, + {"url": "file://{posixpath}"}, + {"url": "file://{posixpath}", "rev": "master"}, + ], + ids=["current", "local", "local_rev", "git", "git_rev"], + ) + def test_filesystem( + self, + M, + tmp_dir, + make_tmp_dir, + scm, + dvc, + remote, + fs_kwargs, + clear_cache, + ): + tmp_dir.scm_gen({"scripts": {"script1": "script1"}}, commit="scripts") + tmp_dir.dvc_gen({"data": {"foo": "foo", "bar": "bar"}}, commit="data") + dvc.push() + + if clear_cache: + remove(dvc.odb.repo.path) + + if url := fs_kwargs.get("url"): + fs_kwargs["url"] = url.format( + path=tmp_dir, posixpath=tmp_dir.as_posix() + ) + + fs = DvcFileSystem(**fs_kwargs) + + assert fs.ls("/", detail=False) == M.unordered( + "/.gitignore", "/scripts", "/data" + ) + assert fs.ls("scripts", detail=False) == ["scripts/script1"] + assert fs.ls("data", detail=False) == M.unordered( + "data/foo", "data/bar" + ) + + data_info = M.dict( + name="/data", + type="directory", + dvc_info=M.dict(isdvc=True, isout=True), + ) + scripts_info = M.dict(name="/scripts", type="directory", isexec=False) + + assert sorted(fs.ls("/"), key=lambda i: i["name"]) == [ + M.dict(name="/.gitignore", type="file", isexec=False), + data_info, + scripts_info, + ] + + with pytest.raises(FileNotFoundError): + fs.info("/not-existing-path") + + assert fs.info("/") == M.dict(name="/", isexec=False, type="directory") + assert fs.info("/data") == data_info + assert fs.info("/scripts") == scripts_info + assert fs.info("/data/foo") == M.dict(name="/data/foo", type="file") + assert fs.info("/scripts/script1") == M.dict( + name="/scripts/script1", type="file" + ) + + assert not fs.isdvc("/") + assert fs.isdvc("/data") + assert fs.isdvc("/data/foo") + assert not fs.isdvc("/scripts") + assert not fs.isdvc("/scripts/script1") + + with pytest.raises((IsADirectoryError, PermissionError)): + fs.open("data") + with pytest.raises((IsADirectoryError, PermissionError)): + fs.open("scripts") + with fs.open("/data/foo") as fobj: + assert fobj.read() == b"foo" + with fs.open("/scripts/script1") as fobj: + assert fobj.read() == b"script1" + + tmp = make_tmp_dir("temp-download") + fs.get_file("data/foo", tmp / "foo") + assert (tmp / "foo").read_text() == "foo" + + fs.get_file("scripts/script1", tmp / "script1") + assert (tmp / "script1").read_text() == "script1" + + fs.get("/", (tmp / "all").fs_path, recursive=True) + assert (tmp / "all").read_text() == { + ".gitignore": "/data\n", + "data": {"bar": "bar", "foo": "foo"}, + "scripts": {"script1": "script1"}, + }