Skip to content

Conversation

@fabiosantoscode
Copy link
Contributor

@fabiosantoscode fabiosantoscode commented Jan 21, 2020

After I'd started, @pared pointed out that it would be best to convert the tests to plain functions as opposed to classes, to better utilise the mocker fixture. I ended up having to create a bunch of wrappers just to use this fixture. Let me know if you'd like me to go ahead and convert all of the classes that were using spy into plain pytest function tests.

Fixes #3198


  • ❗ Have you followed the guidelines in the Contributing to DVC list?

  • 📖 Check this box if this PR does not require documentation updates, or if it does and you have created a separate PR in dvc.org with such updates (or at least opened an issue about it in that repo). Please link below to your PR (or issue) in the dvc.org repo.

  • ❌ Have you checked DeepSource, CodeClimate, and other sanity checks below? We consider their findings recommendatory and don't expect everything to be addressed. Please review them carefully and fix those that actually improve code or fix bugs.

Thank you for the contribution - we'll try to review it as soon as possible. 🙏

@fabiosantoscode fabiosantoscode requested a review from pared January 21, 2020 14:49
Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabiosantoscode , thanks!

I'd prefer to handle the conversion separately, for now, the use_mocker method is good enough)

def test(self):
file_md5_counter = spy(dvc.remote.local.file_md5)
file_md5_counter = self.mocker.spy(dvc.remote.local, "file_md5")
with patch.object(dvc.remote.local, "file_md5", file_md5_counter):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With mocker spy, there is no need to patch, it is automatically done by mocker, so with statement can be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to know, thanks!

def test(self):
file_md5_counter = spy(dvc.remote.local.file_md5)
file_md5_counter = self.mocker.spy(dvc.remote.local, "file_md5")
with patch.object(dvc.remote.local, "file_md5", file_md5_counter):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

get_dir_checksum_counter = self.mocker.spy(
RemoteLOCAL, "get_dir_checksum"
)
with patch.object(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

stage_creator_spy = spy(dvc.repo.add._create_stages)
stage_creator_spy = self.mocker.spy(dvc.repo.add, "_create_stages")

with patch.object(dvc.repo.add, "_create_stages", stage_creator_spy):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above


unprotect_spy = spy(RemoteLOCAL.unprotect)
unprotect_spy = mocker.spy(RemoteLOCAL, "unprotect")
mocker.patch.object(RemoteLOCAL, "unprotect", unprotect_spy)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above


copy_spy = spy(dvc.cache.local.copy)
copy_spy = mocker.spy(dvc.cache.local, "copy")
mocker.patch.object(dvc.cache.local, "copy", copy_spy)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above

test_get_file_checksum = self.mocker.spy(
RemoteLOCAL, "get_file_checksum"
)
with patch.object(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above

remove_outs_call_counter = self.mocker.spy(
dvc.stage.Stage, "remove_outs"
)
with patch.object(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above

contains_symlink_spy = self.mocker.spy(
dvc.utils.fs, "contains_symlink_up_to"
)
with patch.object(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above

@fabiosantoscode
Copy link
Contributor Author

Removed the useless patch.object(), thanks @pared

@ghost ghost requested a review from pared January 22, 2020 07:02
@pared
Copy link
Contributor

pared commented Jan 22, 2020

@fabiosantoscode, moving to mocker's spy seems good. But to be honest I don't like that we are mixning pytest and UnitTest, I think that those tests (only those that use spy) should be converted, it is something that we will have to do anyway at some point. Could you do that?

@fabiosantoscode
Copy link
Contributor Author

Sounds good @pared let me get to that.

@fabiosantoscode
Copy link
Contributor Author

@pared there ⛄

from tests.utils import get_gitignore_content
from tests.utils import spy

dvc_module = dvc # to disambiguate between the module and the fixture
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe import dvc as dvc_module?

with patch.object(dvc.remote.local, "file_md5", file_md5_counter):
ret = main(["config", "cache.type", "copy"])
self.assertEqual(ret, 0)
class TestDvcWithMocker(TestDvc):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it can be removed now :)

self.assertEqual(file_md5_counter.mock.call_count, 1)
ret = main(["add", "foo"])
assert ret == 0
assert file_md5_counter.mock.call_count == 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be simplified to file_md5_counter.call_count, this applies to each use case of spy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pared It is a direct conversion that is explicit, we could leave it as is with == 1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I change it to not have .mock then? It does seem to do the same thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does, I don't think its crucial, let's leave it as is.

tmp_dir.gen({"foo": "foo"})
test_get_file_checksum = mocker.spy(RemoteLOCAL, "get_file_checksum")
url = Local.get_url()
ret = main(["remote", "add", "-d", TEST_REMOTE, url])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to use TEST_REMOTE, you can use some string name, like "upstream"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pared I think it is out of scope for this change 🙂

self.assertEqual(fd.read(), "content")


class TestShouldRemoveOutsBeforeImport(TestDvc):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be refactored.
prepare external resource using tmp_path_factory fixture (eg tmp_path_factory.mktemp("external_source") / "file"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this before refactoring, then forgot to remove the original :)



def test_should_remove_outs_before_import(mocker, dvc, tmp_path_factory):
import_dir = TmpDir(tmp_path_factory.mktemp("import"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have erepo_dir fixture for such things:
with erepo_dir.chdir(): erepo_dir.dvc_gen("foo": "foo content")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this test does not pass for py3.5, need to wrap tmp_path_factory.mktemp in fspath.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense.

dirname_patch.assert_called_once()

@patch.object(System, "is_symlink", return_value=False)
def test_should_call_recursive_on_no_condition_matched(self, _):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can also be refactored. Just extract it to standalone test, not part of TestContainsLink class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@fabiosantoscode
Copy link
Contributor Author

fabiosantoscode commented Jan 22, 2020

I've fixed most comments @pared and @efiop except this one: #3208 (comment) about which I'm not clear on what to do.

Copy link
Contributor

@pared pared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job @fabiosantoscode! LGTM

@efiop efiop merged commit 4903996 into treeverse:master Jan 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove spy method

3 participants