From c7bfd05b89a768c68b8d4b3b9f0d2f4a63c68900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20Hedenstro=CC=88m?= <erik@hedenstroem.com> Date: Tue, 4 Oct 2016 15:14:16 +0200 Subject: [PATCH] Moved generic docker test steps to dockerl project --- apps/consul_proxy/test/common_steps.erl | 108 +++--------------- .../test/feature_consul_proxy.erl | 58 +++++++--- features/consul_proxy.feature | 4 +- rebar.config | 2 +- 4 files changed, 65 insertions(+), 107 deletions(-) diff --git a/apps/consul_proxy/test/common_steps.erl b/apps/consul_proxy/test/common_steps.erl index 8a28a1d..5505366 100644 --- a/apps/consul_proxy/test/common_steps.erl +++ b/apps/consul_proxy/test/common_steps.erl @@ -3,13 +3,11 @@ %% Exported functions -export([ - setup_feature/1, + setup_feature/2, setup_scenario/2, teardown_feature/1, teardown_scenario/1, - given/2, - 'when'/2, - then/2 + given/2 ]). -include("consul_proxy.hrl"). @@ -18,85 +16,45 @@ %%==================================================================== %% Exported functions %%==================================================================== -setup_feature(_Tokens) -> +setup_feature(_Tokens, State) -> Env = lists:map( fun(Env) -> [K, V] = binary:split(list_to_binary(Env), <<"=">>), {K, V} end, os:getenv()), - lager:debug("~p", [Env]), - application:ensure_all_started(hackney), - {ok, Pid} = dockerl:start_link(socket, <<"/var/run/docker.sock">>), IPAddress = list_to_binary(inet:ntoa(local_ip_v4())), - {ok, #{os_env => Env, test_env => [{<<"ip_address">>, IPAddress}], applications => [], containers => [], dockerl_pid => Pid}}. + {ok, State#{{?MODULE, env} => Env, {?MODULE, test_env} => [{<<"ip_address">>, IPAddress}], {?MODULE, applications} => []}}. -teardown_feature(#{dockerl_pid := Pid, containers := Containers, applications := Applications}) -> +teardown_feature(State = #{{?MODULE, applications} := Applications}) -> [application:stop(Application) || Application <- lists:reverse(Applications)], - lists:foreach( - fun({Name, Id}) -> - ok = dockerl:stop_container(Pid, Id), - lager:notice("Docker container ~s stopped", [Name]), - ok = dockerl:remove_container(Pid, Id), - lager:notice("Docker container ~s removed", [Name]) - end, Containers), - ok; -teardown_feature(_State) -> - ok. + {ok, State}; +teardown_feature(State) -> + {ok, State}. setup_scenario(_Tokens, State) -> {ok, State}. -teardown_scenario(_State) -> - ok. +teardown_scenario(State) -> + {ok, State}. %% noinspection ErlangUnboundVariable -given([<<"a">>, <<"docker">>, <<"container">>, <<"named">>, Name, <<"running">>, Image, <<"with">>, <<"commands:">>, {docstring, Args}], State = #{dockerl_pid := Pid, containers := Containers}) -> - InterpolatedImage = consul_proxy_utils:string_interpolate(Image, maps:get(os_env, State, []) ++ maps:get(test_env, State, []), []), - {ok, _} = dockerl:pull_image(Pid, Image), - {ok, Id} = dockerl:create_container(Pid, Image, - #{ - 'Tty' => true, - 'Cmd' => binary:split(Args, <<" ">>, [trim_all, global]), - 'PublishAllPorts' => true - }), - lager:notice("Docker container ~s running ~s created: ~s", [Name, InterpolatedImage, Id]), - ok = dockerl:start_container(Pid, Id), - lager:notice("Docker container ~s started", [Id]), - {ok, State#{containers => [{Name, Id} | Containers]}}; - -given("$Name logs match $Pattern", State = #{dockerl_pid := Pid, containers := Containers}) -> - Id = proplists:get_value(Name, Containers), - {ok, Stream} = dockerl:container_logs(Pid, Id), - ok = match_logs(Stream, Pattern), - {ok, State}; - -given("consul_proxy is connected to $Name port $Port", State = #{dockerl_pid := Pid, containers := Containers, applications := Applications}) -> - Id = proplists:get_value(Name, Containers), - {ok, Ports} = dockerl_utils:get_ports(Pid, Id), - [{_, IntPort}] = maps:get(Port, Ports), - BinPort = integer_to_binary(IntPort), - BinAddress = case os:type() of - {unix, darwin} -> %% Todo: remove this hack once routing is fixed in Docker for Mac - <<"127.0.0.1">>; - _ -> - {ok, Gateway} = dockerl_utils:get_gateway(Pid, Id), - list_to_binary(inet:ntoa(Gateway)) - end, +given("consul_proxy is connected to $Name container port $Port", State = #{{?MODULE, applications} := Applications}) -> + {BinAddress, BinPort} = dockerl_steps:get_port(Name, Port, State), URL = binary_to_list(<<"http://", BinAddress/binary, ":", BinPort/binary>>), lager:notice("Consul URL: ~s", [URL]), ok = application:set_env(consul_proxy, consul_urls, URL, [{timeout, infinity}, {persistent, true}]), case application:ensure_all_started(consul_proxy) of {ok, Started} -> lager:notice("Started ~p applications: ~p on ~p~n", [erlang:length(Started), Started, node()]), - {ok, HttpcPid} = inets:start(httpc, [{profile, consul_proxied}]), + {ok, _} = inets:start(httpc, [{profile, consul_proxied}]), httpc:set_options([{proxy, {{"localhost", 8080}, []}}], consul_proxied), - {ok, State#{consul_proxied_httpc => HttpcPid, applications => Applications ++ Started}}; + {ok, State#{{?MODULE, applications} => Applications ++ Started}}; {error, Reason} -> lager:error("~p", [Reason]), {error, Reason} end; -given("a UDP listener registered as $ServiceName", State = #{test_env := TestEnv}) -> +given("a UDP listener registered as $ServiceName", State = #{{?MODULE, test_env} := TestEnv}) -> {ok, Socket} = gen_udp:open(0, [binary, {ip, {0, 0, 0, 0}}, {active, false}]), {ok, IntPort} = inet:port(Socket), BinPort = integer_to_binary(IntPort), @@ -107,7 +65,7 @@ given("a UDP listener registered as $ServiceName", State = #{test_env := TestEnv spawn(fun() -> udp_receive(Socket) end), {ok, State#{test_env => [{<<ServiceName/binary, "_port">>, BinPort} | TestEnv]}}; -given("an HTTP Server with root $Root and handlers $Handlers registered as $ServiceName", State = #{test_env := TestEnv}) -> +given("an HTTP Server with root $Root and handlers $Handlers registered as $ServiceName", State = #{{?MODULE, test_env} := TestEnv}) -> Modules = [binary_to_atom(Handler, utf8) || Handler <- binary:split(Handlers, <<",">>, [trim_all, global])], ServiceConfig = [ {bind_address, "0.0.0.0"}, @@ -130,7 +88,6 @@ given("an HTTP Server with root $Root and handlers $Handlers registered as $Serv given([<<"consul">>, <<"client">>, <<"has">>, <<"set">>, Key, <<"to:">>, {docstring, Value}], State) -> InterpolatedValue = consul_proxy_utils:string_interpolate(Value, maps:get(os_env, State, []) ++ maps:get(test_env, State, []), []), - lager:debug("~p -> ~p", [Key, InterpolatedValue]), case consul_client:set(Key, InterpolatedValue) of {ok, true} -> {ok, State}; @@ -153,42 +110,11 @@ given("consul client has loaded values from $Filename", State) -> end end, KVData), lager:debug("~p keys set", [length(R)]), - {ok, State}; - -given(Tokens, _State) -> - lager:warning("Unhandled tokens: ~p", [Tokens]), - {error, lists:flatten(io_lib:format("Unhandled tokens: ~p", [Tokens]))}. - -'when'(Tokens, _State) -> - lager:warning("Unhandled tokens: ~p", [Tokens]), - {error, lists:flatten(io_lib:format("Unhandled tokens: ~p", [Tokens]))}. - -then(Tokens, _State) -> - lager:warning("Unhandled tokens: ~p", [Tokens]), - {error, lists:flatten(io_lib:format("Unhandled tokens: ~p", [Tokens]))}. + {ok, State}. %%=================================================================== %% Internal functions %%=================================================================== - -match_logs(Stream, Pattern) -> - receive - done -> - {error, no_match}; - Msg -> - case re:run(Msg, Pattern) of - {match, _} -> - Stream ! stop, - ok; - _ -> - match_logs(Stream, Pattern) - end - after - 10000 -> - Stream ! stop, - {error, timeout} - end. - udp_receive(Socket) -> case gen_udp:recv(Socket, 0, 500) of {ok, {Address, Port, Packet}} -> diff --git a/apps/consul_proxy/test/feature_consul_proxy.erl b/apps/consul_proxy/test/feature_consul_proxy.erl index 3ba9c35..7709eb5 100644 --- a/apps/consul_proxy/test/feature_consul_proxy.erl +++ b/apps/consul_proxy/test/feature_consul_proxy.erl @@ -19,20 +19,21 @@ %% Exported functions %%==================================================================== setup_feature(Tokens) -> - common_steps:setup_feature(Tokens). + Pipeline = build_pipeline([common_steps, dockerl_steps]), + Pipeline(?FUNCTION_NAME, Tokens, #{{?MODULE, pipeline} => Pipeline}). -teardown_feature(State) -> - common_steps:teardown_feature(State). +teardown_feature(State = #{{?MODULE, pipeline} := Pipeline}) -> + Pipeline(?FUNCTION_NAME, no_tokens, State). -setup_scenario(Tokens, State) -> - common_steps:setup_scenario(Tokens, State). +setup_scenario(Tokens, State = #{{?MODULE, pipeline} := Pipeline}) -> + Pipeline(?FUNCTION_NAME, Tokens, State). -teardown_scenario(State) -> - common_steps:teardown_scenario(State). +teardown_scenario(State = #{{?MODULE, pipeline} := Pipeline}) -> + Pipeline(?FUNCTION_NAME, no_tokens, State). %% noinspection ErlangUnboundVariable -given(Tokens, State) -> - common_steps:given(Tokens, State). +given(Tokens, State = #{{?MODULE, pipeline} := Pipeline}) -> + Pipeline(?FUNCTION_NAME, Tokens, State). %% noinspection ErlangUnboundVariable 'when'("the consul client lists nodes", State) -> @@ -67,8 +68,8 @@ given(Tokens, State) -> }, {ok, State#{response => Response}}; -'when'(Tokens, State) -> - common_steps:'when'(Tokens, State). +'when'(Tokens, State = #{{?MODULE, pipeline} := Pipeline}) -> + Pipeline(?FUNCTION_NAME, Tokens, State). %% noinspection ErlangUnboundVariable then("the result should have $Count $Unit", State = #{result := Result}) -> @@ -98,9 +99,40 @@ then("the status code should be $ExpectedCode", State = #{response := #{status_c {error, lists:flatten(io_lib:format("Received ~p", [StatusCode]))} end; -then(Tokens, State) -> - common_steps:then(Tokens, State). +then(Tokens, State = #{{?MODULE, pipeline} := Pipeline}) -> + Pipeline(?FUNCTION_NAME, Tokens, State). %%=================================================================== %% Internal functions %%=================================================================== +build_pipeline(Modules) -> + fun(Step, Tokens, State) -> + {Result, Matches} = lists:foldl( + fun + (Module, {{ok, StateIn}, Matches}) -> + try + case Tokens of + no_tokens -> + {Module:Step(StateIn), Matches + 1}; + _ -> + {Module:Step(Tokens, StateIn), Matches + 1} + end + catch + error:undef -> + {{ok, StateIn}, Matches}; + error:function_clause -> + {{ok, StateIn}, Matches}; + Class:ExceptionPattern -> + {{Class, ExceptionPattern, erlang:get_stacktrace()}, Matches} + end; + (_Module, Error) -> + Error + end, {{ok, State}, 0}, Modules), + case Matches of + 0 -> + lager:warning("Unhandled tokens: ~p", [Tokens]), + {error, lists:flatten(io_lib:format("Unhandled tokens: ~p", [Tokens]))}; + _ -> + Result + end + end. diff --git a/features/consul_proxy.feature b/features/consul_proxy.feature index 04d0bff..b98831d 100644 --- a/features/consul_proxy.feature +++ b/features/consul_proxy.feature @@ -8,8 +8,8 @@ Feature: Proxy functionality """ agent -dev -ui-dir /ui -client 0.0.0.0 """ - And consul logs match "Synced service 'consul'" - And consul_proxy is connected to consul port 8500/tcp + And consul container logs match "Synced service 'consul'" + And consul_proxy is connected to consul container port 8500/tcp And a UDP listener registered as logstash And an HTTP Server with root "apps/consul_proxy/priv" and handlers "eunit_www" registered as eunit And consul client has set consul_proxy/watchers/kv to: diff --git a/rebar.config b/rebar.config index 1694dcb..0e11059 100644 --- a/rebar.config +++ b/rebar.config @@ -40,7 +40,7 @@ {test, [ {deps, [ {gurka, "0.1.7"}, - {dockerl, {git, "https://gitlab.hedenstroem.com/erlang-ninja/dockerl.git", {tag, "0.1.0"}}} + {dockerl, {git, "https://gitlab.hedenstroem.com/erlang-ninja/dockerl.git", {tag, "0.2.0"}}} ]}, {eunit_opts, [{report, {eunit_surefire, [{dir, "_build/test"}]}}]}, {erl_opts, [debug_info, nowarn_unused_vars]} -- GitLab