def test_global_nested_state(): "context managers can be nested for global state" assert state.ENV == {} with settings(foo="bar"): with settings(baz="bop"): assert state.ENV == {"foo": "bar", "baz": "bop"} assert state.ENV == {}
def test_nest_some_settings(): "demonstrates how settings accumulate" with settings(foo="bar"): with settings(bar="baz"): with settings(baz="bup"): LOG.debug( "after three nestings I have the cumulate state: %s" % state.ENV) assert state.ENV == {"foo": "bar", "bar": "baz", "baz": "bup"}
def test_nested_global_state_is_unlocked_inside_context(): "`state.ENV` is only writeable within the context manager, including nested context managers" assert isinstance(state.ENV, state.FreezeableDict) assert state.ENV.read_only with settings(): assert not state.ENV.read_only state.ENV["foo"] = "bar" with settings(): assert not state.ENV.read_only assert not state.ENV.read_only assert state.ENV.read_only assert state.ENV == {}
def test_stateful__ssh_client(): "the SSHClient object factory is creating objects (or not) as expected" with state.settings(): with patch("threadbare.operations.SSHClient") as m1: operations._ssh_client( user="******", host_string="localhost", key_filename="/foo/bar/baz.pem", port=123, ) expected_initialised_with = { "user": "******", "pkey": "/foo/bar/baz.pem", "host": "localhost", "port": 123, "password": None, } m1.assert_called_with(**expected_initialised_with) with patch("threadbare.operations.SSHClient") as m2: operations._ssh_client( user="******", host_string="localhost", key_filename="/foo/bar/baz.pem", port=123, ) m2.assert_not_called()
def test_set_defaults_settings(): "global state defaults can be easily set" state.set_defaults({"foo": "bar"}) with state.settings(foo="baz"): assert state.ENV == {"foo": "baz"} assert state.ENV == {"foo": "bar"} assert state.DEPTH == 0
def test_execute_many_parallel_with_params(): "`parallel` will wrap a given function and run it `pool_size` times in parallel, but is ignored when `execute` is given a list of values" def fn(): with settings() as local_env: return local_env parallel_fn = execute.parallel(fn, pool_size=1) param_key = "mykey" param_values = [1, 2, 3] expected = [ { "parallel": True, "abort_on_prompts": True, "parent": "environment", "mykey": 1, }, { "parallel": True, "abort_on_prompts": True, "parent": "environment", "mykey": 2, }, { "parallel": True, "abort_on_prompts": True, "parent": "environment", "mykey": 3, }, ] with settings(parent="environment"): assert expected == execute.execute(parallel_fn, param_key, param_values)
def test_formatted_output_display_running(): "the 'print running' function obeys formatting rules" cases = [ # command, expected output, more settings ("ls -l", "[1.2.3.4] run: ls -l\n", { "host_string": "1.2.3.4" }), ("ls -l", "ls -l\n", { "host_string": "1.2.3.4", "display_prefix": False }), ("ls -l", "", { "host_string": "1.2.3.4", "quiet": True }), ( "ls -l", "<1.2.3.4> (run) ls -l\n", { "host_string": "1.2.3.4", "line_template": "<{host}> ({pipe}) {line}\n" }, ), ] for command, expected, settings in cases: strbuffer = StringIO() with state.settings(**settings): operations._print_running(command, strbuffer, display_running=True, **settings) assert expected == strbuffer.getvalue()
def test_global_deleted_state(): "original global state is restored if a value is deleted" assert state.ENV == {} with settings(foo="bar", bar="baz") as env: assert state.ENV == env == {"foo": "bar", "bar": "baz"} del env["foo"] assert state.ENV == env == {"bar": "baz"} assert state.ENV == env == {}
def test_abort_display_aborts_message(): "abort message is displayed when `settings.display_aborts` is `True`" result = {"foo": "bar"} with state.settings(display_aborts=True): with patch("threadbare.operations.LOG.error") as mock: with pytest.raises(RuntimeError): operations.abort(result, "failed to succeed") mock.assert_called_once_with("Fatal error: failed to succeed")
def test_abort_display_aborts_message_when_quiet(): "abort message is *not* displayed when `settings.display_aborts` is `True` and `settings.quiet` is also `True`" result = {"foo": "bar"} with state.settings(display_aborts=True, quiet=True): with patch("threadbare.operations.LOG.error") as mock: with pytest.raises(RuntimeError): operations.abort(result, "failed to succeed") mock.assert_not_called()
def test_uncontrolled_global_state_modification_2(): """modifications to global state that happen outside of the context manager's control (with ... as ...) are available as expected BUT are reverted on exit""" assert isinstance(state.ENV, state.FreezeableDict) assert state.ENV == {} with settings() as env: state.ENV["foo"] = {"bar": "bop"} assert env == {"foo": {"bar": "bop"}} assert state.ENV == env == {}
def test_global_overridden_state(): "global overrides exist only for the scope of the context manager" assert state.ENV == {} with settings(foo="baz") as local_env: assert local_env == {"foo": "baz"} # python vagary that this can still be referenced # it should still be as we expect though. assert local_env == {} assert state.ENV == {}
def test_cleanup(): "'cleanup' functions can be added that will be executed when the context manager is left" side_effects = {} def cleanup_fn(): side_effects["?"] = "!" with settings(): state.add_cleanup(cleanup_fn) assert side_effects["?"] == "!"
def test_nested_scopes_dont_cleanup_parent_scopes(): "cleanup functions are only called for the scope they were added to" side_effects = {} def cleanup_fn_1(): side_effects["1"] = "scope 1 cleaned up" def cleanup_fn_2(): side_effects["2"] = "scope 2 cleaned up" with settings(): state.add_cleanup(cleanup_fn_1) with settings(): state.add_cleanup(cleanup_fn_2) assert side_effects == {"2": "scope 2 cleaned up"} assert side_effects == { "2": "scope 2 cleaned up", "1": "scope 1 cleaned up" }
def test_formatted_output_unicode(): "unicode output is correctly encoded before being formatted to avoid UnicodeEncodeErrors in python2" line_template = "{line}" if not common.PY3: unicode_point = u"\u258e\u5b57 <-- omg, so fancy" else: unicode_point = "\u258e\u5b57 <-- omg, so fancy" expected_stdout = "▎字 <-- omg, so fancy" with state.settings(line_template=line_template): result = operations._print_line(StringIO(), unicode_point) assert expected_stdout == result
def test_parallel_with_prompts_custom__swallow_errors(): "prompts issued while executing a worker function in parallel with `abort_exception` set returns the custom exception" @execute.parallel def workerfn(): return operations.prompt("gimmie") with settings(abort_exception=ValueError): results = execute.execute(workerfn, raise_unhandled_errors=False) expected = "prompted with: gimmie" assert expected == str(results[0])
def test_rsync_download_command(): "rsync invocations are generated correctly" expected = "rsync --rsh='ssh -i /example/path/id_rsa -p 23 -o StrictHostKeyChecking=no' [email protected]:/remote/bar /local/foo" settings = { "user": "******", "port": 23, "host_string": "1.2.3.4", "key_filename": "/example/path/id_rsa", } with state.settings(**settings): actual = operations._rsync_download("/remote/bar", "/local/foo") assert expected == actual
def test_formatted_output(): "`_print_line`, called by `remote`, can have it's output string customised and still return the original string for processing" expected_stdout = "hello, world!" expected_buffer = "[35.153.232.132] out: hello, world!" line_template = "[{host}] {pipe}: {line}" strbuffer = StringIO() with state.settings(host_string="35.153.232.132", line_template=line_template): result = operations._print_line(strbuffer, "hello, world!") assert expected_stdout == result assert expected_buffer == strbuffer.getvalue()
def test_parallel_with_prompts_custom__raise_errors(): "prompts issued while executing a worker function in parallel with `abort_exception` return a custom exception" @execute.parallel def workerfn(): return operations.prompt("gimmie") with settings(abort_exception=ValueError): with pytest.raises(ValueError) as e: execute.execute(workerfn) expected = "prompted with: gimmie" assert expected == str(e)
def test_local_command_non_zero_exit_swallowed(): "`local` commands that exit with a non-zero result do not raise an exception if `warn_only` is `True`" expected_result = { "command": '/bin/bash -l -c "exit 1"', "failed": True, "return_code": 1, "stderr": [], "stdout": [], "succeeded": False, } command = "exit 1" with state.settings(warn_only=True): result = operations.local(command) assert expected_result == result # ... and again, but as a parameter result = operations.local(command, warn_only=True) assert expected_result == result
def test_formatted_output_display_prefix(): "a line of output can have it's prefix stripped" cases = [ # (line template, what is printed to given pipe (stringbuffer, stdout, stderr, etc), what is returned to user) ("{line}", "hello, world", "hello, world"), ("{host} {pipe}: {line}", "hello, world", "hello, world"), ("{host} {pipe}: ", "1.2.3.4 out: ", "hello, world"), ("{line} {host}", "hello, world 1.2.3.4", "hello, world"), ("{line} {line}", "hello, world hello, world", "hello, world"), ] for given_template, expected_stdout, expected_return in cases: strbuffer = StringIO() settings = { "host_string": "1.2.3.4", "line_template": given_template, "display_prefix": False, } # suppress warning about a missing '{line}' in `line_template` (case 3) with patch("threadbare.operations.LOG.warn"): with state.settings(**settings): result = operations._print_line(strbuffer, "hello, world") assert expected_stdout == strbuffer.getvalue() assert expected_return == result
def fn(): with settings() as local_env: return "foo" + local_env["mykey"]
def workerfn(): with settings(abort_on_prompts=False): return operations.prompt("gimmie")
def workerfn(): with settings() as env: return env["host_string"] + "host"
def fn(): with settings() as env: return env
def fn(): with settings() as local_env: return local_env
def workerfn(): with state.settings() as env: return remote_file_exists(env["remote_file"], use_sudo=True)
def workerfn(): with state.settings(): return remote("exit 1")
def test_abort_sysexit(): "attempt to exit application when `abort_exception` is disabled." result = {"foo": "bar"} with state.settings(abort_exception=None): with pytest.raises(SystemExit): operations.abort(result, "failed to succeed")
def test_abort_warn_only(): "return given result if `settings.warn_only` is `True`" expected = result = {"foo": "bar"} with state.settings(warn_only=True): actual = operations.abort(result, "failed to succeed") assert expected == actual