def test_fail_to_prepare_local_redis_server_scope_system(monkeypatch, capsys): _monkeypatch_can_connect_to_socket_always_fails(monkeypatch) def check_no_autostart(dirname): project = project_no_dedicated_env(dirname) result = _prepare_printing_errors(project, environ=minimal_environ()) assert not result with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """, DEFAULT_LOCAL_STATE_FILENAME: """ service_options: REDIS_URL: scope: system """}, check_no_autostart) out, err = capsys.readouterr() assert out == "" assert err == ( "Could not connect to system default Redis.\n" + "missing requirement to run this project: A running Redis server, located by a redis: URL set as REDIS_URL.\n" + " Environment variable REDIS_URL is not set.\n")
def test_env_var_provider_with_unencrypted_default_value_in_project_file_for_encrypted_requirement(): # the idea here is that if you want to put an unencrypted # password in the file, we aren't going to be annoying and # stop you. 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) result = provider.provide(requirement, context=context) assert [] == result.errors assert 'FOO_SECRET' in context.environ assert 'from_default' == context.environ['FOO_SECRET'] with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: FOO_SECRET: default: from_default """}, check_env_var_provider)
def test_redis_server_configure_custom_port_range(monkeypatch, capsys): can_connect_args_list = _monkeypatch_can_connect_to_socket_always_succeeds_on_nonstandard( monkeypatch) def start_local_redis(dirname): project = project_no_dedicated_env(dirname) result = _prepare_printing_errors(project, environ=minimal_environ()) assert not result assert 36 == len(can_connect_args_list) with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """, DEFAULT_LOCAL_STATE_FILENAME: """ service_options: REDIS_URL: port_range: 7389-7421 """ }, start_local_redis) out, err = capsys.readouterr() assert "All ports from 7389 to 7421 were in use, could not start redis-server on one of them." in err assert "REDIS_URL" in err assert "missing requirement" in err assert "" == out
def test_remove_service_duplicate(capsys, monkeypatch): def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) local_state = LocalStateFile.load_for_directory(dirname) local_state.set_service_run_state('ABC', { 'shutdown_commands': [_echo_commandline + ['"shutting down ABC"']] }) local_state.set_service_run_state('TEST', { 'shutdown_commands': [_echo_commandline + ['"shutting down TEST"']] }) local_state.save() code = _parse_args_and_run_subcommand( ['conda-kapsel', 'remove-service', 'redis']) assert code == 1 out, err = capsys.readouterr() assert '' == out expected_err = ( "Conflicting results, found 2 matches, use list-services" " to identify which service you want to remove\n") assert expected_err == err with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: 'services:\n ABC: redis\n TEST: redis'}, check)
def test_list_environments(capsys, monkeypatch): def check_list_not_empty(dirname): code = _parse_args_and_run_subcommand(['conda-kapsel', 'list-env-specs', '--directory', dirname]) assert code == 0 out, err = capsys.readouterr() expected_out = """ Environments for project: {dirname} Name Description ==== =========== bar foo """.format(dirname=dirname).strip() + "\n" assert out == expected_out with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: ('env_specs:\n' ' foo:\n' ' packages:\n' ' - bar\n' ' bar:\n' ' packages:\n' ' - bar\n')}, check_list_not_empty)
def test_main_failed_exec(monkeypatch, capsys): def mock_execvpe(file, args, env): raise OSError(errno.ENOMEM, "It did not work, Michael") monkeypatch.setattr('os.execvpe', mock_execvpe) def check_run_main(dirname): project_dir_disable_dedicated_env(dirname) result = main(Args(directory=dirname)) assert 1 == result with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: """ commands: default: conda_app_entry: python --version """ }, check_run_main) out, err = capsys.readouterr() assert "" == out assert 'Failed to execute' in err assert 'It did not work, Michael' in err
def test_add_command_ask_type_interrupted(monkeypatch, capsys): def check_ask_type(dirname): def mock_is_interactive(): return True monkeypatch.setattr( 'conda_kapsel.commands.console_utils.stdin_is_interactive', mock_is_interactive) def mock_input(prompt): raise KeyboardInterrupt('^C') monkeypatch.setattr('conda_kapsel.commands.console_utils._input', mock_input) args = Args(None, 'test', 'file.py', directory=dirname) with pytest.raises(SystemExit) as excinfo: main(args) assert excinfo.value.code == 1 out, err = capsys.readouterr() assert out == '' assert err == '\nCanceling\n\n' with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: ''}, check_ask_type)
def test_remove_env_spec_fails(capsys, monkeypatch): def check(dirname): from shutil import rmtree as real_rmtree _monkeypatch_pwd(monkeypatch, dirname) test_filename = os.path.join(dirname, 'envs', 'foo') # only allow mock to have side effect once # later, when cleaning up directory, allow removal mock_called = [] def mock_remove(path, ignore_errors=False, onerror=None): if path == test_filename and not mock_called: mock_called.append(True) raise Exception('Error') return real_rmtree(path, ignore_errors, onerror) monkeypatch.setattr('shutil.rmtree', mock_remove) code = _parse_args_and_run_subcommand(['conda-kapsel', 'remove-env-spec', '--name', 'foo']) assert code == 1 out, err = capsys.readouterr() assert '' == out assert ("Failed to remove environment files in %s: Error.\n" % os.path.join(dirname, "envs", "foo")) == err with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: 'env_specs:\n foo:\n channels: []\n packages:\n - bar\n' + ' baz:\n channels: []\n packages:\n - bar\n', 'envs/foo/bin/test': 'code here' }, check)
def test_list_environments(capsys, monkeypatch): def check_list_not_empty(dirname): code = _parse_args_and_run_subcommand( ['conda-kapsel', 'list-env-specs', '--directory', dirname]) assert code == 0 out, err = capsys.readouterr() expected_out = """ Environments for project: {dirname} Name Description ==== =========== bar foo """.format(dirname=dirname).strip() + "\n" assert out == expected_out with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: ('env_specs:\n' ' foo:\n' ' packages:\n' ' - bar\n' ' bar:\n' ' packages:\n' ' - bar\n') }, check_list_not_empty)
def test_add_command_ask_type(monkeypatch): def check_ask_type(dirname): def mock_is_interactive(): return True monkeypatch.setattr( 'conda_kapsel.commands.console_utils.stdin_is_interactive', mock_is_interactive) def mock_console_input(prompt): return "b" monkeypatch.setattr( 'conda_kapsel.commands.console_utils.console_input', mock_console_input) args = Args(None, 'test', 'file.py', directory=dirname) res = main(args) assert res == 0 project = Project(dirname) command = project.project_file.get_value(['commands', 'test']) assert len(command.keys()) == 2 assert command['bokeh_app'] == 'file.py' assert command['env_spec'] == 'default' with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: ''}, check_ask_type)
def test_add_command_ask_other_shell(monkeypatch): def check(dirname): def mock_is_interactive(): return True monkeypatch.setattr('conda_kapsel.commands.console_utils.stdin_is_interactive', mock_is_interactive) def mock_console_input(prompt): return "c" monkeypatch.setattr('conda_kapsel.commands.console_utils.console_input', mock_console_input) def mock_system(): return "Linux" monkeypatch.setattr('platform.system', mock_system) args = Args(None, 'test', 'echo hello', directory=dirname) res = main(args) assert res == 0 project = Project(dirname) command = project.project_file.get_value(['commands', 'test']) assert len(command.keys()) == 2 assert command['unix'] == 'echo hello' assert command['env_spec'] == 'default' with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: ''}, check)
def test_add_packages_to_specific_environment(capsys, monkeypatch): def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) params = _monkeypatch_add_packages( monkeypatch, SimpleStatus(success=True, description='Installed ok.')) code = _parse_args_and_run_subcommand([ 'conda-kapsel', 'add-packages', '--env-spec', 'foo', '--channel', 'c1', '--channel=c2', 'a', 'b' ]) assert code == 0 out, err = capsys.readouterr() assert ('Installed ok.\n' + 'Added packages to environment foo in project file: a, b.\n' ) == out assert '' == err assert 1 == len(params['args']) assert dict(env_spec_name='foo', packages=['a', 'b'], channels=['c1', 'c2']) == params['kwargs'] with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: """ env_specs: foo: packages: - bar """ }, check)
def test_list_commands(capsys): def check_empty_project(dirname): code = _parse_args_and_run_subcommand(['conda-kapsel', 'list-commands', '--directory', dirname]) assert code == 0 out, err = capsys.readouterr() assert '' == err expected_out = """ Commands for project: {dirname} Name Description ==== =========== default Bokeh app test.py run_notebook Notebook test.ipynb """.format(dirname=dirname).strip() + "\n" assert expected_out == out with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: ("commands:\n" " default:\n" " bokeh_app: test.py\n" " run_notebook:\n" " notebook: test.ipynb\n" "packages:\n" " - bokeh\n" " - notebook\n")}, check_empty_project)
def test_prepare_some_env_var_not_set_keep_going(): def prepare_some_env_var_keep_going(dirname): project = project_no_dedicated_env(dirname) environ = minimal_environ(BAR='bar') stage = prepare_in_stages(project, environ=environ, keep_going_until_success=True) # there's an initial stage to set the conda env next_stage = stage.execute() assert not stage.failed assert stage.environ['PROJECT_DIR'] == dirname stage = next_stage for i in range(1, 10): next_stage = stage.execute() assert next_stage is not None assert stage.failed assert stage.environ['PROJECT_DIR'] == dirname stage = next_stage assert dict(BAR='bar') == strip_environ(environ) with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: FOO: {} """}, prepare_some_env_var_keep_going)
def test_main_dirname_not_provided_use_pwd(monkeypatch, capsys): can_connect_args = _monkeypatch_can_connect_to_socket_to_succeed(monkeypatch) def main_redis_url(dirname): from os.path import abspath as real_abspath def mock_abspath(path): if path == ".": return dirname else: return real_abspath(path) monkeypatch.setattr('os.path.abspath', mock_abspath) project_dir_disable_dedicated_env(dirname) code = _parse_args_and_run_subcommand(['conda-kapsel', 'activate']) assert code == 0 with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """}, main_redis_url) assert can_connect_args['port'] == 6379 out, err = capsys.readouterr() assert "export PROJECT_DIR" in out assert "export REDIS_URL=redis://localhost:6379\n" in out assert "" == err
def test_remove_service(capsys, monkeypatch): def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) local_state = LocalStateFile.load_for_directory(dirname) local_state.set_service_run_state('ABC', { 'shutdown_commands': [_echo_commandline + ['"shutting down ABC"']] }) local_state.set_service_run_state('TEST', { 'shutdown_commands': [_echo_commandline + ['"shutting down TEST"']] }) local_state.save() code = _parse_args_and_run_subcommand( ['conda-kapsel', 'remove-service', 'TEST']) assert code == 0 out, err = capsys.readouterr() assert '' == err expected_out = ("Removed service 'TEST' from the project file.\n") assert expected_out == out with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: 'services:\n ABC: redis\n TEST: redis'}, check)
def _test_list_packages(capsys, env, expected_deps): def check_list_not_empty(dirname): params = ['conda-kapsel', 'list-packages', '--directory', dirname] if env is not None: params.extend(['--env-spec', env]) code = _parse_args_and_run_subcommand(params) assert code == 0 out, err = capsys.readouterr() project = Project(dirname) assert project.default_env_spec_name == 'foo' expected_out = "Packages for environment '{}':\n{}".format( env or project.default_env_spec_name, expected_deps) assert out == expected_out project_contents = ('env_specs:\n' ' foo:\n' ' packages:\n' ' - requests\n' ' - flask\n' ' bar:\n' ' packages:\n' ' - httplib\n' ' - django\n\n' 'packages:\n' ' - mandatory_package\n') with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: project_contents}, check_list_not_empty)
def test_add_command_ask_other_windows(monkeypatch): def check(dirname): def mock_is_interactive(): return True monkeypatch.setattr( 'conda_kapsel.commands.console_utils.stdin_is_interactive', mock_is_interactive) def mock_console_input(prompt): return "c" monkeypatch.setattr( 'conda_kapsel.commands.console_utils.console_input', mock_console_input) def mock_system(): return "Windows" monkeypatch.setattr('platform.system', mock_system) args = Args(None, 'test', 'echo hello', directory=dirname) res = main(args) assert res == 0 project = Project(dirname) command = project.project_file.get_value(['commands', 'test']) assert len(command.keys()) == 2 assert command['windows'] == 'echo hello' assert command['env_spec'] == 'default' with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: ''}, check)
def _test_list_packages(capsys, env, expected_deps): def check_list_not_empty(dirname): params = ['conda-kapsel', 'list-packages', '--directory', dirname] if env is not None: params.extend(['--env-spec', env]) code = _parse_args_and_run_subcommand(params) assert code == 0 out, err = capsys.readouterr() project = Project(dirname) assert project.default_env_spec_name == 'foo' expected_out = "Packages for environment '{}':\n{}".format(env or project.default_env_spec_name, expected_deps) assert out == expected_out project_contents = ('env_specs:\n' ' foo:\n' ' packages:\n' ' - requests\n' ' - flask\n' ' bar:\n' ' packages:\n' ' - httplib\n' ' - django\n\n' 'packages:\n' ' - mandatory_package\n') with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: project_contents}, check_list_not_empty)
def test_list_commands(capsys): def check_empty_project(dirname): code = _parse_args_and_run_subcommand( ['conda-kapsel', 'list-commands', '--directory', dirname]) assert code == 0 out, err = capsys.readouterr() assert '' == err expected_out = """ Commands for project: {dirname} Name Description ==== =========== default Bokeh app test.py run_notebook Notebook test.ipynb """.format(dirname=dirname).strip() + "\n" assert expected_out == out with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: ("commands:\n" " default:\n" " bokeh_app: test.py\n" " run_notebook:\n" " notebook: test.ipynb\n") }, check_empty_project)
def test_prepare_choose_command(): def check(dirname): project = project_no_dedicated_env(dirname) environ = minimal_environ() result = prepare_without_interaction(project, environ=environ, command_name='foo') assert result assert os.path.join(project.directory_path, 'foo.py') in result.command_exec_info.args environ = minimal_environ() result = prepare_without_interaction(project, environ=environ, command_name='bar') assert result assert os.path.join(project.directory_path, 'bar.py') in result.command_exec_info.args with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ commands: foo: bokeh_app: foo.py bar: bokeh_app: bar.py packages: - bokeh """, "foo.py": "# foo", "bar.py": "# bar"}, check)
def test_activate(monkeypatch): can_connect_args = _monkeypatch_can_connect_to_socket_to_succeed( monkeypatch) def activate_redis_url(dirname): project_dir_disable_dedicated_env(dirname) result = activate(dirname, UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT, conda_environment=None) assert can_connect_args['port'] == 6379 assert result is not None if platform.system() == 'Windows': result = [ line for line in result if not line.startswith("export PATH") ] print("activate changed PATH on Windows and ideally it would not.") if len(result) > 2: import os print("os.environ=" + repr(os.environ)) print("result=" + repr(result)) assert [ 'export PROJECT_DIR=' + quote(dirname), 'export REDIS_URL=redis://localhost:6379' ] == result with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """}, activate_redis_url)
def test_prepare_and_unprepare_local_redis_server_with_failed_unprovide( monkeypatch): # this test will fail if you don't have Redis installed, since # it actually starts it. if platform.system() == 'Windows': print("Cannot start redis-server on Windows") return from conda_kapsel.plugins.network_util import can_connect_to_socket as real_can_connect_to_socket _monkeypatch_can_connect_to_socket_on_nonstandard_port_only( monkeypatch, real_can_connect_to_socket) def start_local_redis(dirname): project = project_no_dedicated_env(dirname) result = _prepare_printing_errors(project, environ=minimal_environ()) assert result # now clean it up, but arrange for that to fail local_state_file = LocalStateFile.load_for_directory(dirname) local_state_file.set_service_run_state( 'REDIS_URL', {'shutdown_commands': [['false']]}) local_state_file.save() status = unprepare(project, result) assert not status assert status.status_description == 'Shutdown commands failed for REDIS_URL.' with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """}, start_local_redis)
def test_prepare_and_unprepare_two_local_redis_servers_with_failed_unprovide(monkeypatch): # this test will fail if you don't have Redis installed, since # it actually starts it. if platform.system() == 'Windows': print("Cannot start redis-server on Windows") return from conda_kapsel.plugins.network_util import can_connect_to_socket as real_can_connect_to_socket _monkeypatch_can_connect_to_socket_on_nonstandard_port_only(monkeypatch, real_can_connect_to_socket) def start_local_redis(dirname): project = project_no_dedicated_env(dirname) result = _prepare_printing_errors(project, environ=minimal_environ()) assert result # now clean it up, but arrange for that to double-fail local_state_file = LocalStateFile.load_for_directory(dirname) local_state_file.set_service_run_state('REDIS_URL', {'shutdown_commands': [['false']]}) local_state_file.set_service_run_state('REDIS_URL_2', {'shutdown_commands': [['false']]}) local_state_file.save() status = unprepare(project, result) assert not status assert status.status_description == 'Failed to clean up REDIS_URL, REDIS_URL_2.' with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis REDIS_URL_2: redis """}, start_local_redis)
def test_fail_to_prepare_local_redis_server_scope_system(monkeypatch, capsys): _monkeypatch_can_connect_to_socket_always_fails(monkeypatch) def check_no_autostart(dirname): project = project_no_dedicated_env(dirname) result = _prepare_printing_errors(project, environ=minimal_environ()) assert not result with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """, DEFAULT_LOCAL_STATE_FILENAME: """ service_options: REDIS_URL: scope: system """ }, check_no_autostart) out, err = capsys.readouterr() assert out == "" assert err == ( "Could not connect to system default Redis.\n" + "missing requirement to run this project: A running Redis server, located by a redis: URL set as REDIS_URL.\n" + " Environment variable REDIS_URL is not set.\n")
def test_add_command_ask_type_twice(monkeypatch, capsys): def check_ask_type(dirname): def mock_is_interactive(): return True monkeypatch.setattr('conda_kapsel.commands.console_utils.stdin_is_interactive', mock_is_interactive) calls = [] def mock_console_input(prompt): res = ['-', 'b'][len(calls)] calls.append(True) return res monkeypatch.setattr('conda_kapsel.commands.console_utils.console_input', mock_console_input) args = Args(None, 'test', 'file.py', directory=dirname) res = main(args) assert res == 0 assert len(calls) == 2 project = Project(dirname) command = project.project_file.get_value(['commands', 'test']) assert len(command.keys()) == 2 assert command['bokeh_app'] == 'file.py' assert command['env_spec'] == 'default' out, err = capsys.readouterr() assert out == ("Please enter 'b', 'n', or 'c'.\n" + " A Bokeh app is the project-relative path to a Bokeh script or app directory.\n" + " A notebook file is the project-relative path to a .ipynb file.\n" " A command line is any command you might type at the command prompt.\n" "Added a command 'test' to the project. Run it with `conda-kapsel run test`.\n") with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: ''}, check_ask_type)
def test_activate_quoting(monkeypatch): def activate_foo(dirname): project_dir_disable_dedicated_env(dirname) result = activate(dirname, UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT, conda_environment=None) assert result is not None if platform.system() == 'Windows': result = [ line for line in result if not line.startswith("export PATH") ] print("activate changed PATH on Windows and ideally it would not.") assert ["export FOO='$! boo'", 'export PROJECT_DIR=' + quote(dirname)] == result with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: """ variables: FOO: {} """, DEFAULT_LOCAL_STATE_FILENAME: """ variables: FOO: $! boo """ }, activate_foo)
def test_prepare_and_unprepare_download(monkeypatch): def provide_download(dirname): @gen.coroutine def mock_downloader_run(self, loop): class Res: pass res = Res() res.code = 200 with open(os.path.join(dirname, 'data.csv'), 'w') as out: out.write('data') self._hash = '12345abcdef' raise gen.Return(res) monkeypatch.setattr("conda_kapsel.internal.http_client.FileDownloader.run", mock_downloader_run) project = project_no_dedicated_env(dirname) result = prepare_without_interaction(project, environ=minimal_environ(PROJECT_DIR=dirname)) assert hasattr(result, 'environ') assert 'DATAFILE' in result.environ filename = os.path.join(dirname, 'data.csv') assert os.path.exists(filename) status = unprepare(project, result) assert status.logs == ["Removed downloaded file %s." % filename, ("Current environment is not in %s, no need to delete it." % dirname)] assert status.status_description == 'Success.' assert status assert not os.path.exists(filename) with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: DATAFILE_CONTENT}, provide_download)
def test_redis_server_configure_custom_port_range(monkeypatch, capsys): can_connect_args_list = _monkeypatch_can_connect_to_socket_always_succeeds_on_nonstandard(monkeypatch) def start_local_redis(dirname): project = project_no_dedicated_env(dirname) result = _prepare_printing_errors(project, environ=minimal_environ()) assert not result assert 36 == len(can_connect_args_list) with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """, DEFAULT_LOCAL_STATE_FILENAME: """ service_options: REDIS_URL: port_range: 7389-7421 """}, start_local_redis) out, err = capsys.readouterr() assert "All ports from 7389 to 7421 were in use, could not start redis-server on one of them." in err assert "REDIS_URL" in err assert "missing requirement" in err assert "" == out
def test_ask_variables_interactively_whitespace_answer_re_asks(monkeypatch): def check(dirname): project_dir_disable_dedicated_env(dirname) def mock_is_interactive(): return True monkeypatch.setattr( 'conda_kapsel.commands.console_utils.stdin_is_interactive', mock_is_interactive) inputs = [" ", "foo", "bar"] def mock_console_input(prompt, encrypted): return inputs.pop(0) monkeypatch.setattr( 'conda_kapsel.commands.console_utils.console_input', mock_console_input) res = _parse_args_and_run_subcommand( ['conda-kapsel', 'prepare', '--directory', dirname]) assert res == 0 local_state = LocalStateFile.load_for_directory(dirname) assert local_state.get_value(['variables', 'FOO']) == 'foo' assert local_state.get_value(['variables', 'BAR']) == 'bar' with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: FOO: null BAR: null """}, check)
def test_no_ask_variables_interactively_not_interactive(monkeypatch, capsys): def check(dirname): project_dir_disable_dedicated_env(dirname) def mock_is_interactive(): return False monkeypatch.setattr( 'conda_kapsel.commands.console_utils.stdin_is_interactive', mock_is_interactive) def mock_console_input(prompt, encrypted): raise Exception("should not have been called") monkeypatch.setattr( 'conda_kapsel.commands.console_utils.console_input', mock_console_input) res = _parse_args_and_run_subcommand( ['conda-kapsel', 'prepare', '--directory', dirname]) assert res == 1 out, err = capsys.readouterr() assert err == _foo_and_bar_missing with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: FOO: null BAR: null """}, check)
def test_main_fails_to_redis(monkeypatch, capsys): _monkeypatch_can_connect_to_socket_to_fail_to_find_redis(monkeypatch) _monkeypatch_open_new_tab(monkeypatch) from conda_kapsel.commands.prepare_with_mode import prepare_with_ui_mode_printing_errors as real_prepare def _mock_prepare_do_not_keep_going( project, environ=None, ui_mode=UI_MODE_TEXT_ASSUME_YES_DEVELOPMENT, extra_command_args=None): return real_prepare(project, environ, ui_mode=ui_mode, extra_command_args=extra_command_args) monkeypatch.setattr( 'conda_kapsel.commands.prepare_with_mode.prepare_with_ui_mode_printing_errors', _mock_prepare_do_not_keep_going) def main_redis_url(dirname): project_dir_disable_dedicated_env(dirname) code = main(Args(directory=dirname)) assert 1 == code with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """}, main_redis_url) out, err = capsys.readouterr() assert "missing requirement" in err assert "All ports from 6380 to 6449 were in use" in err
def test_prepare_command_choose_environment_does_not_exist(capsys): def check_prepare_choose_environment_does_not_exist(dirname): project_dir_disable_dedicated_env(dirname) result = _parse_args_and_run_subcommand([ 'conda-kapsel', 'prepare', '--directory', dirname, '--env-spec=nope' ]) assert result == 1 expected_error = ( "Environment name 'nope' is not in %s, these names were found: bar, foo" % os.path.join(dirname, DEFAULT_PROJECT_FILENAME)) out, err = capsys.readouterr() assert out == "" assert expected_error in err with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: """ env_specs: foo: packages: - nonexistent_foo bar: packages: - nonexistent_bar """ }, check_prepare_choose_environment_does_not_exist)
def test_main(monkeypatch, capsys): can_connect_args = _monkeypatch_can_connect_to_socket_to_succeed( monkeypatch) _monkeypatch_open_new_tab(monkeypatch) def mock_conda_create(prefix, pkgs, channels): raise RuntimeError( "this test should not create an environment in %s with pkgs %r" % (prefix, pkgs)) monkeypatch.setattr('conda_kapsel.internal.conda_api.create', mock_conda_create) def main_redis_url(dirname): project_dir_disable_dedicated_env(dirname) main(Args(directory=dirname, mode='browser')) with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """}, main_redis_url) assert can_connect_args['port'] == 6379 out, err = capsys.readouterr() assert "# Configure the project at " in out assert "" == err
def test_main_dirname_not_provided_use_pwd(monkeypatch, capsys): can_connect_args = _monkeypatch_can_connect_to_socket_to_succeed( monkeypatch) _monkeypatch_open_new_tab(monkeypatch) def main_redis_url(dirname): from os.path import abspath as real_abspath def mock_abspath(path): if path == ".": return dirname else: return real_abspath(path) monkeypatch.setattr('os.path.abspath', mock_abspath) project_dir_disable_dedicated_env(dirname) code = _parse_args_and_run_subcommand( ['conda-kapsel', 'prepare', '--mode=browser']) assert code == 0 with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """}, main_redis_url) assert can_connect_args['port'] == 6379 out, err = capsys.readouterr() assert "# Configure the project at " in out assert "" == err
def test_unprepare_gets_error_on_delete(monkeypatch): def mock_create(prefix, pkgs, channels): os.makedirs(os.path.join(prefix, "conda-meta")) monkeypatch.setattr('conda_kapsel.internal.conda_api.create', mock_create) def prepare_project_scoped_env(dirname): project = Project(dirname) environ = minimal_environ(PROJECT_DIR=dirname) result = prepare_without_interaction(project, environ=environ) assert result expected_env = os.path.join(dirname, "envs", "default") # Now unprepare def mock_rmtree(path): raise IOError("I will never rm the tree!") monkeypatch.setattr('shutil.rmtree', mock_rmtree) status = unprepare(project, result) assert status.status_description == ('Failed to remove environment files in %s: I will never rm the tree!.' % (expected_env)) assert not status assert os.path.exists(expected_env) # so we can rmtree our tmp directory monkeypatch.undo() with_directory_contents_completing_project_file(dict(), prepare_project_scoped_env)
def test_prepare_project_scoped_env_not_attempted_in_check_mode(monkeypatch): def mock_create(prefix, pkgs, channels): raise Exception("Should not have attempted to create env") monkeypatch.setattr('conda_kapsel.internal.conda_api.create', mock_create) def prepare_project_scoped_env_not_attempted(dirname): project = Project(dirname) environ = minimal_environ(PROJECT_DIR=dirname) result = prepare_without_interaction(project, environ=environ, mode=provide.PROVIDE_MODE_CHECK) assert not result expected_env_path = os.path.join(dirname, "envs", "default") assert [ ('missing requirement to run this project: ' + 'The project needs a Conda environment containing all required packages.'), " '%s' doesn't look like it contains a Conda environment yet." % expected_env_path ] == result.errors # unprepare should not have anything to do status = unprepare(project, result) assert status assert status.errors == [] assert status.status_description == ("Nothing to clean up for environment 'default'.") with_directory_contents_completing_project_file(dict(), prepare_project_scoped_env_not_attempted)
def test_remove_download_directory_error(capsys, monkeypatch): def check(dirname): from shutil import rmtree as real_rmtree _monkeypatch_pwd(monkeypatch, dirname) test_filename = os.path.join(dirname, 'foo') # only allow mock to have side effect once # later, when cleaning up directory, allow removal mock_called = [] def mock_remove(path, ignore_errors=False, onerror=None): if path == test_filename and not mock_called: mock_called.append(True) raise Exception('Error') return real_rmtree(path, ignore_errors, onerror) monkeypatch.setattr('shutil.rmtree', mock_remove) code = _parse_args_and_run_subcommand(['conda-kapsel', 'remove-download', 'TEST_FILE']) assert code == 1 out, err = capsys.readouterr() assert '' == out assert "Failed to remove {}: Error.\n".format(test_filename) == err with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: "downloads:\n TEST_FILE: http://localhost/foo.zip", 'foo/data.txt': 'data here' }, check)
def test_prepare_some_env_var_not_set_keep_going(): def prepare_some_env_var_keep_going(dirname): project = project_no_dedicated_env(dirname) environ = minimal_environ(BAR='bar') stage = prepare_in_stages(project, environ=environ, keep_going_until_success=True) # there's an initial stage to set the conda env next_stage = stage.execute() assert not stage.failed assert stage.environ['PROJECT_DIR'] == dirname stage = next_stage for i in range(1, 10): next_stage = stage.execute() assert next_stage is not None assert stage.failed assert stage.environ['PROJECT_DIR'] == dirname stage = next_stage assert dict(BAR='bar') == strip_environ(environ) with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: FOO: {} """}, prepare_some_env_var_keep_going)
def test_env_var_provider_configure_disabled_local_state_value(): def check_env_var_provider_config_disabled_local_state(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()) assert dict(source='unset') == status.analysis.config assert local_state_file.get_value(['variables', 'FOO']) is None assert local_state_file.get_value(['disabled_variables', 'FOO']) is None environ = dict() # source=environ should mean we set disabled_variables instead of variables provider.set_config_values_as_strings(requirement, environ, local_state_file, 'default', UserConfigOverrides(), dict(source='environ', value="bar")) assert local_state_file.get_value(['variables', 'FOO']) is None assert local_state_file.get_value(['disabled_variables', 'FOO']) == "bar" config = provider.read_config(requirement, environ, local_state_file, 'default', UserConfigOverrides()) assert config == dict(source='unset', value='bar') with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: - FOO """}, check_env_var_provider_config_disabled_local_state)
def test_env_var_provider_with_encrypted_value_set_in_local_state(): 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) result = provider.provide(requirement, context=context) assert [] == result.errors assert 'FOO_PASSWORD' in context.environ assert 'from_local_state' == context.environ['FOO_PASSWORD'] with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: FOO_PASSWORD: default: from_default """, DEFAULT_LOCAL_STATE_FILENAME: """ variables: FOO_PASSWORD: from_local_state """}, check_env_var_provider)
def test_env_var_provider_with_value_set_in_environment(): 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) result = provider.provide(requirement, context=context) assert [] == result.errors assert 'FOO' in context.environ assert 'from_environ' == context.environ['FOO'] # set a default to be sure we prefer 'environ' instead with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ variables: FOO: default: from_default """}, check_env_var_provider)
def test_remove_env_spec_in_use(capsys, monkeypatch): def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) code = _parse_args_and_run_subcommand(['conda-kapsel', 'remove-env-spec', '--name', 'bar']) assert code == 1 out, err = capsys.readouterr() assert '' == out assert (("%s: env_spec 'bar' for command 'foo' does not appear in the env_specs section\n" % os.path.join( dirname, DEFAULT_PROJECT_FILENAME)) + "Unable to load the project.\n") == err with_directory_contents_completing_project_file( { DEFAULT_PROJECT_FILENAME: """ commands: foo: unix: envs/foo/bin/test env_spec: bar env_specs: other: packages: - hello bar: packages: - boo """, 'envs/foo/bin/test': 'code here' }, check)
def test_remove_variable_project_problem(monkeypatch): def check_problem_remove(dirname): args = Args(vars_to_remove=['foo', 'baz'], directory=dirname) res = main_remove(args) assert res == 1 with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: ("variables:\n" " foo: true")}, check_problem_remove)
def test_provide_empty_url(monkeypatch): ERR_DATAFILE_CONTENT = ("downloads:\n" " DATAFILE:\n" " url: \"\"\n") def provide_download(dirname): project = project_no_dedicated_env(dirname) prepare_without_interaction(project, environ=minimal_environ(PROJECT_DIR=dirname)) assert "Download item DATAFILE has an empty 'url' field." in project.problems with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: ERR_DATAFILE_CONTENT}, provide_download)
def test_service_dict_bad_default(): def check(dirname): project = Project(dirname) assert ["default value for variable FOOBAR must be null, a string, or a number, not []."] == project.problems with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: FOOBAR: { type: redis, default: [] } """}, check)
def test_remove_command_missing(monkeypatch, capsys): def check(dirname): code = _parse_args_and_run_subcommand(['conda-kapsel', 'remove-command', 'test', '--directory', dirname]) assert code == 1 out, err = capsys.readouterr() assert err == "Command: 'test' not found in project file.\n" assert out == '' with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: ''}, check)
def test_list_commands_empty_project(capsys): def check_empty_project(dirname): code = _parse_args_and_run_subcommand(['conda-kapsel', 'list-commands', '--directory', dirname]) assert code == 0 out, err = capsys.readouterr() assert '' == err assert ("No commands found for project: {}\n\n".format(dirname)) == out with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: ""}, check_empty_project)
def test_clean_command_on_empty_project(capsys): def check(dirname): code = _parse_args_and_run_subcommand(['conda-kapsel', 'clean', '--directory', dirname]) assert code == 0 out, err = capsys.readouterr() assert 'Cleaned.\n' == out assert '' == err with_directory_contents_completing_project_file(dict(), check)
def test_provide_wrong_form(monkeypatch): ERR_DATAFILE_CONTENT = ("downloads:\n" " - http://localhost/data.csv\n") def provide_download(dirname): project = project_no_dedicated_env(dirname) prepare_without_interaction(project, environ=minimal_environ(PROJECT_DIR=dirname)) assert ("%s: 'downloads:' section should be a dictionary, found ['http://localhost/data.csv']" % os.path.join( dirname, DEFAULT_PROJECT_FILENAME)) in project.problems with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: ERR_DATAFILE_CONTENT}, provide_download)
def test_service_dict_with_no_service_type(): def check(dirname): project = Project(dirname) assert ["Service FOOBAR doesn't contain a 'type' field."] == project.problems with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: FOOBAR: {} """}, check)
def test_service_with_bad_service_type(): def check(dirname): project = Project(dirname) assert ["Service FOOBAR has an unknown type 'not_a_service'."] == project.problems with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: FOOBAR: not_a_service """}, check)
def test_prepare_and_unprepare_local_redis_server(monkeypatch): # this test will fail if you don't have Redis installed, since # it actually starts it. if platform.system() == 'Windows': print("Cannot start redis-server on Windows") return from conda_kapsel.plugins.network_util import can_connect_to_socket as real_can_connect_to_socket can_connect_args_list = _monkeypatch_can_connect_to_socket_on_nonstandard_port_only( monkeypatch, real_can_connect_to_socket) def start_local_redis(dirname): project = project_no_dedicated_env(dirname) result = _prepare_printing_errors(project, environ=minimal_environ()) assert result local_state_file = LocalStateFile.load_for_directory(dirname) state = local_state_file.get_service_run_state('REDIS_URL') assert 'port' in state port = state['port'] assert dict(REDIS_URL=("redis://localhost:" + str(port)), PROJECT_DIR=project.directory_path) == strip_environ( result.environ) assert len(can_connect_args_list) >= 2 servicedir = os.path.join(dirname, "services") redisdir = os.path.join(servicedir, "REDIS_URL") pidfile = os.path.join(redisdir, "redis.pid") logfile = os.path.join(redisdir, "redis.log") assert os.path.exists(pidfile) assert os.path.exists(logfile) assert real_can_connect_to_socket(host='localhost', port=port) # now clean it up status = unprepare(project, result) assert status assert not os.path.exists(pidfile) assert not os.path.exists(logfile) assert not os.path.exists(redisdir) assert not os.path.exists(servicedir) assert not real_can_connect_to_socket(host='localhost', port=port) local_state_file.load() assert dict() == local_state_file.get_service_run_state("REDIS_URL") with_directory_contents_completing_project_file( {DEFAULT_PROJECT_FILENAME: """ services: REDIS_URL: redis """}, start_local_redis)
def test_clean_command_on_empty_project(capsys): def check(dirname): code = _parse_args_and_run_subcommand( ['conda-kapsel', 'clean', '--directory', dirname]) assert code == 0 out, err = capsys.readouterr() assert 'Cleaned.\n' == out assert '' == err with_directory_contents_completing_project_file(dict(), check)
def test_prepare_bad_command_name(): def check(dirname): project = project_no_dedicated_env(dirname) environ = minimal_environ(BAR='bar') result = prepare_without_interaction(project, environ=environ, command_name="blah") assert not result assert result.errors assert "Command name 'blah' is not in" in result.errors[0] with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: """ """}, check)
def test_list_service_with_empty_project(capsys, monkeypatch): def check_empty(dirname): _monkeypatch_pwd(monkeypatch, dirname) code = _parse_args_and_run_subcommand(['conda-kapsel', 'list-services']) assert code == 0 out, err = capsys.readouterr() assert err == '' assert out == "No services found for project: {}\n".format(dirname) with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: ""}, check_empty)
def test_upload_command_on_invalid_project(capsys): def check(dirname): code = _parse_args_and_run_subcommand(['conda-kapsel', 'upload', '--directory', dirname]) assert code == 1 out, err = capsys.readouterr() assert '' == out assert ('variables section contains wrong value type 42,' + ' should be dict or list of requirements\n' + 'Unable to load the project.\n') == err with_directory_contents_completing_project_file({DEFAULT_PROJECT_FILENAME: "variables:\n 42"}, check)
def test_remove_env_spec_missing(capsys, monkeypatch): def check(dirname): _monkeypatch_pwd(monkeypatch, dirname) code = _parse_args_and_run_subcommand(['conda-kapsel', 'remove-env-spec', '--name', 'foo']) assert code == 1 out, err = capsys.readouterr() assert '' == out assert "Environment spec foo doesn't exist.\n" == err with_directory_contents_completing_project_file(dict(), check)