Current section
Files
Jump to
Current section
Files
src/testcontainer@internal@wait_runner.erl
-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).