From a788945e55130cfa795e04ae4e774cddfff976d1 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 20 Sep 2024 13:53:43 -0300 Subject: [PATCH 1/5] feat: add http proxy support --- changelog.md | 17 +++++ src/ehttpc.erl | 154 +++++++++++++++++++++++++++++++++++++++--- test/ehttpc_tests.erl | 11 +-- 3 files changed, 166 insertions(+), 16 deletions(-) diff --git a/changelog.md b/changelog.md index 3eb5428..636bfd3 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,22 @@ # ehttpc changes +## 0.4.15 + +- Added support for using HTTP proxy (HTTP 1.1 only). + To use it, pass `proxy` in the pool opts. + + Ex: + + ```erlang + %% Point to the proxy host and port + ProxyOpts = #{host => "127.0.0.1", port => 8888}. + ehttpc_sup:start_pool(<<"pool">>, [{host, "target.host.com"}, {port, 80}, {proxy, ProxyOpts}]). + + %% To use username and password + ProxyOpts = #{host => "127.0.0.1", port => 8888, username => "proxyuser", password => "secret"}. + ehttpc_sup:start_pool(<<"pool">>, [{host, "target.host.com"}, {port, 80}, {proxy, ProxyOpts}]). + ``` + ## 0.4.14 - Forcefully recreate `gproc_pool`s during `ehttpc_pool:init/1` to prevent reusing pools in an inconsistent state. diff --git a/src/ehttpc.erl b/src/ehttpc.erl index 6b354fd..be6d6b3 100644 --- a/src/ehttpc.erl +++ b/src/ehttpc.erl @@ -90,7 +90,8 @@ enable_pipelining :: boolean() | non_neg_integer(), gun_opts :: gun:opts(), gun_state :: down | up, - requests :: map() + requests :: map(), + proxy :: undefined | map() }). -type pool_name() :: any(). @@ -195,9 +196,10 @@ name(Pool) -> {?MODULE, Pool}. %% gen_server callbacks %%-------------------------------------------------------------------- -init([Pool, Id, Opts]) -> +init([Pool, Id, Opts0]) -> process_flag(trap_exit, true), - PrioLatest = proplists:get_bool(prioritise_latest, Opts), + PrioLatest = proplists:get_bool(prioritise_latest, Opts0), + #{opts := Opts, proxy := Proxy} = parse_proxy_opts(Opts0), State = #state{ pool = Pool, id = Id, @@ -213,7 +215,8 @@ init([Pool, Id, Opts]) -> pending_count => 0, sent => #{}, prioritise_latest => PrioLatest - } + }, + proxy = Proxy }, true = gproc_pool:connect_worker(ehttpc:name(Pool), {Pool, Id}), {ok, State}. @@ -356,10 +359,37 @@ code_change({down, _Vsn}, State, [no_enable_pipelining]) -> } = State, OldRequests = downgrade_requests(Requests), {ok, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState, OldRequests}}; -code_change({down, _Vsn}, #state{requests = Requests} = State, [downgrade_requests]) -> +code_change({down, _Vsn}, State, [downgrade_requests]) -> %% downgrade to a version which had old format 'requests' + #state{ + pool = Pool, + id = ID, + client = Client, + mref = MRef, + host = Host, + port = Port, + enable_pipelining = Pipelining, + gun_opts = GunOpts, + gun_state = GunState, + requests = Requests + } = State, OldRequests = downgrade_requests(Requests), - {ok, State#state{requests = OldRequests}}; + {ok, {state, Pool, ID, Client, MRef, Host, Port, Pipelining, GunOpts, GunState, OldRequests}}; +code_change({down, _Vsn}, State, [no_proxy]) -> + %% downgrade to a version before `proxy' was added + #state{ + pool = Pool, + id = ID, + client = Client, + mref = MRef, + host = Host, + port = Port, + enable_pipelining = Pipelining, + gun_opts = GunOpts, + gun_state = GunState, + requests = Requests + } = State, + {ok, {state, Pool, ID, Client, MRef, Host, Port, Pipelining, GunOpts, GunState, Requests}}; %% below are upgrade instructions code_change(_Vsn, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState}, _Extra) -> %% upgrade from a version before 'requests' field was added @@ -373,10 +403,11 @@ code_change(_Vsn, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState} enable_pipelining = true, gun_opts = GunOpts, gun_state = GunState, - requests = upgrade_requests(#{}) + requests = upgrade_requests(#{}), + proxy = undefined }}; code_change(_Vsn, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState, Requests}, _) -> - %% upgrade from a version before 'enable_pipelining' filed was added + %% upgrade from a version before 'enable_pipelining' field was added {ok, #state{ pool = Pool, id = ID, @@ -389,8 +420,25 @@ code_change(_Vsn, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState, gun_state = GunState, requests = upgrade_requests(Requests) }}; +code_change( + _Vsn, {state, Pool, ID, Client, MRef, Host, Port, Pipelining, GunOpts, GunState, Requests}, _ +) -> + %% upgrade from a version before `proxy' field was added + {ok, #state{ + pool = Pool, + id = ID, + client = Client, + mref = MRef, + host = Host, + port = Port, + enable_pipelining = Pipelining, + gun_opts = GunOpts, + gun_state = GunState, + requests = upgrade_requests(Requests), + proxy = undefined + }}; code_change(_Vsn, State, _) -> - %% upgrade from a version ahving old format 'requests' field + %% upgrade from a version having old format 'requests' field {ok, upgrade_requests(State)}. format_status(Status = #{state := State}) -> @@ -776,11 +824,15 @@ do_after_gun_up(State0 = #state{client = Client, mref = MRef}, ExpireAt, Fun) -> {Res, State} = gun_await_up(Client, ExpireAt, Timeout, MRef, State0), case Res of {ok, _} -> - Fun(State#state{gun_state = up}); + Fun(State); {error, connect_timeout} -> %% the caller can not wait logger %% but the connection is likely to be useful {reply, {error, connect_timeout}, State}; + {error, {proxy_error, _} = Error} -> + %% We keep the client around because the proxy might still send data as part + %% of the error response. + {reply, {error, Error}, State}; {error, Reason} -> case is_reference(MRef) of true -> @@ -798,7 +850,13 @@ do_after_gun_up(State0 = #state{client = Client, mref = MRef}, ExpireAt, Fun) -> gun_await_up(Pid, ExpireAt, Timeout, MRef, State0) -> receive {gun_up, Pid, Protocol} -> - {{ok, Protocol}, State0}; + case State0#state.proxy of + undefined -> + State = State0#state{gun_state = up}, + {{ok, Protocol}, State}; + #{} = ProxyOpts -> + gun_connect_proxy(Pid, ExpireAt, Timeout, Protocol, ProxyOpts, State0) + end; {'DOWN', MRef, process, Pid, {shutdown, Reason}} -> %% stale code for appup since 0.4.12 {{error, Reason}, State0}; @@ -824,6 +882,34 @@ gun_await_up(Pid, ExpireAt, Timeout, MRef, State0) -> {{error, connect_timeout}, State0} end. +gun_connect_proxy(Pid, ExpireAt, Timeout, Protocol, ProxyOpts, State0) -> + StreamRef = gun:connect(Pid, ProxyOpts), + gun_await_connect_proxy(Pid, StreamRef, ExpireAt, Timeout, Protocol, ProxyOpts, State0). + +gun_await_connect_proxy(Pid, StreamRef, ExpireAt, Timeout, Protocol, ProxyOpts, State0) -> + receive + {gun_response, Pid, StreamRef, fin, 200, Headers} -> + State = State0#state{gun_state = up}, + {{ok, {Protocol, Headers}}, State}; + {gun_response, Pid, StreamRef, _Fin, 407, _Headers} -> + {{error, {proxy_error, unauthorized}}, State0}; + {gun_response, Pid, StreamRef, _Fin, StatusCode, Headers} -> + {{error, {proxy_error, {StatusCode, Headers}}}, State0}; + ?ASYNC_REQ(Method, Request, ExpireAt1, ResultCallback) -> + Req = ?REQ(Method, Request, ExpireAt1), + State = enqueue_req(ResultCallback, Req, State0), + %% keep waiting + NewTimeout = timeout(ExpireAt), + gun_await_connect_proxy(Pid, StreamRef, ExpireAt, NewTimeout, Protocol, ProxyOpts, State); + ?GEN_CALL_REQ(From, Call) -> + State = enqueue_req(From, Call, State0), + %% keep waiting + NewTimeout = timeout(ExpireAt), + gun_await_connect_proxy(Pid, StreamRef, ExpireAt, NewTimeout, Protocol, ProxyOpts, State) + after Timeout -> + {{error, connect_timeout}, State0} + end. + %% normal handling of gun_response and gun_data reply handle_gun_reply(State, Client, StreamRef, IsFin, StatusCode, Headers, Data) -> #state{requests = Requests} = State, @@ -891,6 +977,52 @@ fresh_expire_at(infinity = _Timeout) -> fresh_expire_at(Timeout) when is_integer(Timeout) -> now_() + Timeout. +parse_proxy_opts(Opts) -> + %% Target host and port + case proplists:get_value(proxy, Opts, undefined) of + undefined -> + #{opts => Opts, proxy => undefined}; + #{host := _, port := _} = ProxyOpts0 -> + %% We open connection to proxy, then issue `gun:connect' to target host. + {ProxyOpts, NewOpts} = + lists:foldl( + fun(Key, {ProxyAcc, GunAcc}) -> + swap(Key, ProxyAcc, GunAcc) + end, + {ProxyOpts0, proplists:delete(proxy, Opts)}, + [host, port, transport, {tls_opts, transport_opts}] + ), + #{opts => NewOpts, proxy => ProxyOpts} + end. + +swap(Key, Map, Proplist) when is_atom(Key) -> + swap({Key, Key}, Map, Proplist); +swap({KeyM, KeyP}, Map0, Proplist0) when is_map_key(KeyM, Map0) -> + ValueFromMap = maps:get(KeyM, Map0), + Map = maps:remove(KeyM, Map0), + case take_proplist(KeyP, Proplist0) of + {ValueFromProplist, Proplist} -> + {Map#{KeyM => ValueFromProplist}, [{KeyP, ValueFromMap} | Proplist]}; + error -> + {Map, [{KeyP, ValueFromMap} | Proplist0]} + end; +swap({KeyM, KeyP}, Map0, Proplist0) -> + case take_proplist(KeyP, Proplist0) of + {ValueFromProplist, Proplist} -> + {Map0#{KeyM => ValueFromProplist}, Proplist}; + error -> + {Map0, Proplist0} + end. + +take_proplist(Key, Proplist0) -> + Proplist1 = lists:keydelete(Key, 1, Proplist0), + case lists:keyfind(Key, 1, Proplist0) of + false -> + error; + {Key, ValueFromProplist} -> + {ValueFromProplist, Proplist1} + end. + -ifdef(TEST). prioritise_latest_test() -> diff --git a/test/ehttpc_tests.erl b/test/ehttpc_tests.erl index a3bf261..fd9dc90 100644 --- a/test/ehttpc_tests.erl +++ b/test/ehttpc_tests.erl @@ -434,22 +434,23 @@ upgrade_state_on_the_fly_test() -> spawn_link(fun() -> ehttpc:request(?POOL, post, {<<"/">>, [], <<"test-post">>}) end), {ok, _} = ?block_until(#{?snk_kind := shot}, 2000, infinity), Pid = ehttpc_pool:pick_worker(?POOL), - GetState = fun() -> lists:reverse(tuple_to_list(sys:get_state(Pid))) end, + GetState = fun() -> sys:get_state(Pid) end, State = GetState(), - Requests = hd(State), + RequestsIdx = 11, + Requests = element(RequestsIdx, State), #{sent := Sent} = Requests, ?assertEqual(1, maps:size(Sent)), - OldState = list_to_tuple(lists:reverse([Sent | tl(State)])), + OldState = setelement(RequestsIdx, State, Sent), %% put old format to the process state sys:replace_state(Pid, fun(_) -> OldState end), %% verify it's in the old format - ?assertEqual(Sent, hd(GetState())), + ?assertEqual(Sent, element(RequestsIdx, GetState())), %% send a message to trigger upgrade Pid ! dummy, {error, _} = gen_server:call(Pid, dummy), ok = gen_server:cast(Pid, dummy), %% now it should be upgraded to the new version - ?assertMatch(#{sent := Sent}, hd(GetState())), + ?assertMatch(#{sent := Sent}, element(RequestsIdx, GetState())), _ = sys:get_status(Pid), ok end From 8966c3666a3636f4f76e67badcdd90cd50f52f6e Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 20 Sep 2024 13:56:28 -0300 Subject: [PATCH 2/5] chore: bump app vsn and appup --- src/ehttpc.app.src | 2 +- src/ehttpc.appup.src | 42 +++++++++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/ehttpc.app.src b/src/ehttpc.app.src index 9ebda70..6eb73f8 100644 --- a/src/ehttpc.app.src +++ b/src/ehttpc.app.src @@ -1,6 +1,6 @@ {application, ehttpc, [ {description, "HTTP Client for Erlang/OTP"}, - {vsn, "0.4.14"}, + {vsn, "0.4.15"}, {registered, []}, {applications, [ kernel, diff --git a/src/ehttpc.appup.src b/src/ehttpc.appup.src index ae9c3df..4cf3f09 100644 --- a/src/ehttpc.appup.src +++ b/src/ehttpc.appup.src @@ -1,10 +1,16 @@ %% -*- mode: erlang -*- -{"0.4.14", +{"0.4.15", [ + {"0.4.14", [ + {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, + {load_module, ehttpc, brutal_purge, soft_purge, []} + ]}, {"0.4.13", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []} + {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, + {load_module, ehttpc, brutal_purge, soft_purge, []} ]}, - {"0.4.12", [ % upgrade gun, no local beam changes in this version + {"0.4.12", [ + {load_module, ehttpc, brutal_purge, soft_purge, []} ]}, {"0.4.11", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, @@ -78,55 +84,61 @@ ]} ], [ + {"0.4.14", [ + {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, + {update, ehttpc, {advanced, [no_proxy]}} + ]}, {"0.4.13", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []} + {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, + {update, ehttpc, {advanced, [no_proxy]}} ]}, - {"0.4.12", [ % upgrade gun, no local beam changes in this version + {"0.4.12", [ + {update, ehttpc, {advanced, [no_proxy]}} ]}, {"0.4.11", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} + {update, ehttpc, {advanced, [no_proxy]}} ]}, {"0.4.10", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} + {update, ehttpc, {advanced, [no_proxy]}} ]}, {"0.4.9", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} + {update, ehttpc, {advanced, [no_proxy]}} ]}, {"0.4.8", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} + {update, ehttpc, {advanced, [no_proxy]}} ]}, {"0.4.7", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} + {update, ehttpc, {advanced, [no_proxy]}} ]}, {"0.4.6", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} + {update, ehttpc, {advanced, [no_proxy]}} ]}, {"0.4.5", [ {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} + {update, ehttpc, {advanced, [no_proxy]}} ]}, {<<"0\\.4\\.[0-4]">>, [ - {load_module, ehttpc, brutal_purge, soft_purge, []}, + {update, ehttpc, {advanced, [no_proxy]}}, {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} ]}, {"0.3.0", [ - {load_module, ehttpc, brutal_purge, soft_purge, []}, + {update, ehttpc, {advanced, [no_proxy]}}, {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} ]}, {<<"0\\.2\\.[0-1]">>, [ - {load_module, ehttpc, brutal_purge, soft_purge, []}, + {update, ehttpc, {advanced, [no_proxy]}}, {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, From 93b956160bf62625395d2bd434734e2fdae7c950 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 20 Sep 2024 14:37:11 -0300 Subject: [PATCH 3/5] test: add tests for proxy --- .github/workflows/erlang.yml | 2 + src/ehttpc.erl | 8 +++- test/ehttpc_google_tests.erl | 64 ++++++++++++++++++++++++++- test/scripts/setup_tinyproxy.sh | 14 ++++++ test/scripts/tinyproxy.conf | 4 ++ test/scripts/tinyproxy_with_auth.conf | 6 +++ 6 files changed, 95 insertions(+), 3 deletions(-) create mode 100755 test/scripts/setup_tinyproxy.sh create mode 100644 test/scripts/tinyproxy.conf create mode 100644 test/scripts/tinyproxy_with_auth.conf diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index 726de5b..5ed1ec3 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -29,6 +29,8 @@ jobs: with: otp-version: ${{ matrix.otp }} rebar3-version: 3 + - name: setup tinyproxy + run: sudo ./test/scripts/setup_tinyproxy.sh - name: Compile run: rebar3 compile - name: Run tests diff --git a/src/ehttpc.erl b/src/ehttpc.erl index be6d6b3..0e0a1da 100644 --- a/src/ehttpc.erl +++ b/src/ehttpc.erl @@ -900,12 +900,16 @@ gun_await_connect_proxy(Pid, StreamRef, ExpireAt, Timeout, Protocol, ProxyOpts, State = enqueue_req(ResultCallback, Req, State0), %% keep waiting NewTimeout = timeout(ExpireAt), - gun_await_connect_proxy(Pid, StreamRef, ExpireAt, NewTimeout, Protocol, ProxyOpts, State); + gun_await_connect_proxy( + Pid, StreamRef, ExpireAt, NewTimeout, Protocol, ProxyOpts, State + ); ?GEN_CALL_REQ(From, Call) -> State = enqueue_req(From, Call, State0), %% keep waiting NewTimeout = timeout(ExpireAt), - gun_await_connect_proxy(Pid, StreamRef, ExpireAt, NewTimeout, Protocol, ProxyOpts, State) + gun_await_connect_proxy( + Pid, StreamRef, ExpireAt, NewTimeout, Protocol, ProxyOpts, State + ) after Timeout -> {{error, connect_timeout}, State0} end. diff --git a/test/ehttpc_google_tests.erl b/test/ehttpc_google_tests.erl index dcf6649..19e4431 100644 --- a/test/ehttpc_google_tests.erl +++ b/test/ehttpc_google_tests.erl @@ -45,6 +45,53 @@ concurrent_callers_test_() -> {timeout, TestTimeout, fun() -> with_pool(Opts4, F) end} ]. +proxy_test_() -> + N = 50, + TestTimeout = 1000, + Host = ?HOST, + Port = ?PORT, + ProxyOpts0 = #{host => "127.0.0.1", port => 8888}, + ProxyOpts1 = ProxyOpts0#{username => "user", password => "pass"}, + %% host port enable_pipelining prioritise_latest + Opts1_ = pool_opts(Host, Port, true, true), + Opts2_ = pool_opts(Host, Port, true, false), + Opts3_ = pool_opts(Host, Port, false, true), + Opts4_ = pool_opts(Host, Port, false, false), + [Opts1, Opts2, Opts3, Opts4] = [ + [{proxy, ProxyOpts0} | O] + || O <- [Opts1_, Opts2_, Opts3_, Opts4_] + ], + [Opts5, Opts6, Opts7, Opts8] = [ + [{proxy, ProxyOpts1} | O] + || O <- [Opts1_, Opts2_, Opts3_, Opts4_] + ], + F = fun() -> req_async(?METHOD, N) end, + NoAuthConfPath = filename:absname("test/scripts/tinyproxy.conf"), + BasicAuthConfPath = filename:absname("test/scripts/tinyproxy_with_auth.conf"), + {inorder, [ + {setup, fun() -> setup_proxy(NoAuthConfPath) end, fun stop_proxy/1, [ + {timeout, TestTimeout, ?_test(with_pool(Opts1, F))}, + {timeout, TestTimeout, ?_test(with_pool(Opts2, F))}, + {timeout, TestTimeout, ?_test(with_pool(Opts3, F))}, + {timeout, TestTimeout, ?_test(with_pool(Opts4, F))} + ]}, + {setup, fun() -> setup_proxy(BasicAuthConfPath) end, fun stop_proxy/1, [ + {"missing auth", + ?_test( + with_pool(Opts1, fun() -> + ?assertMatch( + {error, {proxy_error, unauthorized}}, + do_req_sync(get, 1_000) + ) + end) + )}, + {timeout, TestTimeout, ?_test(with_pool(Opts5, F))}, + {timeout, TestTimeout, ?_test(with_pool(Opts6, F))}, + {timeout, TestTimeout, ?_test(with_pool(Opts7, F))}, + {timeout, TestTimeout, ?_test(with_pool(Opts8, F))} + ]} + ]}. + req(get) -> {?PATH, [{<<"Connection">>, <<"Keep-Alive">>}]}; req(post) -> @@ -54,12 +101,15 @@ req(post) -> req_sync(_Method, 0, _Timeout) -> ok; req_sync(Method, N, Timeout) -> - case ehttpc:request(?POOL, Method, req(Method), Timeout, _Retry = 0) of + case do_req_sync(Method, Timeout) of {ok, _, _Headers, _Body} -> ok; {error, timeout} -> timeout end, req_sync(Method, N - 1, Timeout). +do_req_sync(Method, Timeout) -> + ehttpc:request(?POOL, Method, req(Method), Timeout, _Retry = 0). + req_async(Method, N) -> {Time, Results} = timer:tc(fun() -> req_async(Method, N, 5_000) end), {OK, Timeout} = lists:partition(fun(I) -> I =:= ok end, Results), @@ -97,3 +147,15 @@ with_pool(Opts, F) -> after ehttpc_sup:stop_pool(?POOL) end. + +setup_proxy(ConfPath) -> + ?debugFmt("conf path: ~s", [ConfPath]), + Output = os:cmd("tinyproxy -c" ++ ConfPath), + ?debugFmt("setup proxy output:\n\n~s\n", [Output]), + ok. + +stop_proxy(_) -> + Output = os:cmd("pkill tinyproxy"), + ?debugFmt("kill proxy output:\n\n~s\n", [Output]), + timer:sleep(500), + ok. diff --git a/test/scripts/setup_tinyproxy.sh b/test/scripts/setup_tinyproxy.sh new file mode 100755 index 0000000..9bb09df --- /dev/null +++ b/test/scripts/setup_tinyproxy.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -exuo pipefail + +VERSION=1.11.2 +URL="https://github.com/tinyproxy/tinyproxy/releases/download/${VERSION}/tinyproxy-${VERSION}.tar.bz2" + +cd /tmp +wget -O tinyproxy.tar.bz2 "${URL}" +tar -xf tinyproxy.tar.bz2 +cd tinyproxy-* +./configure +make +make install diff --git a/test/scripts/tinyproxy.conf b/test/scripts/tinyproxy.conf new file mode 100644 index 0000000..b4e7d66 --- /dev/null +++ b/test/scripts/tinyproxy.conf @@ -0,0 +1,4 @@ +Port 8888 +Listen 127.0.0.1 +Timeout 600 +Allow 127.0.0.1 diff --git a/test/scripts/tinyproxy_with_auth.conf b/test/scripts/tinyproxy_with_auth.conf new file mode 100644 index 0000000..b143bd3 --- /dev/null +++ b/test/scripts/tinyproxy_with_auth.conf @@ -0,0 +1,6 @@ +Port 8888 +Listen 127.0.0.1 +Timeout 600 +Allow 127.0.0.1 + +BasicAuth user pass From 4889da4d968a782f6339e674159575b37869448b Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Fri, 20 Sep 2024 17:36:16 -0300 Subject: [PATCH 4/5] ci: check style first --- .github/workflows/erlang.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index 5ed1ec3..be8386f 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -29,6 +29,8 @@ jobs: with: otp-version: ${{ matrix.otp }} rebar3-version: 3 + - name: Ensure style + run: ./check-style.sh - name: setup tinyproxy run: sudo ./test/scripts/setup_tinyproxy.sh - name: Compile @@ -39,5 +41,3 @@ jobs: run: make dialyzer - name: Ensure version consistency run: ./check_vsns.escript - - name: Ensure style - run: ./check-style.sh From be1d56b15b6b3e75376695586eaea7d45d244c27 Mon Sep 17 00:00:00 2001 From: Thales Macedo Garitezi Date: Mon, 23 Sep 2024 10:43:40 -0300 Subject: [PATCH 5/5] chore: drop hot-upgrade suppport --- .github/workflows/erlang.yml | 2 - changelog.md | 3 +- check_vsns.escript | 12 --- src/ehttpc.app.src | 2 +- src/ehttpc.appup.src | 176 ----------------------------------- src/ehttpc.erl | 113 ---------------------- test/ehttpc_app_tests.erl | 65 +++++++++++++ test/ehttpc_appup_tests.erl | 149 ----------------------------- 8 files changed, 68 insertions(+), 454 deletions(-) delete mode 100755 check_vsns.escript delete mode 100644 src/ehttpc.appup.src create mode 100644 test/ehttpc_app_tests.erl delete mode 100644 test/ehttpc_appup_tests.erl diff --git a/.github/workflows/erlang.yml b/.github/workflows/erlang.yml index be8386f..1c49dc7 100644 --- a/.github/workflows/erlang.yml +++ b/.github/workflows/erlang.yml @@ -39,5 +39,3 @@ jobs: run: make eunit - name: Run dialyzer run: make dialyzer - - name: Ensure version consistency - run: ./check_vsns.escript diff --git a/changelog.md b/changelog.md index 636bfd3..c801e77 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,8 @@ # ehttpc changes -## 0.4.15 +## 0.5.0 +- Dropped hot-upgrade support. - Added support for using HTTP proxy (HTTP 1.1 only). To use it, pass `proxy` in the pool opts. diff --git a/check_vsns.escript b/check_vsns.escript deleted file mode 100755 index c9b24dc..0000000 --- a/check_vsns.escript +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env escript - --mode(compile). - -main(_) -> - {ok, [{application, ehttpc, App}]} = file:consult("src/ehttpc.app.src"), - {ok, [{VsnAppup, _Up, _Down}]} = file:consult("src/ehttpc.appup.src"), - {vsn, VsnApp} = lists:keyfind(vsn, 1, App), - case VsnAppup =:= VsnApp of - true -> ok; - false -> error([{appup, VsnAppup}, {app, VsnApp}]) - end. diff --git a/src/ehttpc.app.src b/src/ehttpc.app.src index 6eb73f8..151a496 100644 --- a/src/ehttpc.app.src +++ b/src/ehttpc.app.src @@ -1,6 +1,6 @@ {application, ehttpc, [ {description, "HTTP Client for Erlang/OTP"}, - {vsn, "0.4.15"}, + {vsn, "0.5.0"}, {registered, []}, {applications, [ kernel, diff --git a/src/ehttpc.appup.src b/src/ehttpc.appup.src deleted file mode 100644 index 4cf3f09..0000000 --- a/src/ehttpc.appup.src +++ /dev/null @@ -1,176 +0,0 @@ -%% -*- mode: erlang -*- -{"0.4.15", - [ - {"0.4.14", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.13", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.12", [ - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.11", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.10", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.9", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.8", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.7", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.6", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {"0.4.5", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.4\\.[0-4]">>, [ - {load_module, ehttpc, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {"0.3.0", [ - {load_module, ehttpc, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.2\\.[0-1]">>, [ - {load_module, ehttpc, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.1\\\.[0-7]">>, [ - {update, ehttpc, {advanced, []}}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.1\\.([8-9]|(1[0-4]))">>, [ - {update, ehttpc, {advanced, []}}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {"0.1.15", [ - {update, ehttpc, {advanced, []}}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]} - ], - [ - {"0.4.14", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.13", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.12", [ - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.11", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.10", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.9", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.8", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.7", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.6", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {"0.4.5", [ - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {update, ehttpc, {advanced, [no_proxy]}} - ]}, - {<<"0\\.4\\.[0-4]">>, [ - {update, ehttpc, {advanced, [no_proxy]}}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {"0.3.0", [ - {update, ehttpc, {advanced, [no_proxy]}}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.2\\.[0-1]">>, [ - {update, ehttpc, {advanced, [no_proxy]}}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.1\\.0">>, [ - {update, ehttpc, {advanced, [no_requests]}}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.1\\.[1-7]">>, [ - {update, ehttpc, {advanced, [no_enable_pipelining]}}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {<<"0\\.1\\.([8-9]|(1[0-4]))">>, [ - {update, ehttpc, {advanced, [downgrade_requests]}}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]}, - {"0.1.15", [ - {update, ehttpc, {advanced, [downgrade_requests]}}, - {load_module, ehttpc_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool, brutal_purge, soft_purge, []}, - {load_module, ehttpc_pool_sup, brutal_purge, soft_purge, []}, - {load_module, ehttpc_worker_sup, brutal_purge, soft_purge, []} - ]} - ] -}. diff --git a/src/ehttpc.erl b/src/ehttpc.erl index 0e0a1da..6e3ea0e 100644 --- a/src/ehttpc.erl +++ b/src/ehttpc.erl @@ -37,7 +37,6 @@ handle_cast/2, handle_info/2, terminate/2, - code_change/3, format_status/1, format_status/2, format_state/2 @@ -329,118 +328,6 @@ terminate(_Reason, #state{pool = Pool, id = Id, client = Client}) -> gproc_pool:disconnect_worker(ehttpc:name(Pool), {Pool, Id}), ok. -%% NOTE: the git tag 0.1.0 was re-tagged -%% the actual version in use in EMQX 4.2 had requests missing -code_change({down, _Vsn}, State, [no_requests]) -> - %% downgrage to a version before 'requests' and 'enable_pipelining' were added - #state{ - pool = Pool, - id = ID, - client = Client, - mref = MRef, - host = Host, - port = Port, - gun_opts = GunOpts, - gun_state = GunState - } = State, - {ok, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState}}; -code_change({down, _Vsn}, State, [no_enable_pipelining]) -> - %% downgrade to a version before 'enable_pipelining' was added - #state{ - pool = Pool, - id = ID, - client = Client, - mref = MRef, - host = Host, - port = Port, - gun_opts = GunOpts, - gun_state = GunState, - requests = Requests - } = State, - OldRequests = downgrade_requests(Requests), - {ok, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState, OldRequests}}; -code_change({down, _Vsn}, State, [downgrade_requests]) -> - %% downgrade to a version which had old format 'requests' - #state{ - pool = Pool, - id = ID, - client = Client, - mref = MRef, - host = Host, - port = Port, - enable_pipelining = Pipelining, - gun_opts = GunOpts, - gun_state = GunState, - requests = Requests - } = State, - OldRequests = downgrade_requests(Requests), - {ok, {state, Pool, ID, Client, MRef, Host, Port, Pipelining, GunOpts, GunState, OldRequests}}; -code_change({down, _Vsn}, State, [no_proxy]) -> - %% downgrade to a version before `proxy' was added - #state{ - pool = Pool, - id = ID, - client = Client, - mref = MRef, - host = Host, - port = Port, - enable_pipelining = Pipelining, - gun_opts = GunOpts, - gun_state = GunState, - requests = Requests - } = State, - {ok, {state, Pool, ID, Client, MRef, Host, Port, Pipelining, GunOpts, GunState, Requests}}; -%% below are upgrade instructions -code_change(_Vsn, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState}, _Extra) -> - %% upgrade from a version before 'requests' field was added - {ok, #state{ - pool = Pool, - id = ID, - client = Client, - mref = MRef, - host = Host, - port = Port, - enable_pipelining = true, - gun_opts = GunOpts, - gun_state = GunState, - requests = upgrade_requests(#{}), - proxy = undefined - }}; -code_change(_Vsn, {state, Pool, ID, Client, MRef, Host, Port, GunOpts, GunState, Requests}, _) -> - %% upgrade from a version before 'enable_pipelining' field was added - {ok, #state{ - pool = Pool, - id = ID, - client = Client, - mref = MRef, - host = Host, - port = Port, - enable_pipelining = true, - gun_opts = GunOpts, - gun_state = GunState, - requests = upgrade_requests(Requests) - }}; -code_change( - _Vsn, {state, Pool, ID, Client, MRef, Host, Port, Pipelining, GunOpts, GunState, Requests}, _ -) -> - %% upgrade from a version before `proxy' field was added - {ok, #state{ - pool = Pool, - id = ID, - client = Client, - mref = MRef, - host = Host, - port = Port, - enable_pipelining = Pipelining, - gun_opts = GunOpts, - gun_state = GunState, - requests = upgrade_requests(Requests), - proxy = undefined - }}; -code_change(_Vsn, State, _) -> - %% upgrade from a version having old format 'requests' field - {ok, upgrade_requests(State)}. - format_status(Status = #{state := State}) -> Status#{state => format_state(State, minimal)}. diff --git a/test/ehttpc_app_tests.erl b/test/ehttpc_app_tests.erl new file mode 100644 index 0000000..bc54dff --- /dev/null +++ b/test/ehttpc_app_tests.erl @@ -0,0 +1,65 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(ehttpc_app_tests). + +-include_lib("eunit/include/eunit.hrl"). + +ensure_vsn_bump_test() -> + TagV = parse_semver(os:cmd("git describe --tags")), + {ok, [{application, ehttpc, Props}]} = file:consult("src/ehttpc.app.src"), + V = parse_semver(proplists:get_value(vsn, Props)), + ?assert(TagV =< V). + +ensure_changelog_test() -> + {ok, [{application, ehttpc, Props}]} = file:consult("src/ehttpc.app.src"), + Vsn = proplists:get_value(vsn, Props), + ExpectedChangeLogLine = "## " ++ Vsn, + {ok, Text} = file:read_file("changelog.md"), + Lines = binary:split(Text, <<"\n">>, [global]), + case lists:member(iolist_to_binary(ExpectedChangeLogLine), Lines) of + true -> ok; + false -> error({missing_changelog_for_vsn, ExpectedChangeLogLine}) + end. + +ensure_git_tag_test() -> + Latest = lists:last(lists:sort([parse_semver(T) || T <- all_tags()])), + LatestTag = format_semver(Latest), + ChangedFiles = os:cmd("git diff --name-only " ++ LatestTag ++ "...HEAD src"), + case ChangedFiles of + [] -> + ok; + Other -> + %% only print a warning message + %% because we can not control git tags in eunit + io:format( + user, + "################################~n" + "changed since ~s:~n~p~n", + [LatestTag, Other] + ), + ok + end. + +format_semver({Major, Minor, Patch}) -> + lists:flatten(io_lib:format("~p.~p.~p", [Major, Minor, Patch])). + +parse_semver(Str) -> + [Major, Minor, Patch | _] = string:tokens(Str, ".-\n"), + {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}. + +all_tags() -> + string:tokens(os:cmd("git tag"), "\n"). diff --git a/test/ehttpc_appup_tests.erl b/test/ehttpc_appup_tests.erl deleted file mode 100644 index 8c6d99c..0000000 --- a/test/ehttpc_appup_tests.erl +++ /dev/null @@ -1,149 +0,0 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2021-2022 EMQ Technologies Co., Ltd. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%%-------------------------------------------------------------------- - --module(ehttpc_appup_tests). - --include_lib("eunit/include/eunit.hrl"). - -%% First version used in EMQX v4.3.0 --record(state_0_1_0, {pool, id, client, mref, host, port, gun_opts, gun_state}). --record(state_0_1_7, {pool, id, client, mref, host, port, gun_opts, gun_state, requests}). --record(state_0_1_8, { - pool, id, client, mref, host, port, enable_pipelining, gun_opts, gun_state, requests -}). - -app_vsn_test() -> - {ok, [{AppupVsn, _Upgraes, _Downgrades}]} = file:consult("src/ehttpc.appup.src"), - {ok, [{application, ehttpc, Props}]} = file:consult("src/ehttpc.app.src"), - Vsn = proplists:get_value(vsn, Props), - ?assertEqual(Vsn, AppupVsn). - -ensure_vsn_bump_test() -> - TagV = parse_semver(os:cmd("git describe --tags")), - {ok, [{application, ehttpc, Props}]} = file:consult("src/ehttpc.app.src"), - V = parse_semver(proplists:get_value(vsn, Props)), - ?assert(TagV =< V). - -ensure_changelog_test() -> - {ok, [{application, ehttpc, Props}]} = file:consult("src/ehttpc.app.src"), - Vsn = proplists:get_value(vsn, Props), - ExpectedChangeLogLine = "## " ++ Vsn, - {ok, Text} = file:read_file("changelog.md"), - Lines = binary:split(Text, <<"\n">>, [global]), - case lists:member(iolist_to_binary(ExpectedChangeLogLine), Lines) of - true -> ok; - false -> error({missing_changelog_for_vsn, ExpectedChangeLogLine}) - end. - -ensure_git_tag_test() -> - Latest = lists:last(lists:sort([parse_semver(T) || T <- all_tags()])), - LatestTag = format_semver(Latest), - ChangedFiles = os:cmd("git diff --name-only " ++ LatestTag ++ "...HEAD src"), - case ChangedFiles of - [] -> - ok; - Other -> - %% only print a warning message - %% because we can not control git tags in eunit - io:format( - user, - "################################~n" - "changed since ~s:~n~p~n", - [LatestTag, Other] - ), - ok - end. - -format_semver({Major, Minor, Patch}) -> - lists:flatten(io_lib:format("~p.~p.~p", [Major, Minor, Patch])). - -parse_semver(Str) -> - [Major, Minor, Patch | _] = string:tokens(Str, ".-\n"), - {list_to_integer(Major), list_to_integer(Minor), list_to_integer(Patch)}. - -all_tags() -> - string:tokens(os:cmd("git tag"), "\n"). - -git_tag_match_upgrade_vsn_test_() -> - {ok, [{AppupVsn, Upgraes, Downgrades}]} = file:consult("src/ehttpc.appup.src"), - [ - {Tag, fun() -> true = has_a_match(Tag, Upgraes, Downgrades) end} - || Tag <- all_tags(), Tag =/= AppupVsn - ]. - -has_a_match(Tag, Ups, Downs) -> - has_a_match(Tag, Ups) andalso - has_a_match(Tag, Downs). - -has_a_match(_, []) -> - false; -has_a_match(Tag, [{Tag, _} | _]) -> - true; -has_a_match(Tag, [{Tag1, _} | Rest]) when is_list(Tag1) -> - has_a_match(Tag, Rest); -has_a_match(Tag, [{Re, _} | Rest]) when is_binary(Re) -> - case re:run(Tag, Re, [unicode, {capture, first, list}]) of - {match, [Tag]} -> true; - _ -> has_a_match(Tag, Rest) - end. - -%% NOTE: the git tag 0.1.0 was re-tagged -upgrade_from_test_() -> - Old1 = #state_0_1_0{mref = make_ref()}, - Old2 = #state_0_1_7{mref = make_ref(), requests = #{}}, - Old3 = #state_0_1_8{mref = make_ref(), requests = #{}}, - F = fun({Old, Extra}) -> - {ok, New} = ehttpc:code_change("old", new_tag(Old), []), - #{requests := Requests} = ehttpc:format_state(New, normal), - ?assertMatch( - #{ - pending := _, - pending_count := 0, - sent := #{} - }, - Requests - ), - {ok, Down} = ehttpc:code_change({down, "new"}, New, Extra), - OldTag = element(1, Old), - ?assertEqual(Old, old_tag(OldTag, Down)) - end, - [ - {atom_to_list(element(1, Old)), fun() -> F({Old, Extra}) end} - || {Old, Extra} <- [ - {Old1, [no_requests]}, - {Old2, [no_enable_pipelining]}, - {Old3, [downgrade_requests]} - ] - ]. - -old_tag(Tag, State) -> - setelement(1, State, Tag). - -new_tag(State) -> - setelement(1, State, state). - -upgrade_requests_test() -> - Old = #{foo => bar}, - New = ehttpc:upgrade_requests(Old), - ?assertMatch( - #{ - sent := Old, - pending := _, - pending_count := 0 - }, - New - ), - ?assertMatch(Old, ehttpc:downgrade_requests(New)).