From 198bf55aafad6e98c2b643e652c0b222308cb3c2 Mon Sep 17 00:00:00 2001 From: "Mr. Outis" Date: Wed, 22 Jan 2020 12:23:11 -0600 Subject: [PATCH] diff: reimplement interface and tests from scratch --- dvc/command/diff.py | 140 ++----------------- dvc/repo/brancher.py | 2 + dvc/repo/diff.py | 276 ++++++------------------------------ tests/func/test_diff.py | 303 ++++++++-------------------------------- 4 files changed, 118 insertions(+), 603 deletions(-) diff --git a/dvc/command/diff.py b/dvc/command/diff.py index 7e064adda..74f15edf5 100644 --- a/dvc/command/diff.py +++ b/dvc/command/diff.py @@ -1,12 +1,7 @@ import argparse import logging -import humanize -import inflect -from funcy import compact - -from dvc.command.base import append_doc_link -from dvc.command.base import CmdBase +from dvc.command.base import CmdBase, append_doc_link from dvc.exceptions import DvcException @@ -14,122 +9,11 @@ logger = logging.getLogger(__name__) class CmdDiff(CmdBase): - @staticmethod - def _print_size(size): - if size < 0: - change = "decreased by {}" - elif size > 0: - change = "increased by {}" - else: - change = "not changed" - natur_size = humanize.naturalsize(abs(size)) - return change.format(natur_size) - - @staticmethod - def _get_md5_string(sign, file_name, checksum): - sample_msg = "" - if file_name: - sample_msg = "{}{} with md5 {}\n" - sample_msg = sample_msg.format(sign, file_name, checksum) - return sample_msg - - @classmethod - def _get_dir_changes(cls, dct): - import dvc.repo.diff as diff - - engine = inflect.engine() - changes_msg = ( - "{} {} untouched, {} {} modified, {} {} added, " - "{} {} deleted, size was {}" - ) - changes_msg = changes_msg.format( - dct[diff.DIFF_IDENT], - engine.plural("file", dct[diff.DIFF_IDENT]), - dct[diff.DIFF_CHANGE], - engine.plural("file", dct[diff.DIFF_CHANGE]), - dct[diff.DIFF_NEW], - engine.plural("file", dct[diff.DIFF_NEW]), - dct[diff.DIFF_DEL], - engine.plural("file", dct[diff.DIFF_DEL]), - cls._print_size(dct[diff.DIFF_SIZE]), - ) - return changes_msg - - @classmethod - def _get_file_changes(cls, dct): - import dvc.repo.diff as diff - - if ( - dct.get(diff.DIFF_OLD_FILE) - and dct.get(diff.DIFF_NEW_FILE) - and dct[diff.DIFF_SIZE] == 0 - ): - msg = "file size was not changed" - elif dct.get(diff.DIFF_NEW_FILE): - msg = "added file with size {}".format( - humanize.naturalsize(dct[diff.DIFF_SIZE]) - ) - elif dct.get(diff.DIFF_OLD_FILE): - msg = "deleted file with size {}".format( - humanize.naturalsize(abs(dct[diff.DIFF_SIZE])) - ) - else: - msg = "file was modified, file size {}".format( - cls._print_size(dct[diff.DIFF_SIZE]) - ) - return msg - - @classmethod - def _get_royal_changes(cls, dct): - import dvc.repo.diff as diff - - if dct[diff.DIFF_SIZE] != diff.DIFF_SIZE_UNKNOWN: - if dct.get("is_dir"): - return cls._get_dir_changes(dct) - else: - return cls._get_file_changes(dct) - return "size is ?" - - @classmethod - def _show(cls, diff_dct): - import dvc.repo.diff as diff - - msg = "dvc diff from {} to {}".format( - diff_dct[diff.DIFF_A_REF], diff_dct[diff.DIFF_B_REF] - ) - if diff_dct.get(diff.DIFF_EQUAL): - logger.info(msg) - return - for dct in diff_dct[diff.DIFF_LIST]: - msg += "\n\ndiff for '{}'\n".format(dct[diff.DIFF_TARGET]) - msg += cls._get_md5_string( - "-", - dct.get(diff.DIFF_OLD_FILE), - dct.get(diff.DIFF_OLD_CHECKSUM), - ) - msg += cls._get_md5_string( - "+", - dct.get(diff.DIFF_NEW_FILE), - dct.get(diff.DIFF_NEW_CHECKSUM), - ) - msg += "\n" - msg += cls._get_royal_changes(dct) - logger.info(msg) - return msg - def run(self): try: - msg = self.repo.diff( - self.args.a_ref, target=self.args.target, b_ref=self.args.b_ref - ) - self._show(msg) + self.repo.diff(self.args.a_ref, self.args.b_ref, self.args.target) except DvcException: - msg = "failed to get 'diff {}'" - args = " ".join( - compact([self.args.target, self.args.a_ref, self.args.b_ref]) - ) - msg = msg.format(args) - logger.exception(msg) + logger.exception("failed to get 'diff {}'") return 1 return 0 @@ -147,15 +31,6 @@ def add_parser(subparsers, parent_parser): help=DIFF_HELP, formatter_class=argparse.RawDescriptionHelpFormatter, ) - diff_parser.add_argument( - "-t", - "--target", - help=( - "Source path to a data file or directory. Default None. " - "If not specified, compares all files and directories " - "that are under DVC control in the current working space." - ), - ) diff_parser.add_argument( "a_ref", help="Git reference from which diff calculates" ) @@ -167,4 +42,13 @@ def add_parser(subparsers, parent_parser): ), nargs="?", ) + diff_parser.add_argument( + "-t", + "--target", + help=( + "Source path to a data file or directory. Default None. " + "If not specified, compares all files and directories " + "that are under DVC control in the current working space." + ), + ) diff_parser.set_defaults(func=CmdDiff) diff --git a/dvc/repo/brancher.py b/dvc/repo/brancher.py index 955b13513..9d1090190 100644 --- a/dvc/repo/brancher.py +++ b/dvc/repo/brancher.py @@ -41,6 +41,8 @@ def brancher( # noqa: E302 if all_tags: revs.extend(scm.list_tags()) + revs = filter(None, revs) + try: if revs: for sha, names in group_by(scm.resolve_rev, revs).items(): diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py index 165b286b9..dda1e0c79 100644 --- a/dvc/repo/diff.py +++ b/dvc/repo/diff.py @@ -1,246 +1,56 @@ import os -from errno import ENOENT +import collections -from dvc import logger -from dvc.scm.base import FileNotInCommitError -from dvc.scm.git import DIFF_A_REF, DIFF_B_REF, DIFF_A_TREE, DIFF_B_TREE -from dvc.scm.git import DIFF_EQUAL from . import locked +from ..compat import fspath -DIFF_TARGET = "target" -DIFF_IS_DIR = "is_dir" -DIFF_OLD_FILE = "old_file" -DIFF_OLD_CHECKSUM = "old_checksum" -DIFF_NEW_FILE = "new_file" -DIFF_NEW_CHECKSUM = "new_checksum" -DIFF_SIZE = "size_diff" -DIFF_DEL = "del" -DIFF_IDENT = "ident" -DIFF_CHANGE = "changes" -DIFF_NEW = "new" -DIFF_MOVE = "moves" -DIFF_LIST = "diffs" -DIFF_SIZE_UNKNOWN = "?" -DIFF_A_OUTPUT = "a_output" -DIFF_B_OUTPUT = "b_output" -DIFF_DELETED = "deleted_file" -DIFF_IS_NEW = "created_file" +Diffable = collections.namedtuple("Diffable", "filename, checksum, size") +Diffable.__doc__ = "Common interface for comparable entries." -def _file_not_exists(error, result): - if error.errno == ENOENT: - result.update({DIFF_SIZE: DIFF_SIZE_UNKNOWN}) - else: - raise error +def diffable_from_output(output): + try: + size = os.path.getsize(fspath(output.path_info)) + except OSError: + size = None - -def _extract_dir(self, dir_not_exists, output): - """Extract the content of dvc tree file - Args: - self(object) - Repo class instance - dir_not_exists(bool) - flag for directory existence - output(object) - OutputLOCAL class instance - Returns: - dict - dictionary with keys - paths to file in .dvc/cache - values -checksums for that files - """ - if not dir_not_exists: - lst = output.dir_cache - return {i["relpath"]: i["md5"] for i in lst} - return {} - - -def _get_dir_info(dir_not_exists, a_output): - if not dir_not_exists: - return str(a_output), a_output.checksum - return "", "" - - -def _ident_files(a_entries, b_entries): - keys = [ - key for key in a_entries.keys() if b_entries.get(key) == a_entries[key] - ] - return len(keys) - - -def _modified_files(self, a_entries, b_entries): - keys = [key for key in a_entries.keys() if key in b_entries] - diff_size = 0 - modified_count = 0 - for key in keys: - if a_entries[key] != b_entries[key]: - modified_count += 1 - diff_size += os.path.getsize( - self.cache.local.get(b_entries[key]) - ) - os.path.getsize(self.cache.local.get(a_entries[key])) - return modified_count, diff_size - - -def _deleted_files(self, a_entries, b_entries): - diff_size = 0 - deleted_count = 0 - for key, value in a_entries.items(): - if key not in b_entries: - deleted_count += 1 - diff_size -= os.path.getsize(self.cache.local.get(a_entries[key])) - return deleted_count, diff_size - - -def _new_files(self, a_entries, b_entries): - diff_size = 0 - new_count = 0 - for key, value in b_entries.items(): - if key not in a_entries: - new_count += 1 - diff_size += os.path.getsize(self.cache.local.get(b_entries[key])) - return new_count, diff_size - - -def _get_tree_changes(self, a_entries, b_entries): - result = { - DIFF_DEL: 0, - DIFF_IDENT: 0, - DIFF_CHANGE: 0, - DIFF_NEW: 0, - DIFF_MOVE: 0, - } - result[DIFF_IDENT] = _ident_files(a_entries, b_entries) - result[DIFF_CHANGE], diff_size = _modified_files( - self, a_entries, b_entries + return Diffable( + filename=str(output.path_info), checksum=output.checksum, size=size ) - result[DIFF_SIZE] = diff_size - result[DIFF_DEL], diff_size = _deleted_files(self, a_entries, b_entries) - result[DIFF_SIZE] += diff_size - result[DIFF_NEW], diff_size = _new_files(self, a_entries, b_entries) - result[DIFF_SIZE] += diff_size - return result - - -def _check_local_cache(a_out, is_checked): - if a_out is not None and a_out.scheme != "local": - is_checked.append(str(a_out)) - return True - return False - - -def _is_dir(path, a_outs, b_outs): - if a_outs.get(path): - return a_outs[path].is_dir_checksum - else: - return b_outs[path].is_dir_checksum - - -def _get_diff_outs(self, diff_dct): - self.tree = diff_dct[DIFF_A_TREE] - a_outs = {str(out): out for st in self.stages for out in st.outs} - self.tree = diff_dct[DIFF_B_TREE] - b_outs = {str(out): out for st in self.stages for out in st.outs} - outs_paths = set(a_outs.keys()) - outs_paths.update(b_outs.keys()) - results = {} - non_local_cache = [] - for path in outs_paths: - check1 = _check_local_cache(a_outs.get(path), non_local_cache) - check2 = _check_local_cache(b_outs.get(path), non_local_cache) - # skip files/directories with non-local cache for now - if check1 or check2: - continue - results[path] = {} - results[path][DIFF_A_OUTPUT] = a_outs.get(path) - results[path][DIFF_B_OUTPUT] = b_outs.get(path) - results[path][DIFF_IS_NEW] = path not in a_outs - results[path][DIFF_DELETED] = path not in b_outs - results[path][DIFF_IS_DIR] = _is_dir(path, a_outs, b_outs) - if non_local_cache: - logger.warning( - "Diff is not supported for non-local outputs. Ignoring: {}".format( - non_local_cache - ) - ) - - return results - - -def _diff_dir(self, target, diff_dct): - result = {DIFF_TARGET: target} - result[DIFF_IS_DIR] = True - a_entries, b_entries = {}, {} - try: - a_entries = _extract_dir( - self, diff_dct[DIFF_IS_NEW], diff_dct[DIFF_A_OUTPUT] - ) - b_entries = _extract_dir( - self, diff_dct[DIFF_DELETED], diff_dct[DIFF_B_OUTPUT] - ) - result[DIFF_OLD_FILE], result[DIFF_OLD_CHECKSUM] = _get_dir_info( - diff_dct[DIFF_IS_NEW], diff_dct[DIFF_A_OUTPUT] - ) - result[DIFF_NEW_FILE], result[DIFF_NEW_CHECKSUM] = _get_dir_info( - diff_dct[DIFF_DELETED], diff_dct[DIFF_B_OUTPUT] - ) - result.update(_get_tree_changes(self, a_entries, b_entries)) - except IOError as e: - _file_not_exists(e, result) - return result - - -def _diff_file(self, target, diff_dct): - result = {DIFF_TARGET: target} - size = 0 - try: - if not diff_dct[DIFF_IS_NEW]: - result[DIFF_OLD_FILE] = target - result[DIFF_OLD_CHECKSUM] = diff_dct[DIFF_A_OUTPUT].checksum - size -= os.path.getsize( - self.cache.local.get(diff_dct[DIFF_A_OUTPUT].checksum) - ) - if not diff_dct[DIFF_DELETED]: - result[DIFF_NEW_FILE] = target - result[DIFF_NEW_CHECKSUM] = diff_dct[DIFF_B_OUTPUT].checksum - size += os.path.getsize( - self.cache.local.get(diff_dct[DIFF_B_OUTPUT].checksum) - ) - result[DIFF_SIZE] = size - except IOError as e: - _file_not_exists(e, result) - return result - - -def _diff_royal(self, target, diff_dct): - if diff_dct[DIFF_IS_DIR]: - return _diff_dir(self, target, diff_dct) - return _diff_file(self, target, diff_dct) @locked -def diff(self, a_ref, target=None, b_ref=None): - """Generates diff message string output - - Args: - target(str) - file/directory to check diff of - a_ref(str) - first tag - (optional) b_ref(str) - second git tag - - Returns: - string: string of output message with diff info +def diff(self, a_ref=None, b_ref=None, *, target=None): """ - result = {} - diff_dct = self.scm.get_diff_trees(a_ref, b_ref=b_ref) - result[DIFF_A_REF] = diff_dct[DIFF_A_REF] - result[DIFF_B_REF] = diff_dct[DIFF_B_REF] - if diff_dct[DIFF_EQUAL]: - result[DIFF_EQUAL] = True - return result - result[DIFF_LIST] = [] - diff_outs = _get_diff_outs(self, diff_dct) - if target is None: - result[DIFF_LIST] = [ - _diff_royal(self, path, diff_outs[path]) for path in diff_outs - ] - elif target in diff_outs: - result[DIFF_LIST] = [_diff_royal(self, target, diff_outs[target])] - else: - msg = "Have not found file/directory '{}' in the commits" - raise FileNotInCommitError(msg.format(target)) - return result + By default, it compares the working tree with the last commit's tree. + + When a `target` path is given, it only shows that file's comparison. + + This implementation differs from `git diff`, since DVC doesn't have + the concept of `index`, `dvc diff` would be the same as `dvc diff HEAD`. + """ + a_ref = a_ref or "HEAD" + outs = {} + + for branch in self.brancher(revs=[a_ref, b_ref]): + outs[branch] = set( + diffable_from_output(out) + for stage in self.stages + for out in stage.outs + ) + + old = outs[a_ref] + new = outs[b_ref or "working tree"] + delta = old ^ new + + if not delta: + return + + old &= delta + new &= delta + + return { + "old": [entry._asdict() for entry in old], + "new": [entry._asdict() for entry in new], + } diff --git a/tests/func/test_diff.py b/tests/func/test_diff.py index 15d2cc982..e496d7cf0 100644 --- a/tests/func/test_diff.py +++ b/tests/func/test_diff.py @@ -1,267 +1,86 @@ -import os - -import dvc.repo.diff as diff -from dvc.main import main -from dvc.scm.base import FileNotInCommitError -from tests.basic_env import TestDvcGit +import pytest -class TestDiff(TestDvcGit): - def setUp(self): - super().setUp() +def test_no_scm(tmp_dir, dvc): + tmp_dir.dvc_gen("file", "text") - self.new_file = "new_test_file" - self.create(self.new_file, self.new_file) - stage = self.dvc.add(self.new_file)[0] - self.a_ref = self.git.git.rev_parse(self.git.head.commit, short=True) - self.new_checksum = stage.outs[0].checksum - self.git.index.add([self.new_file + ".dvc"]) - self.git.index.commit("adds new_file") - self.test_dct = { - diff.DIFF_A_REF: self.a_ref, - diff.DIFF_B_REF: self.git.git.rev_parse( - self.git.head.commit, short=True - ), - diff.DIFF_LIST: [ - { - diff.DIFF_TARGET: self.new_file, - diff.DIFF_NEW_FILE: self.new_file, - diff.DIFF_NEW_CHECKSUM: self.new_checksum, - diff.DIFF_SIZE: 13, - } - ], - } - - def test(self): - out = self.dvc.scm.get_diff_trees(self.a_ref) - self.assertFalse(out[diff.DIFF_EQUAL]) - self.assertEqual(self.a_ref, out[diff.DIFF_A_REF]) - self.assertEqual( - self.git.git.rev_parse(self.git.head.commit, short=True), - out[diff.DIFF_B_REF], - ) + pytest.skip("TODO: define behavior, should it fail?") -class TestDiffRepo(TestDiff): - def test(self): - result = self.dvc.diff(self.a_ref, target=self.new_file) - self.assertEqual(self.test_dct, result) +def test_added(tmp_dir, scm, dvc): + tmp_dir.dvc_gen("file", "text") + + pytest.skip("TODO: define output structure") -class TestDiffCmdLine(TestDiff): - def test(self): - ret = main(["diff", "-t", self.new_file, self.a_ref]) - self.assertEqual(ret, 0) +def test_deleted(tmp_dir, scm, dvc): + tmp_dir.dvc_gen("file", "text", commit="add file") + (tmp_dir / "file").unlink() + + pytest.skip("TODO: define output structure") -class TestDiffDir(TestDvcGit): - def setUp(self): - super().setUp() +def test_modified(tmp_dir, scm, dvc): + tmp_dir.dvc_gen("file", "first", commit="first version") + tmp_dir.dvc_gen("file", "second") - stage = self.dvc.add(self.DATA_DIR)[0] - self.git.index.add([self.DATA_DIR + ".dvc"]) - self.git.index.commit("adds data_dir") - self.a_ref = self.git.git.rev_parse( - self.dvc.scm.repo.head.commit, short=True - ) - self.old_checksum = stage.outs[0].checksum - self.new_file = os.path.join(self.DATA_SUB_DIR, diff.DIFF_NEW_FILE) - self.create(self.new_file, self.new_file) - stage = self.dvc.add(self.DATA_DIR)[0] - self.git.index.add([self.DATA_DIR + ".dvc"]) - self.git.index.commit(message="adds data_dir with new_file") - self.new_checksum = stage.outs[0].checksum - - def test(self): - out = self.dvc.scm.get_diff_trees(self.a_ref) - self.assertFalse(out[diff.DIFF_EQUAL]) - self.assertEqual(self.a_ref, out[diff.DIFF_A_REF]) - self.assertEqual( - self.git.git.rev_parse(self.git.head.commit, short=True), - out[diff.DIFF_B_REF], - ) + pytest.skip("TODO: define output structure") -class TestDiffDirRepo(TestDiffDir): - maxDiff = None +def test_refs(tmp_dir, scm, dvc): + tmp_dir.dvc_gen("file", "first", commit="first version") + tmp_dir.dvc_gen("file", "second", commit="second version") + tmp_dir.dvc_gen("file", "third", commit="third version") - def test(self): - result = self.dvc.diff(self.a_ref, target=self.DATA_DIR) - test_dct = { - diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True), - diff.DIFF_B_REF: self.git.git.rev_parse( - self.git.head.commit, short=True - ), - diff.DIFF_LIST: [ - { - diff.DIFF_CHANGE: 0, - diff.DIFF_DEL: 0, - diff.DIFF_IDENT: 2, - diff.DIFF_MOVE: 0, - diff.DIFF_NEW: 1, - diff.DIFF_IS_DIR: True, - diff.DIFF_TARGET: self.DATA_DIR, - diff.DIFF_NEW_FILE: self.DATA_DIR, - diff.DIFF_NEW_CHECKSUM: self.new_checksum, - diff.DIFF_OLD_FILE: self.DATA_DIR, - diff.DIFF_OLD_CHECKSUM: self.old_checksum, - diff.DIFF_SIZE: 30, - } - ], - } - self.assertEqual(test_dct, result) + # dvc.diff("HEAD~1") --> (third, second) + # dvc.diff("HEAD~1", "HEAD~2") --> (second, first) + # dvc.diff("missing") --> error + pytest.skip("TODO: define output structure") -class TestDiffDirRepoDeletedFile(TestDiffDir): - maxDiff = None +def test_target(tmp_dir, scm, dvc): + tmp_dir.dvc_gen("foo", "foo") + tmp_dir.dvc_gen("bar", "bar") + scm.add([".gitignore", "foo.dvc", "bar.dvc"]) + scm.commit("lowercase") - def setUp(self): - super().setUp() + tmp_dir.dvc_gen("foo", "FOO") + tmp_dir.dvc_gen("bar", "BAR") + scm.add(["foo.dvc", "bar.dvc"]) + scm.commit("uppercase") - self.b_ref = self.a_ref - tmp = self.new_checksum - self.new_checksum = self.old_checksum - self.a_ref = str(self.dvc.scm.repo.head.commit) - self.old_checksum = tmp - - def test(self): - result = self.dvc.diff( - self.a_ref, b_ref=self.b_ref, target=self.DATA_DIR - ) - test_dct = { - diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True), - diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True), - diff.DIFF_LIST: [ - { - diff.DIFF_CHANGE: 0, - diff.DIFF_DEL: 1, - diff.DIFF_IDENT: 2, - diff.DIFF_MOVE: 0, - diff.DIFF_NEW: 0, - diff.DIFF_IS_DIR: True, - diff.DIFF_TARGET: self.DATA_DIR, - diff.DIFF_NEW_FILE: self.DATA_DIR, - diff.DIFF_NEW_CHECKSUM: self.new_checksum, - diff.DIFF_OLD_FILE: self.DATA_DIR, - diff.DIFF_OLD_CHECKSUM: self.old_checksum, - diff.DIFF_SIZE: -30, - } - ], - } - self.assertEqual(test_dct, result) + # dvc.diff("HEAD~1", target="foo") + # dvc.diff("HEAD~1", target="missing") --> error + pytest.skip("TODO: define output structure") -class TestDiffFileNotFound(TestDiffDir): - def setUp(self): - super().setUp() - self.unknown_file = "unknown_file_" + str(id(self)) +def test_directories(tmp_dir, scm, dvc): + tmp_dir.dvc_gen({"dir": {"1": "1", "2": "2"}}, commit="add a directory") + tmp_dir.dvc_gen({"dir": {"2": "2"}}, commit="delete a file") + tmp_dir.dvc_gen({"dir": {"2": "two"}}, commit="modify a file") + tmp_dir.dvc_gen({"dir": {"2": "two", "3": "3"}}, commit="add a file") - def test(self): - with self.assertRaises(FileNotInCommitError): - self.dvc.diff(self.a_ref, target=self.unknown_file) + # dvc.diff(":/directory", ":/init") --> (add: dir/1, dir/2) + # dvc.diff(":/delete", ":/directory") --> (deleted: dir/1) + # dvc.diff(":/modify", ":/directory") --> (modified: dir/2) + # dvc.diff(":/modify", ":/delete") --> (add: dir/3) + # dvc.diff(":/add a file", ":/modify") + pytest.skip("TODO: define output structure") -class TestDiffModifiedFile(TestDiff): - maxDiff = None - - def setUp(self): - super().setUp() - - self.old_checksum = self.new_checksum - self.new_file_content = "new_test_file_bigger_content_123456789" - self.diff_len = len(self.new_file) + len(self.new_file_content) - self.create(self.new_file, self.new_file_content) - stage = self.dvc.add(self.new_file)[0] - self.git.index.add([self.new_file + ".dvc"]) - self.git.index.commit("change new_file content to be bigger") - self.new_checksum = stage.outs[0].checksum - self.b_ref = self.git.git.rev_parse(self.git.head.commit, short=True) - - def test(self): - result = self.dvc.diff( - self.a_ref, b_ref=self.b_ref, target=self.new_file - ) - test_dct = { - diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True), - diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True), - diff.DIFF_LIST: [ - { - diff.DIFF_NEW_CHECKSUM: self.new_checksum, - diff.DIFF_NEW_FILE: self.new_file, - diff.DIFF_TARGET: self.new_file, - diff.DIFF_SIZE: self.diff_len, - } - ], - } - self.assertEqual(test_dct, result) +def test_cli(tmp_dir, scm, dvc): + tmp_dir.dvc_gen("file", "text") + # main(["diff"]) + pytest.skip("TODO: define output structure") -class TestDiffDirWithFile(TestDiffDir): - maxDiff = None +def test_json(tmp_dir, scm, dvc): + # result = { + # "added": {...}, + # "renamed": {...}, + # "modified": {...}, + # "deleted": {...}, + # } - def setUp(self): - super().setUp() - - self.a_ref = self.git.git.rev_parse(self.git.head.commit, short=True) - self.old_checksum = self.new_checksum - self.new_file_content = "new_test_file_bigger_content_123456789" - self.diff_len = len(self.new_file_content) - self.create(self.new_file, self.new_file_content) - stage = self.dvc.add(self.DATA_DIR)[0] - self.git.index.add([self.DATA_DIR + ".dvc"]) - self.git.index.commit(message="modify file in the data dir") - self.new_checksum = stage.outs[0].checksum - self.b_ref = self.git.git.rev_parse(self.git.head.commit, short=True) - - def test(self): - result = self.dvc.diff(self.a_ref, target=self.DATA_DIR) - test_dct = { - diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True), - diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True), - diff.DIFF_LIST: [ - { - diff.DIFF_IDENT: 2, - diff.DIFF_CHANGE: 1, - diff.DIFF_DEL: 0, - diff.DIFF_MOVE: 0, - diff.DIFF_NEW: 0, - diff.DIFF_IS_DIR: True, - diff.DIFF_TARGET: self.DATA_DIR, - diff.DIFF_NEW_FILE: self.DATA_DIR, - diff.DIFF_NEW_CHECKSUM: self.new_checksum, - diff.DIFF_OLD_FILE: self.DATA_DIR, - diff.DIFF_OLD_CHECKSUM: self.old_checksum, - diff.DIFF_SIZE: self.diff_len, - } - ], - } - self.assertEqual(test_dct, result) - - -class TestDiffCmdMessage(TestDiff): - maxDiff = None - - def test(self): - ret = main( - [ - "diff", - self.test_dct[diff.DIFF_A_REF], - self.test_dct[diff.DIFF_B_REF], - ] - ) - self.assertEqual(ret, 0) - - msg1 = "dvc diff from {0} to {1}".format( - self.git.git.rev_parse(self.test_dct[diff.DIFF_A_REF], short=True), - self.git.git.rev_parse(self.test_dct[diff.DIFF_B_REF], short=True), - ) - msg2 = "diff for '{0}'".format( - self.test_dct[diff.DIFF_LIST][0][diff.DIFF_TARGET] - ) - msg3 = "+{0} with md5 {1}".format( - self.test_dct[diff.DIFF_LIST][0][diff.DIFF_TARGET], - self.test_dct[diff.DIFF_LIST][0][diff.DIFF_NEW_CHECKSUM], - ) - msg4 = "added file with size 13 Bytes" - for m in [msg1, msg2, msg3, msg4]: - assert m in self._caplog.text + # main(["diff", "--json"]) + pytest.skip("TODO: define output structure")