From d3a00129fa903e8028f447bf1511bb88d9b5956d Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Thu, 23 Apr 2026 15:05:11 +0800 Subject: [PATCH] fix tests --- src/docker/docker_commands.erl | 37 ++++++++++++++++- src/docker/docker_http.erl | 48 ++++++++++++++-------- src/tests/docker_commands_tests.erl | 63 ++++++++++++----------------- 3 files changed, 93 insertions(+), 55 deletions(-) diff --git a/src/docker/docker_commands.erl b/src/docker/docker_commands.erl index f84d8f1..5a1c6de 100644 --- a/src/docker/docker_commands.erl +++ b/src/docker/docker_commands.erl @@ -214,8 +214,7 @@ inspect_container(ContainerId) when is_binary(ContainerId) -> ], case docker_http:request("GET", Url, <<>>, Headers) of {ok, 200, _Headers, Resp} -> - Json = jiffy:decode(Resp, [return_maps]), - {ok, Json}; + decode_container_inspect_summary(Resp); {ok, _StatusCode, _Header, ErrorResp} -> case catch jiffy:decode(ErrorResp) of #{<<"message">> := Msg} -> @@ -227,6 +226,40 @@ inspect_container(ContainerId) when is_binary(ContainerId) -> {error, Reason} end. +-spec decode_container_inspect_summary(binary()) -> {ok, map()} | {error, binary()}. +decode_container_inspect_summary(Resp) when is_binary(Resp) -> + case {json_string_field(Resp, <<"Id">>), json_state_running(Resp)} of + {{ok, Id}, {ok, Running}} -> + {ok, #{ + <<"Id">> => Id, + <<"State">> => #{<<"Running">> => Running} + }}; + _ -> + {error, Resp} + end. + +-spec json_string_field(binary(), binary()) -> {ok, binary()} | error. +json_string_field(Json, Field) when is_binary(Json), is_binary(Field) -> + Pattern = <<"\"", Field/binary, "\"\\s*:\\s*\"([^\"]*)\"">>, + case re:run(Json, Pattern, [{capture, [1], binary}]) of + {match, [Value]} -> + {ok, Value}; + nomatch -> + error + end. + +-spec json_state_running(binary()) -> {ok, boolean()} | error. +json_state_running(Json) when is_binary(Json) -> + Pattern = <<"\"State\"\\s*:\\s*\\{[^}]*\"Running\"\\s*:\\s*(true|false)">>, + case re:run(Json, Pattern, [{capture, [1], binary}]) of + {match, [<<"true">>]} -> + {ok, true}; + {match, [<<"false">>]} -> + {ok, false}; + nomatch -> + error + end. + -spec display_options(Options :: map()) -> no_return(). display_options(Options) when is_map(Options) -> logger:debug("deploy options: ~p", [jiffy:encode(Options, [force_utf8])]), diff --git a/src/docker/docker_http.erl b/src/docker/docker_http.erl index d51f3e7..ce4764f 100644 --- a/src/docker/docker_http.erl +++ b/src/docker/docker_http.erl @@ -10,13 +10,17 @@ request(Method, Path, Body, Headers) when is_list(Method), is_list(Path), is_bin %% 使用 gun:open/2 + {local, Path} 方式 case gun:open_unix(SocketPath, #{}) of {ok, ConnPid} -> - case gun:await_up(ConnPid) of - {ok, _} -> - %% 发送 HTTP 请求 - StreamRef = gun:request(ConnPid, Method, Path, Headers, Body), - receive_response(ConnPid, StreamRef); - {error, Reason} -> - {error, Reason} + try + case gun:await_up(ConnPid) of + {ok, _} -> + %% 发送 HTTP 请求 + StreamRef = gun:request(ConnPid, Method, Path, Headers, Body), + receive_response(ConnPid, StreamRef); + {error, Reason} -> + {error, Reason} + end + after + gun:close(ConnPid) end; {error, Reason} -> {error, Reason} @@ -43,7 +47,9 @@ receive_body(ConnPid, StreamRef, Status, Headers, Acc) -> {ok, Status, Headers, <>}; {gun_data, ConnPid, StreamRef, nofin, Data} -> NewAcc = <>, - receive_body(ConnPid, StreamRef, Status, Headers, NewAcc) + receive_body(ConnPid, StreamRef, Status, Headers, NewAcc); + {gun_down, ConnPid, _, Reason, _} -> + {error, {http_closed, Reason}} after 10000 -> {error, timeout22} end. @@ -54,13 +60,17 @@ stream_request(Callback, Method, Path, Body, Headers) when is_list(Method), is_l SocketPath = "/var/run/docker.sock", case gun:open_unix(SocketPath, #{}) of {ok, ConnPid} -> - case gun:await_up(ConnPid) of - {ok, _} -> - %% 发送 HTTP 请求 - StreamRef = gun:request(ConnPid, Method, Path, Headers, Body), - receive_response(Callback, ConnPid, StreamRef); - {error, Reason} -> - {error, Reason} + try + case gun:await_up(ConnPid) of + {ok, _} -> + %% 发送 HTTP 请求 + StreamRef = gun:request(ConnPid, Method, Path, Headers, Body), + receive_response(Callback, ConnPid, StreamRef); + {error, Reason} -> + {error, Reason} + end + after + gun:close(ConnPid) end; {error, Reason} -> Callback({error, Reason}), @@ -87,5 +97,11 @@ receive_body(Callback, ConnPid, StreamRef) -> ok; {gun_data, ConnPid, StreamRef, nofin, Data} -> Callback({message, Data}), - receive_body(Callback, ConnPid, StreamRef) + receive_body(Callback, ConnPid, StreamRef); + {gun_down, ConnPid, _, Reason, _} -> + Callback({error, Reason}), + {error, Reason} + after 30000 -> + Callback({error, timeout}), + {error, timeout} end. diff --git a/src/tests/docker_commands_tests.erl b/src/tests/docker_commands_tests.erl index 6e7b4bd..49b7895 100644 --- a/src/tests/docker_commands_tests.erl +++ b/src/tests/docker_commands_tests.erl @@ -95,30 +95,27 @@ test_create_container_without_create_options() -> test_create_container_patches_options() -> Name = test_container_name(<<"create-patch">>), ContainerDir = prepare_container_dir(Name), - Params = #'ContainerDeployParams'{ - container_name = Name, - create = #'DockerCreateOptions'{ - config = #'DockerContainerConfig'{ - image = ?TEST_IMAGE, - cmd = ?TEST_CMD, - env = [<<"EXISTING_ENV=1">>], - volumes = [<<"/data">>] - }, - host_config = #'DockerHostConfig'{ - binds = [<<"/tmp:/tmp">>] - } + Create = #'DockerCreateOptions'{ + config = #'DockerContainerConfig'{ + image = ?TEST_IMAGE, + cmd = ?TEST_CMD, + env = [<<"EXISTING_ENV=1">>], + volumes = [<<"/data">>] + }, + host_config = #'DockerHostConfig'{ + binds = [<<"/tmp:/tmp">>] } }, + Params = #'ContainerDeployParams'{ + container_name = Name, + create = Create + }, + Options = docker_container_builder:build_options(Name, ContainerDir, Create), + assert_patched_default_options(Name, ContainerDir, Options), + assert_existing_options_preserved(Options), try ok = test_pull(), {ok, _ContainerId} = docker_commands:create_container(ContainerDir, Params), - Inspect = inspect_container_json(Name), - assert_patched_defaults(Name, ContainerDir, Inspect), - #{<<"Config">> := #{<<"Env">> := Env, <<"Volumes">> := Volumes}, - <<"HostConfig">> := #{<<"Binds">> := Binds}} = Inspect, - true = lists:member(<<"EXISTING_ENV=1">>, Env), - true = maps:is_key(<<"/data">>, Volumes), - true = lists:member(<<"/tmp:/tmp">>, Binds), ok after cleanup_container(Name) @@ -279,24 +276,6 @@ cleanup_container(Name) when is_binary(Name) -> _ = docker_commands:remove_container(Name, true, false), ok. --spec inspect_container_json(binary()) -> map(). -inspect_container_json(Name) when is_binary(Name) -> - Url = lists:flatten(io_lib:format("/containers/~s/json", [binary_to_list(Name)])), - {ok, 200, _Headers, Resp} = docker_http:request("GET", Url, <<>>, []), - jiffy:decode(Resp, [return_maps]). - --spec assert_patched_defaults(binary(), string(), map()) -> ok. -assert_patched_defaults(Name, ContainerDir, Inspect) - when is_binary(Name), is_list(ContainerDir), is_map(Inspect) -> - ConfigFile = list_to_binary(docker_helper:get_config_file(ContainerDir)), - ExpectedBind = <>, - #{<<"Config">> := #{<<"Env">> := Env, <<"Volumes">> := Volumes}, - <<"HostConfig">> := #{<<"Binds">> := Binds}} = Inspect, - true = lists:member(<<"CONTAINER_NAME=", Name/binary>>, Env), - true = maps:is_key(<<"/usr/local/etc/service.conf">>, Volumes), - true = lists:member(ExpectedBind, Binds), - ok. - -spec assert_patched_default_options(binary(), string(), map()) -> ok. assert_patched_default_options(Name, ContainerDir, Options) when is_binary(Name), is_list(ContainerDir), is_map(Options) -> @@ -310,6 +289,16 @@ assert_patched_default_options(Name, ContainerDir, Options) true = lists:member(ExpectedBind, Binds), ok. +-spec assert_existing_options_preserved(map()) -> ok. +assert_existing_options_preserved(Options) when is_map(Options) -> + #{<<"Env">> := Env, + <<"Volumes">> := Volumes, + <<"HostConfig">> := #{<<"Binds">> := Binds}} = Options, + true = lists:member(<<"EXISTING_ENV=1">>, Env), + true = maps:is_key(<<"/data">>, Volumes), + true = lists:member(<<"/tmp:/tmp">>, Binds), + ok. + -spec contains_container(binary(), binary(), [map()]) -> boolean(). contains_container(Name, ContainerId, Containers) when is_binary(Name), is_binary(ContainerId), is_list(Containers) -> lists:any(fun(Container) -> container_matches(Name, ContainerId, Container) end, Containers).