<reponame>mrstegeman/dvc
import logging

import pytest
from funcy import first

from dvc.exceptions import DvcException
from dvc.repo.experiments import Experiments, MultipleBranchError
from dvc.repo.experiments.base import EXEC_APPLY, EXEC_CHECKPOINT


@pytest.mark.parametrize("workspace", [True, False])
def test_new_checkpoint(
    tmp_dir, scm, dvc, checkpoint_stage, mocker, workspace
):
    new_mock = mocker.spy(dvc.experiments, "new")
    results = dvc.experiments.run(
        checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
    )
    exp = first(results)

    new_mock.assert_called_once()
    for rev in dvc.brancher([exp]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "5"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    if workspace:
        assert scm.get_ref(EXEC_APPLY) == exp
    assert scm.get_ref(EXEC_CHECKPOINT) == exp
    if workspace:
        assert (tmp_dir / "foo").read_text().strip() == "5"
        assert (tmp_dir / "metrics.yaml").read_text().strip() == "foo: 2"


@pytest.mark.parametrize(
    "checkpoint_resume, workspace",
    [
        (Experiments.LAST_CHECKPOINT, True),
        (Experiments.LAST_CHECKPOINT, False),
        ("foo", True),
        ("foo", False),
    ],
)
def test_resume_checkpoint(
    tmp_dir, scm, dvc, checkpoint_stage, checkpoint_resume, workspace
):
    with pytest.raises(DvcException):
        dvc.experiments.run(
            checkpoint_stage=checkpoint_stage.addressing,
            checkpoint_resume=checkpoint_resume,
            tmp_dir=not workspace,
        )

    results = dvc.experiments.run(
        checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
    )

    with pytest.raises(DvcException):
        dvc.experiments.run(
            checkpoint_stage.addressing,
            checkpoint_resume="abc1234",
            tmp_dir=not workspace,
        )

    if checkpoint_resume != Experiments.LAST_CHECKPOINT:
        checkpoint_resume = first(results)

    if not workspace:
        dvc.experiments.apply(first(results))
    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        checkpoint_resume=checkpoint_resume,
        tmp_dir=not workspace,
    )
    exp = first(results)

    for rev in dvc.brancher([exp]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "10"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    if workspace:
        assert scm.get_ref(EXEC_APPLY) == exp
    assert scm.get_ref(EXEC_CHECKPOINT) == exp


@pytest.mark.parametrize("workspace", [True, False])
def test_reset_checkpoint(
    tmp_dir, scm, dvc, checkpoint_stage, caplog, workspace
):
    from dvc.repo.experiments.base import CheckpointExistsError

    dvc.experiments.run(
        checkpoint_stage.addressing, name="foo", tmp_dir=not workspace,
    )

    if workspace:
        scm.reset(hard=True)
        scm.gitpython.repo.git.clean(force=True)

    if workspace:
        with caplog.at_level(logging.ERROR):
            dvc.experiments.run(
                checkpoint_stage.addressing,
                name="foo",
                params=["foo=2"],
                tmp_dir=not workspace,
            )
        assert "checkpoint experiment conflicts with existing" in caplog.text

        scm.reset(hard=True)
        scm.gitpython.repo.git.clean(force=True)
    else:
        with pytest.raises(CheckpointExistsError):
            dvc.experiments.run(
                checkpoint_stage.addressing,
                name="foo",
                params=["foo=2"],
                tmp_dir=not workspace,
            )

    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        params=["foo=2"],
        name="foo",
        force=True,
        tmp_dir=not workspace,
    )
    exp = first(results)

    for rev in dvc.brancher([exp]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "5"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    if workspace:
        assert scm.get_ref(EXEC_APPLY) == exp
    assert scm.get_ref(EXEC_CHECKPOINT) == exp


@pytest.mark.parametrize("workspace", [True, False])
def test_resume_branch(tmp_dir, scm, dvc, checkpoint_stage, workspace):
    results = dvc.experiments.run(
        checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
    )
    branch_rev = first(results)
    if not workspace:
        dvc.experiments.apply(branch_rev)

    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        checkpoint_resume=branch_rev,
        tmp_dir=not workspace,
    )
    checkpoint_a = first(results)
    if not workspace:
        dvc.experiments.apply(checkpoint_a, force=True)

    with pytest.raises(DvcException):
        results = dvc.experiments.run(
            checkpoint_stage.addressing,
            checkpoint_resume=branch_rev,
            params=["foo=100"],
            tmp_dir=not workspace,
        )

    dvc.experiments.apply(branch_rev, force=True)
    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        checkpoint_resume=branch_rev,
        params=["foo=100"],
        tmp_dir=not workspace,
    )
    checkpoint_b = first(results)

    for rev in dvc.brancher([checkpoint_a]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "10"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    for rev in dvc.brancher([checkpoint_b]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "10"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 100"

    with pytest.raises(MultipleBranchError):
        dvc.experiments.get_branch_by_rev(branch_rev)

    assert branch_rev == dvc.experiments.scm.gitpython.repo.git.merge_base(
        checkpoint_a, checkpoint_b
    )

import logging

import pytest
from funcy import first

from dvc.exceptions import DvcException
from dvc.repo.experiments import Experiments, MultipleBranchError
from dvc.repo.experiments.base import EXEC_APPLY, EXEC_CHECKPOINT


@pytest.mark.parametrize("workspace", [True, False])
def test_new_checkpoint(
    tmp_dir, scm, dvc, checkpoint_stage, mocker, workspace
):
    new_mock = mocker.spy(dvc.experiments, "new")
    results = dvc.experiments.run(
        checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
    )
    exp = first(results)

    new_mock.assert_called_once()
    for rev in dvc.brancher([exp]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "5"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    if workspace:
        assert scm.get_ref(EXEC_APPLY) == exp
    assert scm.get_ref(EXEC_CHECKPOINT) == exp
    if workspace:
        assert (tmp_dir / "foo").read_text().strip() == "5"
        assert (tmp_dir / "metrics.yaml").read_text().strip() == "foo: 2"


@pytest.mark.parametrize(
    "checkpoint_resume, workspace",
    [
        (Experiments.LAST_CHECKPOINT, True),
        (Experiments.LAST_CHECKPOINT, False),
        ("foo", True),
        ("foo", False),
    ],
)
def test_resume_checkpoint(
    tmp_dir, scm, dvc, checkpoint_stage, checkpoint_resume, workspace
):
    with pytest.raises(DvcException):
        dvc.experiments.run(
            checkpoint_stage=checkpoint_stage.addressing,
            checkpoint_resume=checkpoint_resume,
            tmp_dir=not workspace,
        )

    results = dvc.experiments.run(
        checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
    )

    with pytest.raises(DvcException):
        dvc.experiments.run(
            checkpoint_stage.addressing,
            checkpoint_resume="abc1234",
            tmp_dir=not workspace,
        )

    if checkpoint_resume != Experiments.LAST_CHECKPOINT:
        checkpoint_resume = first(results)

    if not workspace:
        dvc.experiments.apply(first(results))
    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        checkpoint_resume=checkpoint_resume,
        tmp_dir=not workspace,
    )
    exp = first(results)

    for rev in dvc.brancher([exp]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "10"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    if workspace:
        assert scm.get_ref(EXEC_APPLY) == exp
    assert scm.get_ref(EXEC_CHECKPOINT) == exp


@pytest.mark.parametrize("workspace", [True, False])
def test_reset_checkpoint(
    tmp_dir, scm, dvc, checkpoint_stage, caplog, workspace
):
    from dvc.repo.experiments.base import CheckpointExistsError

    dvc.experiments.run(
        checkpoint_stage.addressing, name="foo", tmp_dir=not workspace,
    )

    if workspace:
        scm.reset(hard=True)
        scm.gitpython.repo.git.clean(force=True)

    if workspace:
        with caplog.at_level(logging.ERROR):
            dvc.experiments.run(
                checkpoint_stage.addressing,
                name="foo",
                params=["foo=2"],
                tmp_dir=not workspace,
            )
        assert "checkpoint experiment conflicts with existing" in caplog.text

        scm.reset(hard=True)
        scm.gitpython.repo.git.clean(force=True)
    else:
        with pytest.raises(CheckpointExistsError):
            dvc.experiments.run(
                checkpoint_stage.addressing,
                name="foo",
                params=["foo=2"],
                tmp_dir=not workspace,
            )

    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        params=["foo=2"],
        name="foo",
        force=True,
        tmp_dir=not workspace,
    )
    exp = first(results)

    for rev in dvc.brancher([exp]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "5"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    if workspace:
        assert scm.get_ref(EXEC_APPLY) == exp
    assert scm.get_ref(EXEC_CHECKPOINT) == exp


@pytest.mark.parametrize("workspace", [True, False])
def test_resume_branch(tmp_dir, scm, dvc, checkpoint_stage, workspace):
    results = dvc.experiments.run(
        checkpoint_stage.addressing, params=["foo=2"], tmp_dir=not workspace
    )
    branch_rev = first(results)
    if not workspace:
        dvc.experiments.apply(branch_rev)

    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        checkpoint_resume=branch_rev,
        tmp_dir=not workspace,
    )
    checkpoint_a = first(results)
    if not workspace:
        dvc.experiments.apply(checkpoint_a, force=True)

    with pytest.raises(DvcException):
        results = dvc.experiments.run(
            checkpoint_stage.addressing,
            checkpoint_resume=branch_rev,
            params=["foo=100"],
            tmp_dir=not workspace,
        )

    dvc.experiments.apply(branch_rev, force=True)
    results = dvc.experiments.run(
        checkpoint_stage.addressing,
        checkpoint_resume=branch_rev,
        params=["foo=100"],
        tmp_dir=not workspace,
    )
    checkpoint_b = first(results)

    for rev in dvc.brancher([checkpoint_a]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "10"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 2"

    for rev in dvc.brancher([checkpoint_b]):
        if rev == "workspace":
            continue
        tree = dvc.repo_tree
        with tree.open(tmp_dir / "foo") as fobj:
            assert fobj.read().strip() == "10"
        with tree.open(tmp_dir / "metrics.yaml") as fobj:
            assert fobj.read().strip() == "foo: 100"

    with pytest.raises(MultipleBranchError):
        dvc.experiments.get_branch_by_rev(branch_rev)

    assert branch_rev == dvc.experiments.scm.gitpython.repo.git.merge_base(
        checkpoint_a, checkpoint_b
    )