migrate test_{add,analytics,init,unprotect,local,process}.py to pytest

This commit is contained in:
Saugat Pachhai (सौगात)
2022-11-03 23:07:28 +05:45
committed by skshetry
parent 08a643e005
commit 1cad8ea7fd
6 changed files with 185 additions and 249 deletions
+145 -194
View File
@@ -4,8 +4,7 @@ import os
import shutil import shutil
import stat import stat
import textwrap import textwrap
import time from unittest.mock import call
from unittest.mock import call, patch
import colorama import colorama
import pytest import pytest
@@ -35,12 +34,11 @@ from dvc.stage.exceptions import (
StagePathNotFoundError, StagePathNotFoundError,
) )
from dvc.testing.workspace_tests import TestAdd from dvc.testing.workspace_tests import TestAdd
from dvc.utils import LARGE_DIR_SIZE, relpath from dvc.utils import LARGE_DIR_SIZE
from dvc.utils.fs import path_isin from dvc.utils.fs import path_isin
from dvc.utils.serialize import YAMLFileCorruptedError, load_yaml from dvc.utils.serialize import YAMLFileCorruptedError
from dvc_data.hashfile.hash import file_md5 from dvc_data.hashfile.hash import file_md5
from dvc_data.hashfile.hash_info import HashInfo from dvc_data.hashfile.hash_info import HashInfo
from tests.basic_env import TestDvc
from tests.utils import get_gitignore_content from tests.utils import get_gitignore_content
@@ -120,49 +118,38 @@ def test_add_directory(tmp_dir, dvc):
assert "\\" not in part assert "\\" not in part
class TestAddDirectoryRecursive(TestDvc): def test_add_directory_recursive(tmp_dir, dvc):
def test(self): tmp_dir.gen("data", {"file1": "file1", "sub": {"file2": "file2"}})
stages = self.dvc.add(self.DATA_DIR, recursive=True) stages = dvc.add("data", recursive=True)
self.assertEqual(len(stages), 2) assert len(stages) == 2
class TestAddCmdDirectoryRecursive(TestDvc): def test_add_cmd_directory_recursive(tmp_dir, dvc):
def test(self): tmp_dir.gen("data", {"file1": "file1", "sub": {"file2": "file2"}})
ret = main(["add", "--recursive", self.DATA_DIR]) assert main(["add", "--recursive", "data"]) == 0
self.assertEqual(ret, 0)
def test_warn_about_large_directories(self):
warning = (
"You are adding a large directory 'large-dir' recursively."
"\nConsider tracking it as a whole instead with "
"`{cyan}dvc add large-dir{nc}`"
).format(
cyan=colorama.Fore.CYAN,
nc=colorama.Style.RESET_ALL,
)
os.mkdir("large-dir")
# Create a lot of files
for iteration in range(LARGE_DIR_SIZE + 1):
path = os.path.join("large-dir", str(iteration))
with open(path, "w", encoding="utf-8") as fobj:
fobj.write(path)
assert main(["add", "--recursive", "large-dir"]) == 0
assert warning in self._capsys.readouterr()[1]
class TestAddDirectoryWithForwardSlash(TestDvc): def test_warn_about_large_directories_recursive_add(tmp_dir, dvc, capsys):
def test(self): warning = (
dname = "directory/" "You are adding a large directory 'large-dir' recursively."
os.mkdir(dname) "\nConsider tracking it as a whole instead with "
self.create(os.path.join(dname, "file"), "file") "`{cyan}dvc add large-dir{nc}`"
stages = self.dvc.add(dname) ).format(
self.assertEqual(len(stages), 1) cyan=colorama.Fore.CYAN,
stage = stages[0] nc=colorama.Style.RESET_ALL,
self.assertTrue(stage is not None) )
self.assertEqual(os.path.abspath("directory.dvc"), stage.path)
tmp_dir.gen(
"large-dir", {f"{i}": f"{i}" for i in range(LARGE_DIR_SIZE + 1)}
)
assert main(["add", "--recursive", "large-dir"]) == 0
assert warning in capsys.readouterr()[1]
def test_add_directory_with_forward_slash(tmp_dir, dvc):
tmp_dir.gen("directory", {"file": "file"})
(stage,) = dvc.add("directory/")
assert stage.relpath == "directory.dvc"
def test_add_tracked_file(tmp_dir, scm, dvc): def test_add_tracked_file(tmp_dir, scm, dvc):
@@ -178,33 +165,23 @@ def test_add_tracked_file(tmp_dir, scm, dvc):
dvc.add(path) dvc.add(path)
class TestAddDirWithExistingCache(TestDvc): def test_add_dir_with_existing_cache(tmp_dir, dvc):
def test(self): tmp_dir.gen({"foo": "foo", "dir": {"file": "foo"}})
dname = "a"
fname = os.path.join(dname, "b")
os.mkdir(dname)
shutil.copyfile(self.FOO, fname)
stages = self.dvc.add(self.FOO) (stage,) = dvc.add("foo")
self.assertEqual(len(stages), 1) assert stage is not None
self.assertTrue(stages[0] is not None) (stage,) = dvc.add("dir")
stages = self.dvc.add(dname) assert stage is not None
self.assertEqual(len(stages), 1)
self.assertTrue(stages[0] is not None)
class TestAddModifiedDir(TestDvc): def test_add_modified_dir(tmp_dir, dvc):
def test(self): tmp_dir.gen("data", {"foo": "foo", "sub": {"bar": "bar"}})
stages = self.dvc.add(self.DATA_DIR) (stage,) = dvc.add("data")
self.assertEqual(len(stages), 1) assert stage is not None
self.assertTrue(stages[0] is not None)
os.unlink(self.DATA)
time.sleep(2) (tmp_dir / "data" / "foo").unlink()
(stage,) = dvc.add("data")
stages = self.dvc.add(self.DATA_DIR) assert stage is not None
self.assertEqual(len(stages), 1)
self.assertTrue(stages[0] is not None)
def test_add_file_in_dir(tmp_dir, dvc): def test_add_file_in_dir(tmp_dir, dvc):
@@ -326,57 +303,47 @@ def test_add_external_relpath(tmp_dir, dvc, local_cloud):
assert dvc.status() == {} assert dvc.status() == {}
class TestAddLocalRemoteFile(TestDvc): def test_add_local_remote_file(tmp_dir, dvc):
def test(self): """
""" Making sure that 'remote' syntax is handled properly for local outs.
Making sure that 'remote' syntax is handled properly for local outs. """
""" tmp_dir.gen({"foo": "foo", "bar": "bar"})
cwd = os.getcwd() tmp_dir.add_remote(url=tmp_dir.fs_path, name="myremote")
remote = "myremote"
ret = main(["remote", "add", remote, cwd]) assert main(["add", "remote://myremote/foo"]) == 0
self.assertEqual(ret, 0) d = (tmp_dir / "foo.dvc").load_yaml()
assert d["outs"][0]["path"] == "remote://myremote/foo"
self.dvc.config.load() assert main(["add", (tmp_dir / "bar").fs_path]) == 0
d = (tmp_dir / "bar.dvc").load_yaml()
foo = f"remote://{remote}/{self.FOO}" assert d["outs"][0]["path"] == "bar"
ret = main(["add", foo])
self.assertEqual(ret, 0)
d = load_yaml("foo.dvc")
self.assertEqual(d["outs"][0]["path"], foo)
bar = os.path.join(cwd, self.BAR)
ret = main(["add", bar])
self.assertEqual(ret, 0)
d = load_yaml("bar.dvc")
self.assertEqual(d["outs"][0]["path"], self.BAR)
class TestCmdAdd(TestDvc): def test_cmd_add(tmp_dir, dvc):
def test(self): tmp_dir.gen("foo", "foo")
ret = main(["add", self.FOO]) ret = main(["add", "foo"])
self.assertEqual(ret, 0) assert ret == 0
ret = main(["add", "non-existing-file"]) ret = main(["add", "non-existing-file"])
self.assertNotEqual(ret, 0) assert ret != 0
class TestDoubleAddUnchanged(TestDvc): def test_double_add_unchanged_file(tmp_dir, dvc):
def test_file(self): tmp_dir.gen("foo", "foo")
ret = main(["add", self.FOO]) ret = main(["add", "foo"])
self.assertEqual(ret, 0) assert ret == 0
ret = main(["add", self.FOO]) ret = main(["add", "foo"])
self.assertEqual(ret, 0) assert ret == 0
def test_dir(self):
ret = main(["add", self.DATA_DIR])
self.assertEqual(ret, 0)
ret = main(["add", self.DATA_DIR]) def test_double_add_unchanged_dir(tmp_dir, dvc):
self.assertEqual(ret, 0) tmp_dir.gen("data", {"foo": "foo"})
ret = main(["add", "data"])
assert ret == 0
ret = main(["add", "data"])
assert ret == 0
def test_should_update_state_entry_for_file_after_add(mocker, dvc, tmp_dir): def test_should_update_state_entry_for_file_after_add(mocker, dvc, tmp_dir):
@@ -443,19 +410,17 @@ def test_should_update_state_entry_for_directory_after_add(
assert file_md5_counter.mock.call_count == 10 assert file_md5_counter.mock.call_count == 10
class TestAddCommit(TestDvc): def test_add_commit(tmp_dir, dvc):
def test(self): tmp_dir.gen("foo", "foo")
ret = main(["add", self.FOO, "--no-commit"]) ret = main(["add", "foo", "--no-commit"])
self.assertEqual(ret, 0) assert ret == 0
self.assertTrue(os.path.isfile(self.FOO)) assert os.path.isfile("foo")
self.assertFalse(os.path.exists(self.dvc.odb.local.path)) assert not os.path.exists(dvc.odb.local.path)
ret = main(["commit", self.FOO + ".dvc"]) ret = main(["commit", "foo" + ".dvc"])
self.assertEqual(ret, 0) assert ret == 0
self.assertTrue(os.path.isfile(self.FOO)) assert os.path.isfile("foo")
self.assertTrue( assert dvc.odb.local.exists("acbd18db4cc2f85cedef654fccc4a4d8")
self.dvc.odb.local.exists("acbd18db4cc2f85cedef654fccc4a4d8")
)
def test_should_collect_dir_cache_only_once(mocker, tmp_dir, dvc): def test_should_collect_dir_cache_only_once(mocker, tmp_dir, dvc):
@@ -474,80 +439,66 @@ def test_should_collect_dir_cache_only_once(mocker, tmp_dir, dvc):
assert counter.mock.call_count == 5 assert counter.mock.call_count == 5
class TestShouldPlaceStageInDataDirIfRepositoryBelowSymlink(TestDvc): def test_should_place_stage_in_data_dir_if_repository_below_symlink(
def test(self): mocker, tmp_dir, dvc
def is_symlink_true_below_dvc_root(path): ):
if path == os.path.dirname(self.dvc.root_dir): def is_symlink_true_below_dvc_root(path):
return True return path == os.path.dirname(dvc.root_dir)
return False
with patch.object( tmp_dir.gen({"data": {"foo": "foo"}})
system, "is_symlink", side_effect=is_symlink_true_below_dvc_root mocker.patch.object(
): system, "is_symlink", side_effect=is_symlink_true_below_dvc_root
)
ret = main(["add", os.path.join("data", "foo")])
assert ret == 0
ret = main(["add", self.DATA]) assert not (tmp_dir / "foo.dvc").exists()
self.assertEqual(0, ret) assert (tmp_dir / "data" / "foo.dvc").exists()
stage_file_path_on_data_below_symlink = (
os.path.basename(self.DATA) + DVC_FILE_SUFFIX
)
self.assertFalse(
os.path.exists(stage_file_path_on_data_below_symlink)
)
stage_file_path = self.DATA + DVC_FILE_SUFFIX
self.assertTrue(os.path.exists(stage_file_path))
class TestShouldThrowProperExceptionOnCorruptedStageFile(TestDvc): def test_should_throw_proper_exception_on_corrupted_stage_file(
def test(self): caplog, tmp_dir, dvc
ret = main(["add", self.FOO]) ):
assert 0 == ret tmp_dir.gen({"foo": "foo", "bar": " bar"})
assert main(["add", "foo"]) == 0
foo_stage = relpath(self.FOO + DVC_FILE_SUFFIX) with (tmp_dir / "foo.dvc").open("a+") as f:
f.write("this will break yaml file structure")
# corrupt stage file caplog.clear()
with open(foo_stage, "a+", encoding="utf-8") as file: assert main(["add", "bar"]) == 1
file.write("this will break yaml file structure") expected_error = (
"unable to read: 'foo.dvc', YAML file structure is corrupted"
self._caplog.clear() )
assert expected_error in caplog.text
ret = main(["add", self.BAR])
assert 1 == ret
expected_error = (
f"unable to read: '{foo_stage}', YAML file structure is corrupted"
)
assert expected_error in self._caplog.text
class TestAddFilename(TestDvc): def test_add_filename(tmp_dir, dvc):
def test(self): tmp_dir.gen({"foo": "foo", "bar": "bar", "data": {"file": "file"}})
ret = main(["add", self.FOO, self.BAR, "--file", "error.dvc"]) ret = main(["add", "foo", "bar", "--file", "error.dvc"])
self.assertNotEqual(0, ret) assert ret != 0
ret = main(["add", "-R", self.DATA_DIR, "--file", "error.dvc"]) ret = main(["add", "-R", "data", "--file", "error.dvc"])
self.assertNotEqual(0, ret) assert ret != 0
with self.assertRaises(RecursiveAddingWhileUsingFilename): with pytest.raises(RecursiveAddingWhileUsingFilename):
self.dvc.add(self.DATA_DIR, recursive=True, fname="error.dvc") dvc.add("data", recursive=True, fname="error.dvc")
ret = main(["add", self.DATA_DIR, "--file", "data_directory.dvc"]) ret = main(["add", "data", "--file", "data_directory.dvc"])
self.assertEqual(0, ret) assert ret == 0
self.assertTrue(os.path.exists("data_directory.dvc")) assert (tmp_dir / "data_directory.dvc").exists()
ret = main(["add", self.FOO, "--file", "bar.dvc"]) ret = main(["add", "foo", "--file", "bar.dvc"])
self.assertEqual(0, ret) assert ret == 0
self.assertTrue(os.path.exists("bar.dvc")) assert (tmp_dir / "bar.dvc").exists()
self.assertFalse(os.path.exists("foo.dvc")) assert not (tmp_dir / "foo.dvc").exists()
os.remove("bar.dvc") (tmp_dir / "bar.dvc").unlink()
ret = main(["add", self.FOO, "--file", "bar.dvc"]) ret = main(["add", "foo", "--file", "bar.dvc"])
self.assertEqual(0, ret) assert ret == 0
self.assertTrue(os.path.exists("bar.dvc")) assert (tmp_dir / "bar.dvc").exists()
self.assertFalse(os.path.exists("foo.dvc")) assert not (tmp_dir / "foo.dvc").exists()
def test_failed_add_cleanup(tmp_dir, scm, dvc): def test_failed_add_cleanup(tmp_dir, scm, dvc):
@@ -577,25 +528,25 @@ def test_should_not_track_git_internal_files(mocker, dvc, tmp_dir):
assert ".git" not in fname assert ".git" not in fname
class TestAddUnprotected(TestDvc): def test_add_unprotected(tmp_dir, dvc):
def test(self): tmp_dir.gen("foo", "foo")
ret = main(["config", "cache.type", "hardlink"]) ret = main(["config", "cache.type", "hardlink"])
self.assertEqual(ret, 0) assert ret == 0
ret = main(["add", self.FOO]) ret = main(["add", "foo"])
self.assertEqual(ret, 0) assert ret == 0
self.assertFalse(os.access(self.FOO, os.W_OK)) assert not os.access("foo", os.W_OK)
self.assertTrue(system.is_hardlink(self.FOO)) assert system.is_hardlink("foo")
ret = main(["unprotect", self.FOO]) ret = main(["unprotect", "foo"])
self.assertEqual(ret, 0) assert ret == 0
ret = main(["add", self.FOO]) ret = main(["add", "foo"])
self.assertEqual(ret, 0) assert ret == 0
self.assertFalse(os.access(self.FOO, os.W_OK)) assert not os.access("foo", os.W_OK)
self.assertTrue(system.is_hardlink(self.FOO)) assert system.is_hardlink("foo")
@pytest.fixture @pytest.fixture
+2 -2
View File
@@ -12,7 +12,7 @@ from tests.utils import ANY
def test_daemon_analytics(mocker, tmp_path): def test_daemon_analytics(mocker, tmp_path):
mock_send = mocker.patch("dvc.analytics.send") mock_send = mocker.patch("dvc.analytics.send")
report = os.fspath(tmp_path) report = os.fspath(tmp_path)
assert 0 == main(["daemon", "analytics", report]) assert main(["daemon", "analytics", report]) == 0
mock_send.assert_called_with(report) mock_send.assert_called_with(report)
@@ -21,7 +21,7 @@ def test_main_analytics(mocker, tmp_dir, dvc):
mock_is_enabled = mocker.patch("dvc.analytics.collect_and_send_report") mock_is_enabled = mocker.patch("dvc.analytics.collect_and_send_report")
mock_report = mocker.patch("dvc.analytics.is_enabled", return_value=True) mock_report = mocker.patch("dvc.analytics.is_enabled", return_value=True)
tmp_dir.gen("foo", "text") tmp_dir.gen("foo", "text")
assert 0 == main(["add", "foo"]) assert main(["add", "foo"]) == 0
assert mock_is_enabled.called assert mock_is_enabled.called
assert mock_report.called assert mock_report.called
+1 -1
View File
@@ -64,7 +64,7 @@ def test_init_quiet_should_not_display_welcome_screen(tmp_dir, scm, caplog):
with caplog.at_level(logging.INFO, logger="dvc"): with caplog.at_level(logging.INFO, logger="dvc"):
ret = main(["init", "--quiet"]) ret = main(["init", "--quiet"])
assert 0 == ret assert ret == 0
assert "" == caplog.text assert "" == caplog.text
+20 -30
View File
@@ -1,37 +1,27 @@
import os import os
from dvc.cli import main
from tests.basic_env import TestDvc
def test_unprotect(tmp_dir, dvc):
tmp_dir.gen("foo", "foo")
class TestUnprotect(TestDvc): dvc.odb.local.cache_types = ["hardlink"]
def test(self): dvc.add("foo")
ret = main(["config", "cache.type", "hardlink"]) cache = os.path.join(
self.assertEqual(ret, 0) ".dvc", "cache", "ac", "bd18db4cc2f85cedef654fccc4a4d8"
)
assert not os.access("foo", os.W_OK)
assert not os.access(cache, os.W_OK)
ret = main(["add", self.FOO]) dvc.unprotect("foo")
self.assertEqual(ret, 0) assert os.access("foo", os.W_OK)
cache = os.path.join( if os.name == "nt":
".dvc", "cache", "ac", "bd18db4cc2f85cedef654fccc4a4d8" # NOTE: cache is now unprotected, because NTFS doesn't allow
) # deleting read-only files, so we have to try to set write perms
# on files that we try to delete, which propagates to the cache
# file. But it should be restored after the next cache check, hence
# why we call `dvc status` here.
assert os.access(cache, os.W_OK)
dvc.status()
self.assertFalse(os.access(self.FOO, os.W_OK)) assert not os.access(cache, os.W_OK)
self.assertFalse(os.access(cache, os.W_OK))
ret = main(["unprotect", self.FOO])
self.assertEqual(ret, 0)
self.assertTrue(os.access(self.FOO, os.W_OK))
if os.name == "nt":
# NOTE: cache is now unprotected, because NTFS doesn't allow
# deleting read-only files, so we have to try to set write perms
# on files that we try to delete, which propagates to the cache
# file. But it should be restored after the next cache check, hence
# why we call `dvc status` here.
self.assertTrue(os.access(cache, os.W_OK))
ret = main(["status"])
self.assertEqual(ret, 0)
self.assertFalse(os.access(cache, os.W_OK))
+15 -20
View File
@@ -1,12 +1,10 @@
import os import os
from unittest.mock import patch
from dvc.output import Output from dvc.output import Output
from dvc.stage import Stage from dvc.stage import Stage
from dvc.utils import relpath from dvc.utils import relpath
from dvc_data.hashfile.hash_info import HashInfo from dvc_data.hashfile.hash_info import HashInfo
from dvc_data.hashfile.meta import Meta from dvc_data.hashfile.meta import Meta
from tests.basic_env import TestDvc
def test_str_workdir_outside_repo(tmp_dir, erepo_dir): def test_str_workdir_outside_repo(tmp_dir, erepo_dir):
@@ -52,24 +50,21 @@ def test_str_on_external_absolute_path(dvc):
assert str(output) == abs_path assert str(output) == abs_path
class TestGetFilesNumber(TestDvc): def test_return_0_on_no_cache(dvc):
def _get_output(self): o = Output(Stage(dvc), "path")
stage = Stage(self.dvc) o.use_cache = False
return Output(stage, "path") assert o.get_files_number() == 0
def test_return_0_on_no_cache(self):
o = self._get_output()
o.use_cache = False
self.assertEqual(0, o.get_files_number())
def test_return_multiple_for_dir(self): def test_return_multiple_for_dir(dvc):
o = self._get_output() o = Output(Stage(dvc), "path")
o.hash_info = HashInfo("md5", "12345678.dir") o.hash_info = HashInfo("md5", "12345678.dir")
o.meta = Meta(nfiles=2) o.meta = Meta(nfiles=2)
self.assertEqual(2, o.get_files_number()) assert o.get_files_number() == 2
@patch.object(Output, "is_dir_checksum", False)
def test_return_1_on_single_file_cache(self): def test_return_1_on_single_file_cache(mocker, dvc):
o = self._get_output() mocker.patch.object(Output, "is_dir_checksum", False)
o.hash_info = HashInfo("md5", "12345678") o = Output(Stage(dvc), "path")
self.assertEqual(1, o.get_files_number()) o.hash_info = HashInfo("md5", "12345678")
assert o.get_files_number() == 1
+2 -2
View File
@@ -50,11 +50,11 @@ def test_wait(tmp_dir, mocker):
proc._proc.wait = mocker.Mock(return_value=None) proc._proc.wait = mocker.Mock(return_value=None)
proc._proc.returncode = 0 proc._proc.returncode = 0
assert 0 == proc.wait() assert proc.wait() == 0
proc._proc.wait.assert_called_once() proc._proc.wait.assert_called_once()
# once subprocess return code is set, future ManagedProcess.wait() calls # once subprocess return code is set, future ManagedProcess.wait() calls
# should not block # should not block
proc._proc.wait.reset_mock() proc._proc.wait.reset_mock()
assert 0 == proc.wait() assert proc.wait() == 0
proc._proc.wait.assert_not_called() proc._proc.wait.assert_not_called()