예제 #1
0
    def check_provide_contents(dirname):
        environ = dict()
        local_state_file = LocalStateFile.load_for_directory(dirname)
        local_state_file.set_service_run_state("myservice", dict(port=42))
        requirement = EnvVarRequirement(RequirementsRegistry(), env_var="FOO")
        status = requirement.check_status(environ, local_state_file, 'default',
                                          UserConfigOverrides())
        context = ProvideContext(environ=environ,
                                 local_state_file=local_state_file,
                                 default_env_spec_name='default',
                                 status=status,
                                 mode=PROVIDE_MODE_DEVELOPMENT,
                                 frontend=NullFrontend())

        def transform_it(state):
            assert 42 == state['port']
            state['port'] = 43
            state['foo'] = 'bar'
            return 1234

        result = context.transform_service_run_state("myservice", transform_it)
        assert 1234 == result
        assert dict(
            port=43,
            foo='bar') == local_state_file.get_service_run_state("myservice")
예제 #2
0
    def do_test(dirname):
        from codecs import open as real_open

        envdir = os.path.join(dirname, spec.name)

        manager = DefaultCondaManager(frontend=NullFrontend())

        counts = dict(calls=0)

        def mock_open(*args, **kwargs):
            counts['calls'] += 1
            if counts['calls'] == 1:
                raise IOError("did not open")
            else:
                return real_open(*args, **kwargs)

        monkeypatch.setattr('codecs.open', mock_open)

        # this should NOT throw but also should not write the
        # timestamp file (we ignore errors)
        filename = manager._timestamp_file(envdir, spec)
        assert filename.startswith(envdir)
        assert not os.path.exists(filename)
        manager._write_timestamp_file(envdir, spec)
        assert not os.path.exists(filename)
        # the second time we really wsrite it (this is to prove we
        # are looking at the right filename)
        manager._write_timestamp_file(envdir, spec)
        assert os.path.exists(filename)

        # check on the file contents
        with real_open(filename, 'r', encoding='utf-8') as f:
            content = json.loads(f.read())
            assert dict(anaconda_project_version=version) == content
예제 #3
0
    def check(dirname):
        prefix = os.path.join(dirname, "myenv")
        os.makedirs(os.path.join(prefix, 'conda-meta'))

        def mock_installed(prefix):
            return {'bokeh': ('bokeh', '0.12.4', '1')}

        monkeypatch.setattr('anaconda_project.internal.conda_api.installed',
                            mock_installed)

        spec_with_matching_bokeh = EnvSpec(name='myenv',
                                           conda_packages=['bokeh=0.12.4=1'],
                                           pip_packages=[],
                                           channels=[])
        spec_with_more_vague_bokeh = EnvSpec(name='myenv',
                                             conda_packages=['bokeh=0.12'],
                                             pip_packages=[],
                                             channels=[])
        spec_with_unspecified_bokeh = EnvSpec(name='myenv',
                                              conda_packages=['bokeh'],
                                              pip_packages=[],
                                              channels=[])
        spec_with_wrong_version_bokeh = EnvSpec(
            name='myenv',
            conda_packages=['bokeh=0.12.3'],
            pip_packages=[],
            channels=[])
        spec_with_wrong_build_bokeh = EnvSpec(
            name='myenv',
            conda_packages=['bokeh=0.12.4=0'],
            pip_packages=[],
            channels=[])

        manager = DefaultCondaManager(frontend=NullFrontend())

        deviations = manager.find_environment_deviations(
            prefix, spec_with_matching_bokeh)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        deviations = manager.find_environment_deviations(
            prefix, spec_with_more_vague_bokeh)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        deviations = manager.find_environment_deviations(
            prefix, spec_with_unspecified_bokeh)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        deviations = manager.find_environment_deviations(
            prefix, spec_with_wrong_version_bokeh)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ('bokeh', )

        deviations = manager.find_environment_deviations(
            prefix, spec_with_wrong_build_bokeh)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ('bokeh', )
예제 #4
0
def test_resolve_dependencies_with_actual_conda():
    manager = DefaultCondaManager(frontend=NullFrontend())

    lock_set = manager.resolve_dependencies(['bokeh'], channels=(), platforms=(conda_api.current_platform(), ))
    specs = lock_set.package_specs_for_current_platform
    pprint(specs)
    names = [conda_api.parse_spec(spec).name for spec in specs]
    assert 'bokeh' in names
    assert len(specs) > 5  # 5 is an arbitrary number of deps that surely bokeh has
예제 #5
0
def test_resolve_dependencies_with_conda_api_mock(monkeypatch):
    def mock_resolve_dependencies(pkgs, platform, channels):
        return [('bokeh', '0.12.4', '0'), ('thing', '1.0', '1')]

    monkeypatch.setattr('anaconda_project.internal.conda_api.resolve_dependencies', mock_resolve_dependencies)

    manager = DefaultCondaManager(frontend=NullFrontend())

    lock_set = manager.resolve_dependencies(['bokeh'], channels=(), platforms=(conda_api.current_platform(), ))
    assert lock_set.package_specs_for_current_platform == ('bokeh=0.12.4=0', 'thing=1.0=1')
예제 #6
0
def test_resolve_dependencies_with_conda_api_mock_raises_error(monkeypatch):
    def mock_resolve_dependencies(pkgs, platform, channels):
        raise conda_api.CondaError("nope")

    monkeypatch.setattr('anaconda_project.internal.conda_api.resolve_dependencies', mock_resolve_dependencies)

    manager = DefaultCondaManager(frontend=NullFrontend())

    with pytest.raises(CondaManagerError) as excinfo:
        manager.resolve_dependencies(['bokeh'], channels=(), platforms=(conda_api.current_platform(), ))

    assert 'Error resolving for' in str(excinfo.value)
예제 #7
0
 def check_provide_contents(dirname):
     environ = dict(foo='bar')
     local_state_file = LocalStateFile.load_for_directory(dirname)
     requirement = EnvVarRequirement(RequirementsRegistry(), env_var="FOO")
     status = requirement.check_status(environ, local_state_file, 'default', UserConfigOverrides())
     context = ProvideContext(environ=environ,
                              local_state_file=local_state_file,
                              default_env_spec_name='default',
                              status=status,
                              mode=PROVIDE_MODE_DEVELOPMENT,
                              frontend=NullFrontend())
     assert dict(foo='bar') == context.environ
     assert context.status is status
예제 #8
0
    def do_test(dirname):
        envdir = os.path.join(dirname, spec.name)

        manager = DefaultCondaManager(frontend=NullFrontend())

        deviations = manager.find_environment_deviations(envdir, spec)

        error = "Env spec 'myenv' does not have the current platform %s in the lock file" % conda_api.current_platform()
        assert error == deviations.summary

        with pytest.raises(CondaManagerError) as excinfo:
            manager.fix_environment_deviations(envdir, spec, deviations=deviations)
        assert str(excinfo.value).startswith("Unable to update environment at ")
예제 #9
0
 def check_provide_contents(dirname):
     environ = dict()
     local_state_file = LocalStateFile.load_for_directory(dirname)
     requirement = EnvVarRequirement(RequirementsRegistry(), env_var="FOO")
     status = requirement.check_status(environ, local_state_file, 'default', UserConfigOverrides())
     context = ProvideContext(environ=environ,
                              local_state_file=local_state_file,
                              default_env_spec_name='default',
                              status=status,
                              mode=PROVIDE_MODE_DEVELOPMENT,
                              frontend=NullFrontend())
     with pytest.raises(IOError) as excinfo:
         context.ensure_service_directory("foo")
     assert "this is not EEXIST" in repr(excinfo.value)
예제 #10
0
    def check_env_var_provider(dirname):
        provider = EnvVarProvider()
        requirement = _load_env_var_requirement(dirname, "FOO")
        local_state_file = LocalStateFile.load_for_directory(dirname)
        status = requirement.check_status(dict(), local_state_file, 'default', UserConfigOverrides())
        context = ProvideContext(environ=dict(),
                                 local_state_file=local_state_file,
                                 default_env_spec_name='default',
                                 status=status,
                                 mode=PROVIDE_MODE_DEVELOPMENT,
                                 frontend=NullFrontend())

        provider.provide(requirement, context=context)
        assert 'FOO' not in context.environ
예제 #11
0
 def check_env_var_provider(dirname):
     provider = EnvVarProvider()
     requirement = _load_env_var_requirement(dirname, "FOO")
     local_state_file = LocalStateFile.load_for_directory(dirname)
     environ = dict(FOO='from_environ')
     status = requirement.check_status(environ, local_state_file, 'default', UserConfigOverrides())
     assert dict(source='environ', value='from_environ') == status.analysis.config
     context = ProvideContext(environ=environ,
                              local_state_file=local_state_file,
                              default_env_spec_name='default',
                              status=status,
                              mode=PROVIDE_MODE_DEVELOPMENT,
                              frontend=NullFrontend())
     result = provider.provide(requirement, context=context)
     assert [] == result.errors
     assert 'FOO' in context.environ
     assert 'from_environ' == context.environ['FOO']
예제 #12
0
 def check_env_var_provider(dirname):
     provider = EnvVarProvider()
     requirement = _load_env_var_requirement(dirname, "FOO_SECRET")
     assert requirement.encrypted
     assert dict(default='from_default') == requirement.options
     local_state_file = LocalStateFile.load_for_directory(dirname)
     environ = dict()
     status = requirement.check_status(environ, local_state_file, 'default', UserConfigOverrides())
     context = ProvideContext(environ=environ,
                              local_state_file=local_state_file,
                              default_env_spec_name='default',
                              status=status,
                              mode=PROVIDE_MODE_DEVELOPMENT,
                              frontend=NullFrontend())
     result = provider.provide(requirement, context=context)
     assert [] == result.errors
     assert 'FOO_SECRET' in context.environ
     assert 'from_default' == context.environ['FOO_SECRET']
예제 #13
0
 def check_env_var_provider(dirname):
     provider = EnvVarProvider()
     requirement = _load_env_var_requirement(dirname, "FOO_PASSWORD")
     assert requirement.encrypted
     local_state_file = LocalStateFile.load_for_directory(dirname)
     # set in environ to be sure we override it with local state
     environ = dict(FOO_PASSWORD='******')
     status = requirement.check_status(environ, local_state_file, 'default', UserConfigOverrides())
     assert dict(value="from_local_state", source="variables") == status.analysis.config
     context = ProvideContext(environ=environ,
                              local_state_file=local_state_file,
                              default_env_spec_name='default',
                              status=status,
                              mode=PROVIDE_MODE_DEVELOPMENT,
                              frontend=NullFrontend())
     result = provider.provide(requirement, context=context)
     assert [] == result.errors
     assert 'FOO_PASSWORD' in context.environ
     assert 'from_local_state' == context.environ['FOO_PASSWORD']
예제 #14
0
    def check_provide_contents(dirname):
        environ = dict()
        local_state_file = LocalStateFile.load_for_directory(dirname)
        requirement = EnvVarRequirement(RequirementsRegistry(), env_var="FOO")
        status = requirement.check_status(environ, local_state_file, 'default', UserConfigOverrides())
        context = ProvideContext(environ=environ,
                                 local_state_file=local_state_file,
                                 default_env_spec_name='default',
                                 status=status,
                                 mode=PROVIDE_MODE_DEVELOPMENT,
                                 frontend=NullFrontend())
        workpath = context.ensure_service_directory("foo")
        assert os.path.isdir(workpath)
        assert workpath.endswith("foo")
        parent = os.path.dirname(workpath)
        assert parent.endswith("services")
        parent = os.path.dirname(parent)
        assert parent == dirname

        # be sure we can create if it already exists
        workpath2 = context.ensure_service_directory("foo")
        assert os.path.isdir(workpath2)
        assert workpath == workpath2
예제 #15
0
    def do_test(dirname):
        envdir = os.path.join(dirname, spec.name)

        manager = DefaultCondaManager(frontend=NullFrontend())

        assert not os.path.isdir(envdir)
        assert not os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert not os.path.exists(os.path.join(envdir, FLAKE8_BINARY))
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)

        assert deviations.missing_packages == ('ipython', )
        assert deviations.missing_pip_packages == ('pyinstrument', )

        # with create=False, we won't create the env
        with pytest.raises(CondaManagerError) as excinfo:
            manager.fix_environment_deviations(envdir, spec, deviations, create=False)
            assert 'does not exist' in str(excinfo.value)

        assert not os.path.isdir(envdir)

        # now create the env
        manager.fix_environment_deviations(envdir, spec, deviations)

        assert os.path.isdir(envdir)
        assert os.path.isdir(os.path.join(envdir, "conda-meta"))
        assert os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert os.path.exists(os.path.join(envdir, PYINSTRUMENT_BINARY))

        assert manager._timestamp_file_up_to_date(envdir, spec)
        assert not manager._timestamp_file_up_to_date(envdir, spec_with_phony_pip_package)

        # test bad pip package throws error
        deviations = manager.find_environment_deviations(envdir, spec_with_phony_pip_package)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()
        assert deviations.missing_pip_packages == ('nope_not_a_thing', )

        with pytest.raises(CondaManagerError) as excinfo:
            manager.fix_environment_deviations(envdir, spec_with_phony_pip_package, deviations)
        assert 'Failed to install missing pip packages' in str(excinfo.value)
        assert not manager._timestamp_file_up_to_date(envdir, spec_with_phony_pip_package)

        # test bad url package throws error
        deviations = manager.find_environment_deviations(envdir, spec_with_bad_url_pip_package)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()
        assert deviations.missing_pip_packages == ('phony', )

        with pytest.raises(CondaManagerError) as excinfo:
            manager.fix_environment_deviations(envdir, spec_with_bad_url_pip_package, deviations)
        assert 'Failed to install missing pip packages' in str(excinfo.value)
        assert not manager._timestamp_file_up_to_date(envdir, spec_with_bad_url_pip_package)

        # test we notice wrong ipython version AND missing bokeh
        deviations = manager.find_environment_deviations(envdir, spec_with_bokeh_and_old_ipython)

        assert deviations.missing_packages == ('bokeh', )
        assert deviations.wrong_version_packages == ('ipython', )

        # test we notice only missing bokeh
        deviations = manager.find_environment_deviations(envdir, spec_with_bokeh)

        assert deviations.missing_packages == ('bokeh', )
        assert deviations.wrong_version_packages == ()

        # test we notice wrong ipython version and can downgrade
        deviations = manager.find_environment_deviations(envdir, spec_with_old_ipython)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ('ipython', )

        manager.fix_environment_deviations(envdir, spec_with_old_ipython, deviations)

        assert manager._timestamp_file_up_to_date(envdir, spec_with_old_ipython)

        deviations = manager.find_environment_deviations(envdir, spec_with_old_ipython)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        # update timestamp; this doesn't re-upgrade because `spec` doesn't
        # specify an ipython version
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        manager.fix_environment_deviations(envdir, spec, deviations)
        assert manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        # test that we can remove a package
        assert manager._timestamp_file_up_to_date(envdir, spec)
        time.sleep(1)  # removal is fast enough to break our timestamp resolution
        manager.remove_packages(prefix=envdir, packages=['ipython'])
        assert not os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        # test for error removing
        with pytest.raises(CondaManagerError) as excinfo:
            manager.remove_packages(prefix=envdir, packages=['ipython'])
        # different versions of conda word this differently
        message = str(excinfo.value)
        valid_strings = ('no packages found to remove', 'Package not found', "named 'ipython' found to remove",
                         "is missing from the environment")
        assert any(s in message for s in valid_strings)
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        # test failure to exec pip
        def mock_call_pip(*args, **kwargs):
            raise pip_api.PipError("pip fail")

        monkeypatch.setattr('anaconda_project.internal.pip_api._call_pip', mock_call_pip)

        with pytest.raises(CondaManagerError) as excinfo:
            deviations = manager.find_environment_deviations(envdir, spec)
        assert 'pip failed while listing' in str(excinfo.value)
예제 #16
0
    def do_test(dirname):
        envdir = os.path.join(dirname, spec.name)

        manager = DefaultCondaManager(frontend=NullFrontend())

        def print_timestamps(when):
            newest_in_prefix = 0
            for d in manager._timestamp_comparison_directories(envdir):
                try:
                    t = os.path.getmtime(d)
                except Exception:
                    t = 0
                if t > newest_in_prefix:
                    newest_in_prefix = t
            timestamp_fname = manager._timestamp_file(envdir, spec)
            try:
                timestamp_file = os.path.getmtime(timestamp_fname)
            except Exception:
                timestamp_file = 0
            print("%s: timestamp file %d prefix %d diff %g" %
                  (when, timestamp_file, newest_in_prefix,
                   newest_in_prefix - timestamp_file))

        print_timestamps("before env creation")

        assert not os.path.isdir(envdir)
        assert not os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert not os.path.exists(os.path.join(envdir, PYINSTRUMENT_BINARY))
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)

        assert set(deviations.missing_packages) == {'python', 'ipython'}
        assert deviations.missing_pip_packages == ('pyinstrument', )
        assert not deviations.ok

        manager.fix_environment_deviations(envdir, spec, deviations)

        print_timestamps("after fixing deviations")

        assert os.path.isdir(envdir)
        assert os.path.isdir(os.path.join(envdir, "conda-meta"))
        assert os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert os.path.exists(os.path.join(envdir, PYINSTRUMENT_BINARY))

        assert manager._timestamp_file_up_to_date(envdir, spec)

        called = []
        from anaconda_project.internal.pip_api import installed as real_pip_installed
        from anaconda_project.internal.conda_api import installed as real_conda_installed

        def traced_pip_installed(*args, **kwargs):
            called.append(("pip_api.installed", args, kwargs))
            return real_pip_installed(*args, **kwargs)

        monkeypatch.setattr('anaconda_project.internal.pip_api.installed',
                            traced_pip_installed)

        def trace_conda_installed(*args, **kwargs):
            called.append(("conda_api.installed", args, kwargs))
            return real_conda_installed(*args, **kwargs)

        monkeypatch.setattr('anaconda_project.internal.conda_api.installed',
                            trace_conda_installed)

        deviations = manager.find_environment_deviations(envdir, spec)

        assert [] == called

        assert deviations.missing_packages == ()
        assert deviations.missing_pip_packages == ()
        assert deviations.ok

        assert manager._timestamp_file_up_to_date(envdir, spec)

        # now modify conda-meta and check that we DO call the package managers
        time.sleep(1.1)  # be sure we are in a new second
        conda_meta_dir = os.path.join(envdir, "conda-meta")
        print("conda-meta original timestamp: %d" %
              os.path.getmtime(conda_meta_dir))
        inside_conda_meta = os.path.join(conda_meta_dir, "thing.txt")
        with codecs.open(inside_conda_meta, 'w', encoding='utf-8') as f:
            f.write(u"This file should change the mtime on conda-meta\n")
        print("file inside conda-meta %d and conda-meta itself %d" %
              (os.path.getmtime(inside_conda_meta),
               os.path.getmtime(conda_meta_dir)))
        os.remove(inside_conda_meta)

        print_timestamps("after touching conda-meta")

        assert not manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)

        assert len(called) == 2

        assert deviations.missing_packages == ()
        assert deviations.missing_pip_packages == ()
        # deviations should not be ok (due to timestamp)
        assert not deviations.ok

        assert not manager._timestamp_file_up_to_date(envdir, spec)

        # we want to be sure we update the timestamp file even though
        # there wasn't any actual work to do
        manager.fix_environment_deviations(envdir, spec, deviations)

        print_timestamps("after fixing deviations 2")

        assert manager._timestamp_file_up_to_date(envdir, spec)
예제 #17
0
    def do_test(dirname):
        from codecs import open as real_open

        envdir = os.path.join(dirname, spec.name)

        manager = DefaultCondaManager(frontend=NullFrontend())

        is_readonly = dict(readonly=False)

        def mock_open(*args, **kwargs):
            if is_readonly['readonly']:
                raise IOError("did not open")
            return real_open(*args, **kwargs)

        monkeypatch.setattr('codecs.open', mock_open)

        assert not os.path.isdir(envdir)
        assert not os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert not os.path.exists(os.path.join(envdir, FLAKE8_BINARY))
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)

        assert set(deviations.missing_packages) == {'python', 'ipython'}
        assert deviations.missing_pip_packages == ('pyinstrument', )

        # with create=False, we won't create the env
        with pytest.raises(CondaManagerError) as excinfo:
            manager.fix_environment_deviations(envdir,
                                               spec,
                                               deviations,
                                               create=False)
            assert 'does not exist' in str(excinfo.value)

        assert not os.path.isdir(envdir)

        # now create the env
        manager.fix_environment_deviations(envdir, spec, deviations)

        assert os.path.isdir(envdir)
        assert os.path.isdir(os.path.join(envdir, "conda-meta"))
        assert os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert os.path.exists(os.path.join(envdir, PYINSTRUMENT_BINARY))

        assert manager._timestamp_file_up_to_date(envdir, spec)
        assert not manager._timestamp_file_up_to_date(
            envdir, spec_with_phony_pip_package)

        # test bad pip package throws error
        deviations = manager.find_environment_deviations(
            envdir, spec_with_phony_pip_package)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()
        assert deviations.missing_pip_packages == ('nope_not_a_thing', )

        with pytest.raises(CondaManagerError) as excinfo:
            manager.fix_environment_deviations(envdir,
                                               spec_with_phony_pip_package,
                                               deviations)
        assert 'Failed to install missing pip packages' in str(excinfo.value)
        assert not manager._timestamp_file_up_to_date(
            envdir, spec_with_phony_pip_package)

        # test bad url package throws error
        deviations = manager.find_environment_deviations(
            envdir, spec_with_bad_url_pip_package)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()
        assert deviations.missing_pip_packages == ('phony', )

        with pytest.raises(CondaManagerError) as excinfo:
            manager.fix_environment_deviations(envdir,
                                               spec_with_bad_url_pip_package,
                                               deviations)
        assert 'Failed to install missing pip packages' in str(excinfo.value)
        assert not manager._timestamp_file_up_to_date(
            envdir, spec_with_bad_url_pip_package)

        # test we notice wrong ipython version AND missing bokeh AND readonly environment
        is_readonly['readonly'] = True
        deviations = manager.find_environment_deviations(
            envdir, spec_with_bokeh_and_old_ipython)

        assert deviations.missing_packages == ('bokeh', )
        assert deviations.wrong_version_packages == ('ipython', )
        assert deviations.unfixable
        is_readonly['readonly'] = False

        # test we notice only missing bokeh
        deviations = manager.find_environment_deviations(
            envdir, spec_with_bokeh)

        assert deviations.missing_packages == ('bokeh', )
        assert deviations.wrong_version_packages == ()
        assert not deviations.unfixable

        # test we notice wrong ipython version and can downgrade
        deviations = manager.find_environment_deviations(
            envdir, spec_with_old_ipython)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ('ipython', )
        assert not deviations.unfixable

        manager.fix_environment_deviations(envdir, spec_with_old_ipython,
                                           deviations)

        assert manager._timestamp_file_up_to_date(envdir,
                                                  spec_with_old_ipython)

        deviations = manager.find_environment_deviations(
            envdir, spec_with_old_ipython)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        # update timestamp; this doesn't re-upgrade because `spec` doesn't
        # specify an ipython version
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)

        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        # fix_environment_deviations should be a no-op on readonly envs
        # with no deviations, in particular the time stamp file should
        # not be changed and therefore not be up to date
        is_readonly['readonly'] = True
        manager.fix_environment_deviations(envdir, spec, deviations)
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        # when the environment is readwrite, the timestamp file should
        # be updated
        is_readonly['readonly'] = False
        manager.fix_environment_deviations(envdir, spec, deviations)
        assert manager._timestamp_file_up_to_date(envdir, spec)

        deviations = manager.find_environment_deviations(envdir, spec)
        assert deviations.missing_packages == ()
        assert deviations.wrong_version_packages == ()

        # test that we can remove a package
        assert manager._timestamp_file_up_to_date(envdir, spec)
        time.sleep(
            1)  # removal is fast enough to break our timestamp resolution
        manager.remove_packages(prefix=envdir, packages=['ipython'])
        assert not os.path.exists(os.path.join(envdir, IPYTHON_BINARY))
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        # test for error removing
        with pytest.raises(CondaManagerError) as excinfo:
            manager.remove_packages(prefix=envdir, packages=['ipython'])
        # different versions of conda word this differently
        message = str(excinfo.value)
        valid_strings = ('no packages found to remove', 'Package not found',
                         "named 'ipython' found to remove",
                         'PackagesNotFoundError:',
                         "is missing from the environment")
        assert any(s in message for s in valid_strings)
        assert not manager._timestamp_file_up_to_date(envdir, spec)

        # test failure to exec pip
        def mock_call_pip(*args, **kwargs):
            raise pip_api.PipError("pip fail")

        monkeypatch.setattr('anaconda_project.internal.pip_api._call_pip',
                            mock_call_pip)

        with pytest.raises(CondaManagerError) as excinfo:
            deviations = manager.find_environment_deviations(envdir, spec)
        assert 'pip failed while listing' in str(excinfo.value)