-module(testcontainer@internal@wait_runner). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/testcontainer/internal/wait_runner.gleam"). -export([run/3]). -export_type([port_binding/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -type port_binding() :: {port_binding, binary()}. -file("src/testcontainer/internal/wait_runner.gleam", 39). ?DOC(false). -spec port_binding_decoder() -> gleam@dynamic@decode:decoder(port_binding()). port_binding_decoder() -> gleam@dynamic@decode:field( <<"HostPort"/utf8>>, {decoder, fun gleam@dynamic@decode:decode_string/1}, fun(Hp) -> gleam@dynamic@decode:success({port_binding, Hp}) end ). -file("src/testcontainer/internal/wait_runner.gleam", 44). ?DOC(false). -spec ports_decoder() -> gleam@dynamic@decode:decoder(gleam@dict:dict(binary(), gleam@option:option(list(port_binding())))). ports_decoder() -> gleam@dynamic@decode:at( [<<"NetworkSettings"/utf8>>, <<"Ports"/utf8>>], gleam@dynamic@decode:dict( {decoder, fun gleam@dynamic@decode:decode_string/1}, gleam@dynamic@decode:optional( gleam@dynamic@decode:list(port_binding_decoder()) ) ) ). -file("src/testcontainer/internal/wait_runner.gleam", 251). ?DOC(false). -spec check_command(binary(), list(binary()), integer()) -> {ok, nil} | {error, testcontainer@error:error()}. check_command(Container_id, Cmd, Expected_exit) -> case testcontainer@internal@docker:exec_container(Container_id, Cmd) of {ok, {exec_result, Exit_code, _, _}} -> case Exit_code =:= Expected_exit of true -> {ok, nil}; false -> {error, {wait_failed, <<<<"command("/utf8, (gleam@string:join(Cmd, <<" "/utf8>>))/binary>>/binary, ")"/utf8>>, <<"exit="/utf8, (erlang:integer_to_binary(Exit_code))/binary>>}} end; {error, E} -> {error, E} end. -file("src/testcontainer/internal/wait_runner.gleam", 231). ?DOC(false). -spec check_health(gleam@option:option(binary())) -> {ok, nil} | {error, testcontainer@error:error()}. check_health(Inspect) -> case Inspect of none -> {error, {wait_failed, <<"health_check"/utf8>>, <<"unable to inspect container"/utf8>>}}; {some, Body} -> case gleam@json:parse( Body, gleam@dynamic@decode:at( [<<"State"/utf8>>, <<"Health"/utf8>>, <<"Status"/utf8>>], {decoder, fun gleam@dynamic@decode:decode_string/1} ) ) of {ok, <<"healthy"/utf8>>} -> {ok, nil}; {ok, Status} -> {error, {wait_failed, <<"health_check"/utf8>>, <<"status="/utf8, Status/binary>>}}; {error, _} -> {error, {wait_failed, <<"health_check"/utf8>>, <<"no health status in inspect"/utf8>>}} end end. -file("src/testcontainer/internal/wait_runner.gleam", 304). ?DOC(false). -spec resolve_host_port(integer(), gleam@dict:dict(integer(), integer())) -> {ok, integer()} | {error, testcontainer@error:error()}. resolve_host_port(Container_port, Port_map) -> case gleam_stdlib:map_get(Port_map, Container_port) of {ok, Hp} -> {ok, Hp}; {error, nil} -> {error, {port_not_mapped, Container_port}} end. -file("src/testcontainer/internal/wait_runner.gleam", 202). ?DOC(false). -spec check_http( integer(), binary(), integer(), binary(), gleam@dict:dict(integer(), integer()) ) -> {ok, nil} | {error, testcontainer@error:error()}. check_http(Container_port, Path, Expected_status, Host, Port_map) -> gleam@result:'try'( resolve_host_port(Container_port, Port_map), fun(Host_port) -> case docker_transport:http_get_status(Host, Host_port, Path) of {ok, Status} -> case Status =:= Expected_status of true -> {ok, nil}; false -> {error, {wait_failed, <<<<<<<<"http("/utf8, (erlang:integer_to_binary( Container_port ))/binary>>/binary, ", "/utf8>>/binary, Path/binary>>/binary, ")"/utf8>>, <<<<<<"got HTTP "/utf8, (erlang:integer_to_binary( Status ))/binary>>/binary, ", want "/utf8>>/binary, (erlang:integer_to_binary( Expected_status ))/binary>>}} end; {error, Reason} -> {error, {wait_failed, <<<<<<<<"http("/utf8, (erlang:integer_to_binary( Container_port ))/binary>>/binary, ", "/utf8>>/binary, Path/binary>>/binary, ")"/utf8>>, Reason}} end end ). -file("src/testcontainer/internal/wait_runner.gleam", 186). ?DOC(false). -spec check_port(integer(), binary(), gleam@dict:dict(integer(), integer())) -> {ok, nil} | {error, testcontainer@error:error()}. check_port(Container_port, Host, Port_map) -> gleam@result:'try'( resolve_host_port(Container_port, Port_map), fun(Host_port) -> case docker_transport:tcp_can_connect(Host, Host_port) of {ok, nil} -> {ok, nil}; {error, Reason} -> {error, {wait_failed, <<<<"port("/utf8, (erlang:integer_to_binary(Container_port))/binary>>/binary, ")"/utf8>>, Reason}} end end ). -file("src/testcontainer/internal/wait_runner.gleam", 179). ?DOC(false). -spec count_occurrences(binary(), binary()) -> integer(). count_occurrences(Haystack, Needle) -> case gleam@string:split(Haystack, Needle) of [_] -> 0; Parts -> erlang:length(Parts) - 1 end. -file("src/testcontainer/internal/wait_runner.gleam", 158). ?DOC(false). -spec check_log(binary(), binary(), integer()) -> {ok, nil} | {error, testcontainer@error:error()}. check_log(Container_id, Message, Times) -> case testcontainer@internal@docker:container_logs(Container_id, none) of {ok, Logs} -> Count = count_occurrences(Logs, Message), case Count >= Times of true -> {ok, nil}; false -> {error, {wait_failed, <<<<"log("/utf8, Message/binary>>/binary, ")"/utf8>>, <<<<<<"found "/utf8, (erlang:integer_to_binary(Count))/binary>>/binary, "/"/utf8>>/binary, (erlang:integer_to_binary(Times))/binary>>}} end; {error, E} -> {error, E} end. -file("src/testcontainer/internal/wait_runner.gleam", 283). ?DOC(false). -spec check_any_of( list(testcontainer@wait:wait_strategy()), binary(), binary(), gleam@dict:dict(integer(), integer()), gleam@option:option(binary()) ) -> {ok, nil} | {error, testcontainer@error:error()}. check_any_of(Strategies, Container_id, Host, Port_map, Inspect) -> case Strategies of [] -> {error, {wait_failed, <<"any_of"/utf8>>, <<"no strategies provided"/utf8>>}}; [First | Rest] -> case check_once(First, Container_id, Host, Port_map, Inspect) of {ok, nil} -> {ok, nil}; {error, _} -> check_any_of(Rest, Container_id, Host, Port_map, Inspect) end end. -file("src/testcontainer/internal/wait_runner.gleam", 270). ?DOC(false). -spec check_all_of( list(testcontainer@wait:wait_strategy()), binary(), binary(), gleam@dict:dict(integer(), integer()), gleam@option:option(binary()) ) -> {ok, nil} | {error, testcontainer@error:error()}. check_all_of(Strategies, Container_id, Host, Port_map, Inspect) -> _pipe = gleam@list:try_map( Strategies, fun(S) -> check_once(S, Container_id, Host, Port_map, Inspect) end ), gleam@result:map(_pipe, fun(_) -> nil end). -file("src/testcontainer/internal/wait_runner.gleam", 131). ?DOC(false). -spec check_once( testcontainer@wait:wait_strategy(), binary(), binary(), gleam@dict:dict(integer(), integer()), gleam@option:option(binary()) ) -> {ok, nil} | {error, testcontainer@error:error()}. check_once(Strategy, Container_id, Host, Port_map, Inspect) -> case testcontainer@wait:base(Strategy) of for_none -> {ok, nil}; {for_log, Message, Times} -> check_log(Container_id, Message, Times); {for_port, Container_port} -> check_port(Container_port, Host, Port_map); {for_http, Container_port@1, Path, Expected_status} -> check_http(Container_port@1, Path, Expected_status, Host, Port_map); for_health_check -> check_health(Inspect); {for_command, Cmd, Expected_exit} -> check_command(Container_id, Cmd, Expected_exit); {all_of, Strategies} -> check_all_of(Strategies, Container_id, Host, Port_map, Inspect); {any_of, Strategies@1} -> check_any_of(Strategies@1, Container_id, Host, Port_map, Inspect) end. -file("src/testcontainer/internal/wait_runner.gleam", 345). ?DOC(false). -spec first_host_port(list(port_binding())) -> {ok, integer()} | {error, nil}. first_host_port(Bs) -> case Bs of [] -> {error, nil}; [{port_binding, Hp} | _] -> gleam_stdlib:parse_int(Hp) end. -file("src/testcontainer/internal/wait_runner.gleam", 338). ?DOC(false). -spec parse_tcp_key(binary()) -> {ok, integer()} | {error, nil}. parse_tcp_key(Key) -> case gleam@string:split(Key, <<"/"/utf8>>) of [P, <<"tcp"/utf8>>] -> gleam_stdlib:parse_int(P); _ -> {error, nil} end. -file("src/testcontainer/internal/wait_runner.gleam", 325). ?DOC(false). -spec parse_entry({binary(), gleam@option:option(list(port_binding()))}) -> {ok, {integer(), integer()}} | {error, nil}. parse_entry(Entry) -> {Key, Bindings} = Entry, gleam@result:'try'( parse_tcp_key(Key), fun(Container_port) -> gleam@result:'try'(case Bindings of {some, Value} -> {ok, Value}; none -> {error, nil} end, fun(Bs) -> gleam@result:'try'( first_host_port(Bs), fun(Host_port) -> {ok, {Container_port, Host_port}} end ) end) end ). -file("src/testcontainer/internal/wait_runner.gleam", 314). ?DOC(false). -spec parse_port_map(binary()) -> gleam@dict:dict(integer(), integer()). parse_port_map(Inspect_json) -> case gleam@json:parse(Inspect_json, ports_decoder()) of {ok, Raw} -> _pipe = Raw, _pipe@1 = maps:to_list(_pipe), _pipe@2 = gleam@list:filter_map(_pipe@1, fun parse_entry/1), maps:from_list(_pipe@2); {error, _} -> maps:new() end. -file("src/testcontainer/internal/wait_runner.gleam", 80). ?DOC(false). -spec poll_loop( testcontainer@wait:wait_strategy(), binary(), binary(), gleam@dict:dict(integer(), integer()), integer(), integer(), integer() ) -> {ok, nil} | {error, testcontainer@error:error()}. poll_loop(Strategy, Container_id, Host, Port_map, Start_ms, Deadline, Poll_ms) -> Now = docker_transport:now_ms(), case Now >= Deadline of true -> Elapsed = Now - Start_ms, {error, {wait_timed_out, testcontainer@wait:describe(Strategy), Elapsed}}; false -> Inspect = case testcontainer@internal@docker:inspect_container( Container_id ) of {ok, Body} -> {some, Body}; {error, _} -> none end, Pm = case {gleam@dict:is_empty(Port_map), Inspect} of {true, {some, Body@1}} -> parse_port_map(Body@1); {_, _} -> Port_map end, case check_once(Strategy, Container_id, Host, Pm, Inspect) of {ok, nil} -> {ok, nil}; {error, _} -> docker_transport:sleep_ms(Poll_ms), poll_loop( Strategy, Container_id, Host, Pm, Start_ms, Deadline, Poll_ms ) end end. -file("src/testcontainer/internal/wait_runner.gleam", 65). ?DOC(false). -spec run(testcontainer@wait:wait_strategy(), binary(), binary()) -> {ok, nil} | {error, testcontainer@error:error()}. run(Strategy, Container_id, Host) -> Start = docker_transport:now_ms(), Deadline = Start + testcontainer@wait:timeout_ms(Strategy), Poll = testcontainer@wait:poll_interval_ms(Strategy), poll_loop(Strategy, Container_id, Host, maps:new(), Start, Deadline, Poll).