Esempio n. 1
0
def test_auto(mocker, yes):
    mocker.patch("conda_env_tracker.gateways.io.Path.mkdir")

    name = "test-auto"
    env = Environment(name=name)
    env_after_pull = Environment(name=name)
    env_after_push = Environment(name=name)

    read_mock = mocker.patch("conda_env_tracker.main.Environment.read",
                             mocker.Mock(return_value=env))
    mocker.patch("conda_env_tracker.main.infer_remote_dir")
    setup_remote_mock = mocker.patch(
        "conda_env_tracker.main.EnvIO.set_remote_dir")
    pull_mock = mocker.patch("conda_env_tracker.main._pull",
                             mocker.Mock(return_value=env_after_pull))
    push_mock = mocker.patch("conda_env_tracker.main._push",
                             mocker.Mock(return_value=env_after_push))

    final_env = sync(name=name, yes=yes)

    read_mock.assert_called()
    setup_remote_mock.assert_not_called()
    pull_mock.assert_called_once_with(env=env, yes=yes)
    push_mock.assert_called_once_with(env=env_after_pull)
    assert final_env == env_after_push
Esempio n. 2
0
def test_pull_no_new_action_in_local(setup_tests):
    overwrite_mock = setup_tests["overwrite_mock"]
    remote_history = setup_tests["remote_history"]

    local_packages = (Package.from_spec("pandas"), )
    local_logs = ["conda create --name pull_testing_environment pandas"]
    local_actions = [
        "conda create --name pull_testing_environment pandas=0.23=py36"
    ]

    local_history = History(
        name=ENV_NAME,
        packages=HistoryPackages.create(local_packages),
        channels=Channels(),
        logs=Logs([log for log in local_logs]),
        actions=Actions(local_actions),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    pull(env=env)

    assert env.history.packages == remote_history.packages
    assert env.history.logs == remote_history.logs
    assert env.history.actions == remote_history.actions
    overwrite_mock.called_once()
Esempio n. 3
0
def test_pull_new_action_in_both(setup_tests, local_packages, local_logs,
                                 local_actions):
    overwrite_mock = setup_tests["overwrite_mock"]
    remote_history = setup_tests["remote_history"]
    packages_mock = setup_tests["get_dependencies"]

    packages = HistoryPackages.create(local_packages["conda"])
    if local_packages.get("pip"):
        packages.update_packages(local_packages["pip"], source="pip")

    local_history = History(
        name=ENV_NAME,
        channels=CHANNELS,
        packages=packages,
        logs=Logs([log for log in local_logs]),
        actions=Actions(local_actions),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    if local_packages.get("pip") is not None:
        packages_mock.configure_mock(
            return_value={
                "conda": {
                    "pandas": Package("pandas", "pandas", "0.23", "py36"),
                    "pytest": Package("pytest", "pytest", "0.1", "py36_3"),
                },
                "pip": {
                    "pylint": Package("pylint", "pylint", "1.11")
                },
            })
    else:
        packages_mock.configure_mock(
            return_value={
                "conda": {
                    "pandas": Package("pandas", "pandas", "0.23", "py36"),
                    "pytest": Package("pytest", "pytest", "0.1", "py36_3"),
                    "pylint": Package("pylint", "pylint", "1.11", "py36"),
                },
                "pip": {},
            })

    pull(env=env)

    expected_packages = remote_history.packages
    if local_packages.get("pip"):
        expected_packages["pip"]["pylint"] = local_history.packages["pip"][
            "pylint"]
    elif "pylint" in local_history.packages["conda"]:
        expected_packages["conda"]["pylint"] = local_history.packages["conda"][
            "pylint"]
    expected_logs = remote_history.logs
    expected_actions = remote_history.actions

    assert env.history.packages == expected_packages
    assert env.history.logs == expected_logs
    assert env.history.actions == expected_actions
    assert env.history.logs[-1] == local_history.logs[-1]
    assert env.history.actions[-1] == local_history.actions[-1]
    overwrite_mock.called_once()
def test_get_packages_with_version_spec(mocker):
    mocker.patch(
        "conda_env_tracker.env.get_dependencies",
        return_value={
            "conda": {
                "pandas": Package("pandas", "pandas", "0.23", "py_36"),
                "numpy": Package("numpy", "numpy", "0.13", "py_36"),
            },
            "pip": {
                "pytest": Package("pytest", "pytest", "4.0")
            },
        },
    )
    local_packages = HistoryPackages.create(
        (Package.from_spec("pandas=0.23"), ))
    local_packages.update_packages(
        packages=(Package.from_spec("pytest==4.0"), ), source="pip")
    local_history = History(
        name=ENV_NAME,
        packages=local_packages,
        channels=Channels("defaults"),
        logs=Logs([]),
        actions=Actions(),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    expected = {
        "conda": [Package("pandas", "pandas=0.23", "0.23", "py_36")],
        "pip": [Package("pytest", "pytest==4.0", "4.0")],
    }
    actual = get_packages(env)
    assert actual == expected
Esempio n. 5
0
def merge_conflicting_changes(env: Environment):
    """Reconciles packages between local and remote"""
    remote_dir = env.local_io.get_remote_dir()
    remote_io = EnvIO(env_directory=remote_dir)
    remote_history = remote_io.get_history()

    local_history = env.history
    update_conda_environment(env_dir=remote_dir)
    if _r_env_needs_updating(local_history=local_history,
                             remote_history=remote_history):
        update_r_environment(name=env.name, env_dir=remote_dir)
    EnvIO.overwrite_local(local_io=env.local_io, remote_io=remote_io)
    new_env = Environment(name=env.name, history=remote_history)
    new_env.validate()
    extra_logs = []
    for log in local_history.logs:
        if log not in set(remote_history.logs):
            extra_logs.append(log)
    for log in extra_logs:
        new_env = _update_from_extra_log(env=new_env,
                                         history=local_history,
                                         log=log)

    new_env.validate()
    new_env.export()

    env.history = new_env.history
    logger.info("Successfully updated the environment.")
    return new_env
Esempio n. 6
0
def test_export(mocker):
    name = "test-export"

    io_mock = mocker.patch("conda_env_tracker.env.EnvIO")
    mocker.patch(
        "conda_env_tracker.env.get_dependencies",
        mocker.Mock(
            return_value={
                "conda": {
                    "python":
                    Package("python", "python=3.7.2=buildstr", "3.7.2",
                            "buildstr")
                },
                "pip": {},
            }),
    )
    mocker.patch("conda_env_tracker.history.get_pip_version",
                 mocker.Mock(return_value="18.1"))

    history = History.parse({
        "name":
        name,
        "channels": ["conda-forge", "main"],
        "packages": {
            "conda": {
                "python": "3.7.2=buildstr"
            }
        },
        "logs": ["conda create --name test python=3.7"],
        "actions": ["conda create --name test python=3.7.2=buildstr"],
        "debug": [{
            "platform": "osx",
            "conda_version": "4.6.1",
            "pip_version": "18.1"
        }],
    })

    env = Environment(name=name, history=history)

    env.export()

    expected = """name: test-export
channels:
- conda-forge
- main
- nodefaults
dependencies:
- python=3.7.2=buildstr
"""

    assert io_mock.mock_calls == [
        mocker.call(env_directory=Path(USER_ENVS_DIR / name)),
        mocker.call().write_history_file(history=history),
        mocker.call().export_packages(
            contents=expected.replace("=buildstr", "")),
        mocker.call().delete_install_r(),
    ]
def setup(mocker, request):
    """Set up for diff function"""
    local_packages = request.param["local_packages"]
    local_logs = request.param["local_logs"]
    local_actions = request.param["local_actions"]
    env_name = request.param["env_name"]

    remote_packages = request.param["remote_packages"]
    remote_logs = request.param["remote_logs"]
    remote_actions = request.param["remote_actions"]

    dependencies = {
        "conda": {
            "pandas": Package("pandas", "pandas", version="0.23.0"),
            "pytest": Package("pytest", "pytest", version="4.0.0"),
        }
    }
    mocker.patch(
        "conda_env_tracker.env.get_dependencies", mocker.Mock(return_value=dependencies)
    )
    history = History.create(
        name=env_name,
        packages=PackageRevision.create(local_packages, dependencies=dependencies),
        channels=Channels(["conda-forge"]),
        logs=Logs([log for log in local_logs]),
        actions=local_actions,
        diff=Diff(),
        debug=Debug(),
    )
    env = Environment(name=env_name, history=history)
    mocker.patch("conda_env_tracker.main.Environment.read", return_value=env)

    mocker.patch(
        "conda_env_tracker.gateways.io.EnvIO.get_remote_dir",
        return_value="~/path/to/remote",
    )
    mocker.patch("pathlib.Path.is_dir", return_value=True)
    history = History.create(
        name=env_name,
        channels=Channels(["conda-forge"]),
        packages=PackageRevision.create(remote_packages, dependencies=dependencies),
        logs=Logs([log for log in remote_logs]),
        actions=remote_actions,
        diff=Diff(),
        debug=Debug(),
    )
    mocker.patch(
        "conda_env_tracker.gateways.io.EnvIO.get_history", return_value=history
    )
    mocker.patch("pathlib.Path.write_text")

    yield env, request.param["push_should_fail"]

    if (USER_ENVS_DIR / env_name).exists():
        shutil.rmtree(USER_ENVS_DIR / env_name)
Esempio n. 8
0
def test_pull_pip(setup_tests):
    new = Package.from_spec("pytest-pylint==0.13.0")
    remote_history = setup_tests["remote_history"]

    remote_history.packages.update_packages(packages=[new], source="pip")
    remote_history.logs.append(f"pip install {new}")
    remote_history.actions.append(f"pip install {new}")

    env = Environment(name="test", history=None)
    final_env = pull(env=env)

    assert final_env.history == remote_history
Esempio n. 9
0
def test_pull_different_actions_with_pip(setup_tests):
    new = Package.from_spec("pytest-pylint==0.13.0")
    remote_history = setup_tests["remote_history"]

    local_history = copy.deepcopy(remote_history)
    env = Environment(name=local_history.name, history=local_history)

    remote_history.packages.update_packages(packages=[new], source="pip")
    remote_history.logs.append(f"pip install {new}")
    remote_history.actions.append(f"pip install {new}")

    final_env = pull(env=env)

    assert final_env.history == remote_history
Esempio n. 10
0
def test_pull_r(setup_r_tests):
    remote_history = setup_r_tests["remote_history"]

    remote_history.packages.update_packages(
        packages=Packages(Package("jsonlite", "install.packages('jsonlite')")),
        source="r",
    )
    remote_history.logs.append(
        f"{R_COMMAND} -e 'install.packages('jsonlite')'")
    remote_history.actions.append(
        f"{R_COMMAND} -e 'install.packages('jsonlite')'")

    env = Environment(name="test", history=None)
    final_env = pull(env=env)

    assert final_env.history == remote_history
Esempio n. 11
0
def test_pull_different_actions_in_both_r(setup_r_tests):
    remote_history = setup_r_tests["remote_history"]
    get_r_dependencies = setup_r_tests["get_r_dependencies"]
    get_r_dependencies.configure_mock(
        **{
            "return_value": {
                "praise": Package("praise", "praise", "1.0.0"),
                "jsonlite": Package("jsonlite", "jsonlite", "1.6"),
            }
        })

    local_history = copy.deepcopy(remote_history)

    local_history.packages.update_packages(packages=Packages(
        Package("praise", 'install.packages("praise")')),
                                           source="r")
    local_history.logs.append(f"{R_COMMAND} -e 'install.packages(\"praise\")'")
    local_history.actions.append(
        f"{R_COMMAND} -e 'install.packages(\"praise\")'")

    env = Environment(name=local_history.name, history=local_history)

    remote_history.packages.update_packages(
        packages=Packages(Package("jsonlite", 'install.packages("jsonlite")')),
        source="r",
    )
    remote_history.logs.append(
        f"{R_COMMAND} -e 'install.packages(\"jsonlite\")'")
    remote_history.actions.append(
        f"{R_COMMAND} -e 'install.packages(\"jsonlite\")'")

    expected_history = copy.deepcopy(remote_history)

    expected_history.packages.update_packages(packages=Packages(
        Package("praise", 'install.packages("praise")')),
                                              source="r")
    expected_history.logs.append(
        f"{R_COMMAND} -e 'install.packages(\"praise\")'")
    expected_history.actions.append(
        f"{R_COMMAND} -e 'install.packages(\"praise\")'")

    final_env = pull(env=env)

    assert final_env.history.packages == expected_history.packages
    assert final_env.history.logs == expected_history.logs
    assert final_env.history.actions == expected_history.actions
Esempio n. 12
0
def test_pull_remote_local_different_create_commands(setup_tests,
                                                     local_packages,
                                                     local_logs,
                                                     local_actions):
    # pylint: disable=unused-argument
    local_history = History(
        name=ENV_NAME,
        channels=CHANNELS,
        packages=HistoryPackages.create(local_packages),
        logs=Logs([log for log in local_logs]),
        actions=Actions(local_actions),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    with pytest.raises(CondaEnvTrackerCreationError):
        pull(env=env)
Esempio n. 13
0
def test_get_packages_r(mocker):
    mocker.patch(
        "conda_env_tracker.env.get_dependencies",
        return_value={
            "conda": {
                "r-base":
                Package("r-base", "r-base", "3.5.1", "h539"),
                "r-devtools":
                Package("r-devtools", "r-devtools", "0.1.0", "r351_0"),
            }
        },
    )
    mocker.patch(
        "conda_env_tracker.env.get_r_dependencies",
        return_value={
            "trelliscopejs": Package("trelliscopejs", "trelliscopejs"
                                     "0.1.0")
        },
    )
    local_packages = HistoryPackages.create(
        Packages(
            (Package.from_spec("r-base"), Package.from_spec("r-devtools"))))
    local_packages.update_packages(
        packages=(Package.from_spec("trelliscopejs"), ), source="r")
    local_history = History(
        name=ENV_NAME,
        packages=local_packages,
        channels=Channels("defaults"),
        logs=Logs([]),
        actions=Actions(),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    expected = {
        "conda": [
            Package("r-base", "r-base", "3.5.1", "h539"),
            Package("r-devtools", "r-devtools", "0.1.0", "r351_0"),
        ],
        "r": [Package("trelliscopejs", "trelliscopejs", "0.1.0")],
    }
    actual = get_packages(env)
    assert actual == expected
Esempio n. 14
0
def test_pull_actions_in_different_order(setup_tests):
    overwrite_mock = setup_tests["overwrite_mock"]
    remote_history = setup_tests["remote_history"]

    new_log = "conda install --name pull_testing_environment pylint"
    new_action = "conda install --name pull_testing_environment pylint=1.11=py36"

    remote_history.packages["pylint"] = "*"
    remote_history.logs.append(new_log)
    remote_history.actions.append(new_action)

    local_packages = (
        Package.from_spec("pandas"),
        Package.from_spec("pylint"),
        Package.from_spec("pytest"),
    )
    local_logs = [
        "conda create --name pull_testing_environment pandas",
        new_log,
        "conda install --name pull_testing_environment pytest",
    ]
    local_actions = [
        "conda create --name pull_testing_environment pandas=0.23=py36",
        new_action,
        "conda install --name pull_testing_environment pytest=0.1=py36_3",
    ]
    local_history = History(
        name=ENV_NAME,
        channels=CHANNELS,
        packages=HistoryPackages.create(local_packages),
        logs=Logs([log for log in local_logs]),
        actions=Actions(local_actions),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    pull(env=env)

    assert env.history.packages == remote_history.packages
    assert env.history.logs == remote_history.logs
    assert env.history.actions == remote_history.actions
    overwrite_mock.called_once()
Esempio n. 15
0
def test_pull_no_new_action_in_remote(setup_tests, local_packages, local_logs,
                                      local_actions):
    overwrite_mock = setup_tests["overwrite_mock"]

    local_history = History(
        name=ENV_NAME,
        packages=HistoryPackages.create(local_packages),
        channels=Channels(),
        logs=Logs([log for log in local_logs]),
        actions=Actions(local_actions),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    pull(env=env)

    assert env.history.packages == local_history.packages
    assert env.history.logs == local_history.logs
    assert env.history.actions == local_history.actions
    overwrite_mock.assert_not_called()
Esempio n. 16
0
def expected_diff(mocker, request):
    """Set up for diff function"""

    packages = request.param["packages"]
    conda_dependencies = request.param["conda_dependencies"]
    local = request.param["local"]
    name = "test-diff"
    mocker.patch("conda_env_tracker.gateways.io.Path.mkdir")
    mocker.patch("conda_env_tracker.gateways.io.Path.write_text")
    dependencies = {"conda": conda_dependencies, "pip": {}}
    mocker.patch("conda_env_tracker.env.get_dependencies",
                 mocker.Mock(return_value=dependencies))
    history = History.create(
        name=name,
        packages=PackageRevision.create(packages=packages,
                                        dependencies=dependencies),
        channels=None,
        logs=None,
        actions=None,
        debug=None,
        diff=None,
    )
    env = Environment(name=name, history=history)
    mocker.patch("conda_env_tracker.main.Environment.read", return_value=env)
    mocker.patch(
        "conda_env_tracker.gateways.io.EnvIO.get_environment",
        return_value={
            "name": "test-diff",
            "channels": ["conda-forge"],
            "dependencies": dependencies,
        },
    )
    mocker.patch(
        "conda_env_tracker.history.history.get_dependencies",
        return_value={"conda": local},
    )
    yield request.param["expected"]
    if (USER_ENVS_DIR / name).exists():
        shutil.rmtree(USER_ENVS_DIR / name)
Esempio n. 17
0
def expected_update(mocker):
    """Set up for update function"""
    packages = Packages.from_specs("pandas")
    channel = "conda-forge"
    create_cmd = "conda install pandas"
    name = "test-update"
    mocker.patch("conda_env_tracker.gateways.io.Path.mkdir")
    mocker.patch("conda_env_tracker.gateways.io.Path.write_text")
    mocker.patch("conda_env_tracker.gateways.io.Path.iterdir")
    mocker.patch("conda_env_tracker.history.debug.get_pip_version",
                 return_value=None)
    dependencies = {
        "conda": {
            "pandas": Package("pandas", "pandas", "0.23", "py36"),
            "pytest": Package("pytest", "pytest", "0.1", "py36"),
        },
        "pip": {},
    }
    mocker.patch("conda_env_tracker.env.get_dependencies",
                 return_value=dependencies)
    mocker.patch("conda_env_tracker.env.EnvIO.export_packages")
    history = History.create(
        name=name,
        channels=Channels([channel]),
        packages=PackageRevision.create(packages, dependencies=dependencies),
        logs=Logs(create_cmd),
        actions=Actions.create(name="test-update",
                               specs=["pandas"],
                               channels=Channels([channel])),
        diff=Diff.create(packages, dependencies=dependencies),
        debug=Debug(),
    )
    mocker.patch(
        "conda_env_tracker.main.Environment.read",
        return_value=Environment(name=name, history=history),
    )
    return history
Esempio n. 18
0
def test_get_packages_conda(mocker):
    mocker.patch(
        "conda_env_tracker.env.get_dependencies",
        return_value={
            "conda": {
                "pandas": Package("pandas", "pandas", "0.23", "py_36"),
                "numpy": Package("numpy", "numpy", "0.13", "py_36"),
            }
        },
    )
    packages = (Package.from_spec("pandas"), )
    local_history = History(
        name=ENV_NAME,
        packages=HistoryPackages.create(packages),
        channels=Channels("defaults"),
        logs=Logs([]),
        actions=Actions(),
        debug=Debug(),
    )
    env = Environment(name=ENV_NAME, history=local_history)

    expected = {"conda": [Package("pandas", "pandas", "0.23", "py_36")]}
    actual = get_packages(env)
    assert actual == expected
Esempio n. 19
0
def pull(env: Environment, yes: bool = False) -> Environment:
    """Pull history from remote to local"""
    remote_dir = env.local_io.get_remote_dir()
    remote_io = EnvIO(env_directory=remote_dir)
    remote_history = remote_io.get_history()

    local_history = env.history

    _check_for_errors(local_history=local_history, remote_history=remote_history)

    if _nothing_to_pull(local_history=local_history, remote_history=remote_history):
        logger.info("Nothing to pull.")
        return env
    if _local_needs_update(local_history=local_history, remote_history=remote_history):
        update(
            env=env,
            remote_dir=remote_dir,
            remote_io=remote_io,
            remote_history=remote_history,
        )
        return env
    if _actions_in_different_order(
        local_history=local_history, remote_history=remote_history
    ):
        if not yes and not prompt_yes_no(
            prompt_msg="Remote environment has same packages but in different order, "
            "Should we overwrite local with remote environment"
        ):
            logger.info("Exiting without updating local environment.")
            return env
        update(
            env=env,
            remote_dir=remote_dir,
            remote_io=remote_io,
            remote_history=remote_history,
        )
        return env
    if not yes and not prompt_yes_no(
        prompt_msg=(
            "Remote and local have different packages, do you want to overwrite "
            "with remote and append local"
        )
    ):
        logger.info("Exiting without updating local environment.")
        return env
    update_conda_environment(env_dir=remote_dir)
    if _r_env_needs_updating(
        local_history=local_history, remote_history=remote_history
    ):
        update_r_environment(name=env.name, env_dir=remote_dir)
    EnvIO.overwrite_local(local_io=env.local_io, remote_io=remote_io)
    new_env = Environment(name=env.name, history=remote_history)
    new_env.validate()
    extra_logs = []
    for log in local_history.logs:
        if log not in set(remote_history.logs):
            extra_logs.append(log)
    for log in extra_logs:
        new_env = _update_from_extra_log(env=new_env, history=local_history, log=log)
    new_env = _update_r_packages(
        env=new_env, local_history=local_history, remote_history=remote_history
    )

    new_env.validate()
    new_env.export()

    env.history = new_env.history
    logger.info("Successfully updated the environment.")
    return new_env
Esempio n. 20
0
def test_pull_different_actions_in_both_remove_multiple_packages_r(
        setup_r_tests, remove_location):
    remote_history = setup_r_tests["remote_history"]
    get_r_dependencies = setup_r_tests["get_r_dependencies"]
    get_r_dependencies.configure_mock(
        **{
            "return_value": {
                "praise": Package("praise", "praise", "1.0.0"),
                "dplyr": Package("dplyr", "dplyr", "0.8.3"),
                "jsonlite": Package("jsonlite", "jsonlite", "1.6"),
            }
        })

    def update_histories(history_1, history_2):
        add_log_1 = (
            f'{R_COMMAND} -e \'install.packages("praise");install.packages("dplyr")\''
        )
        remove_log_1 = f'{R_COMMAND} -e \'remove.packages(c("praise","dplyr"))\''

        history_1.logs.append(add_log_1)
        history_1.logs.append(remove_log_1)
        history_1.actions.append(add_log_1)
        history_1.actions.append(remove_log_1)

        packages_2 = Packages(
            Package("jsonlite", 'install.packages("jsonlite")'))
        log_2 = f"{R_COMMAND} -e 'install.packages(\"jsonlite\")'"
        action_2 = f"{R_COMMAND} -e 'install.packages(\"jsonlite\")'"
        history_2.packages.update_packages(packages=packages_2, source="r")
        history_2.logs.append(log_2)
        history_2.actions.append(action_2)
        return history_1, history_2

    local_history = copy.deepcopy(remote_history)

    if remove_location == "local":
        local_history, remote_history = update_histories(
            local_history, remote_history)
    else:
        remote_history, local_history = update_histories(
            remote_history, local_history)

    env = Environment(name=local_history.name, history=local_history)

    expected_history = copy.deepcopy(remote_history)

    for index, log in enumerate(local_history.logs):
        if log not in remote_history.logs:
            expected_history.logs.append(log)
            expected_history.actions.append(local_history.actions[index])

    expected_history.packages["r"] = {
        "jsonlite": Package("jsonlite", 'install.packages("jsonlite")')
    }

    final_env = pull(env=env)

    final_env.history.debug = []

    if remove_location == "local":
        # If user added and removed packages, then we do not add both logs
        expected_history.logs.pop(2)
        expected_history.actions.pop(2)

    assert final_env.history.packages == expected_history.packages
    assert final_env.history.logs == expected_history.logs
    assert final_env.history.actions == expected_history.actions
Esempio n. 21
0
def test_empty_local_history(setup_tests):
    """Empty local history should pull successfully."""
    env = Environment(name="test", history=None)
    final_env = pull(env=env)
    assert final_env.history == setup_tests["remote_history"]
    setup_tests["overwrite_mock"].assert_called_once()