diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | src/cowboy_spdy.erl | 47 | ||||
-rw-r--r-- | test/cowboy_ct_hook.erl | 2 | ||||
-rw-r--r-- | test/cowboy_error_h.erl | 65 | ||||
-rw-r--r-- | test/cowboy_test.erl | 76 | ||||
-rw-r--r-- | test/handlers/long_polling_h.erl | 27 | ||||
-rw-r--r-- | test/handlers/loop_handler_body_h.erl | 24 | ||||
-rw-r--r-- | test/handlers/loop_handler_timeout_h.erl | 23 | ||||
-rw-r--r-- | test/http_SUITE.erl | 17 | ||||
-rw-r--r-- | test/http_SUITE_data/http_long_polling.erl | 24 | ||||
-rw-r--r-- | test/http_SUITE_data/http_loop_recv.erl | 18 | ||||
-rw-r--r-- | test/http_SUITE_data/http_loop_timeout.erl | 16 | ||||
-rw-r--r-- | test/loop_handler_SUITE.erl | 87 |
13 files changed, 334 insertions, 94 deletions
@@ -7,7 +7,7 @@ PROJECT = cowboy ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \ +warn_shadow_vars +warn_obsolete_guard +warn_missing_spec COMPILE_FIRST = cowboy_middleware cowboy_sub_protocol -CT_SUITES = eunit http spdy ws +CT_SUITES = eunit http loop_handler spdy ws CT_OPTS += -pa test -ct_hooks cowboy_ct_hook [] PLT_APPS = crypto public_key ssl diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl index e5aeb21..ce75419 100644 --- a/src/cowboy_spdy.erl +++ b/src/cowboy_spdy.erl @@ -33,9 +33,11 @@ %% Internal transport functions. -export([name/0]). +-export([messages/0]). -export([recv/3]). -export([send/2]). -export([sendfile/2]). +-export([setopts/2]). -type streamid() :: non_neg_integer(). -type socket() :: {pid(), streamid()}. @@ -45,8 +47,8 @@ pid :: pid(), input = nofin :: fin | nofin, in_buffer = <<>> :: binary(), - is_recv = false :: {true, {non_neg_integer(), pid()}, - pid(), non_neg_integer(), reference()} | false, + is_recv = false :: false | {active, socket(), pid()} + | {passive, socket(), pid(), non_neg_integer(), reference()}, output = nofin :: fin | nofin }). @@ -138,15 +140,15 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport, TRef = erlang:send_after(Timeout, self(), {recv_timeout, FromSocket}), loop(replace_child(Child#child{ - is_recv={true, FromSocket, FromPid, Length, TRef}}, + is_recv={passive, FromSocket, FromPid, Length, TRef}}, State)) end; {recv_timeout, {Pid, StreamID}} when Pid =:= self() -> - Child = #child{is_recv={true, FromSocket, FromPid, _, _}} + Child = #child{is_recv={passive, FromSocket, FromPid, _, _}} = get_child(StreamID, State), FromPid ! {recv, FromSocket, {error, timeout}}, - loop(replace_child(Child#child{is_recv=false}, State)); + loop(replace_child(Child#child{is_recv=passive}, State)); {reply, {Pid, StreamID}, Status, Headers} when Pid =:= self() -> Child = #child{output=nofin} = get_child(StreamID, State), @@ -178,6 +180,22 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport, Child = #child{output=nofin} = get_child(StreamID, State), data_from_file(State, StreamID, Filepath), loop(replace_child(Child#child{output=fin}, State)); + {active, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() -> + Child = #child{in_buffer=InBuffer, is_recv=false} + = get_child(StreamID, State), + case InBuffer of + <<>> -> + loop(replace_child(Child#child{ + is_recv={active, FromSocket, FromPid}}, State)); + _ -> + FromPid ! {spdy, FromSocket, InBuffer}, + loop(replace_child(Child#child{in_buffer= <<>>}, State)) + end; + {passive, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() -> + Child = #child{is_recv=IsRecv} = get_child(StreamID, State), + %% Make sure we aren't in the middle of a recv call. + case IsRecv of false -> ok; {active, FromSocket, FromPid} -> ok end, + loop(replace_child(Child#child{is_recv=false}, State)); {'EXIT', Parent, Reason} -> exit(Reason); {'EXIT', Pid, _} -> @@ -262,11 +280,14 @@ handle_frame(State, {data, StreamID, IsFin, Data}) -> Data2 = << Buffer/binary, Data/binary >>, IsFin2 = if IsFin -> fin; true -> nofin end, Child2 = case IsRecv of - {true, FromSocket, FromPid, 0, TRef} -> + {active, FromSocket, FromPid} -> + FromPid ! {spdy, FromSocket, Data}, + Child#child{input=IsFin2, is_recv=false}; + {passive, FromSocket, FromPid, 0, TRef} -> FromPid ! {recv, FromSocket, {ok, Data2}}, cancel_recv_timeout(StreamID, TRef), Child#child{input=IsFin2, in_buffer= <<>>, is_recv=false}; - {true, FromSocket, FromPid, Length, TRef} + {passive, FromSocket, FromPid, Length, TRef} when byte_size(Data2) >= Length -> << Data3:Length/binary, Rest/binary >> = Data2, FromPid ! {recv, FromSocket, {ok, Data3}}, @@ -443,6 +464,10 @@ stream_close(Socket = {Pid, _}) -> name() -> spdy. +-spec messages() -> {spdy, spdy_closed, spdy_error}. +messages() -> + {spdy, spdy_closed, spdy_error}. + -spec recv(socket(), non_neg_integer(), timeout()) -> {ok, binary()} | {error, timeout}. recv(Socket = {Pid, _}, Length, Timeout) -> @@ -463,3 +488,11 @@ send(Socket, Data) -> sendfile(Socket = {Pid, _}, Filepath) -> _ = Pid ! {sendfile, Socket, Filepath}, {ok, undefined}. + +-spec setopts(inet:socket(), list()) -> ok. +setopts(Socket = {Pid, _}, [{active, once}]) -> + _ = Pid ! {active, Socket, self()}, + ok; +setopts(Socket = {Pid, _}, [{active, false}]) -> + _ = Pid ! {passive, Socket, self()}, + ok. diff --git a/test/cowboy_ct_hook.erl b/test/cowboy_ct_hook.erl index 89f480d..1586412 100644 --- a/test/cowboy_ct_hook.erl +++ b/test/cowboy_ct_hook.erl @@ -18,6 +18,6 @@ init(_, _) -> cowboy_test:start([cowboy, gun]), - error_logger:tty(false), + cowboy_test:make_certs(), error_logger:add_report_handler(cowboy_error_h), {ok, undefined}. diff --git a/test/cowboy_error_h.erl b/test/cowboy_error_h.erl index fe79645..b4ae78f 100644 --- a/test/cowboy_error_h.erl +++ b/test/cowboy_error_h.erl @@ -30,11 +30,12 @@ %% Ignore crashes from Pid occuring in M:F/A. ignore(M, F, A) -> - gen_event:call(error_logger, ?MODULE, {expect, self(), M, F, A}). + gen_event:call(error_logger, ?MODULE, {expect, {self(), M, F, A}}). %% gen_event. init(_) -> + spawn(fun() -> error_logger:tty(false) end), {ok, []}. %% Ignore supervisor and progress reports. @@ -50,9 +51,41 @@ handle_event({error_report, _, {_, crash_report, {error_info, {error, gone, _}}|_]|_]}}, State) -> {ok, State}; -%% Ignore emulator reports, they are a duplicate of what Ranch gives us. -handle_event({error, _, {emulator, _, _}}, State) -> - {ok, State}; +%% Ignore emulator reports that are a duplicate of what Ranch gives us. +%% +%% The emulator always sends strings for errors, which makes it very +%% difficult to extract the information we need, hence the regexps. +handle_event(Event = {error, GL, {emulator, _, Msg}}, State) + when node(GL) =:= node() -> + Result = re:run(Msg, + "Error in process ([^\s]+).+? with exit value: " + ".+?{stacktrace,\\[{([^,]+),([^,]+),(.+)", + [{capture, all_but_first, list}]), + case Result of + nomatch -> + write_event(Event), + {ok, State}; + {match, [PidStr, MStr, FStr, Rest]} -> + A = case Rest of + "[]" ++ _ -> + 0; + "[" ++ Rest2 -> + count_args(Rest2, 1, 0); + _ -> + {match, [AStr]} = re:run(Rest, "([^,]+).+", + [{capture, all_but_first, list}]), + list_to_integer(AStr) + end, + Crash = {list_to_pid(PidStr), list_to_existing_atom(MStr), + list_to_existing_atom(FStr), A}, + case lists:member(Crash, State) of + true -> + {ok, lists:delete(Crash, State)}; + false -> + write_event(Event), + {ok, State} + end + end; handle_event(Event = {error, GL, {_, "Ranch listener" ++ _, [_, _, Pid, {[_, _, {stacktrace, [{M, F, A, _}|_]}|_], _}]}}, @@ -72,8 +105,8 @@ handle_event(Event = {_, GL, _}, State) when node(GL) =:= node() -> handle_event(_, State) -> {ok, State}. -handle_call({expect, Pid, M, F, A}, State) -> - {ok, ok, [{Pid, M, F, A}|State]}; +handle_call({expect, Crash}, State) -> + {ok, ok, [Crash, Crash|State]}; handle_call(_, State) -> {ok, {error, bad_query}, State}. @@ -81,12 +114,32 @@ handle_info(_, State) -> {ok, State}. terminate(_, _) -> + spawn(fun() -> error_logger:tty(true) end), ok. code_change(_, State, _) -> {ok, State}. +%% Internal. + write_event(Event) -> error_logger_tty_h:write_event( {erlang:universaltime(), Event}, io). + +count_args("]" ++ _, N, 0) -> + N; +count_args("]" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels - 1); +count_args("[" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels + 1); +count_args("}" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels - 1); +count_args("{" ++ Tail, N, Levels) -> + count_args(Tail, N, Levels + 1); +count_args("," ++ Tail, N, Levels = 0) -> + count_args(Tail, N + 1, Levels); +count_args("," ++ Tail, N, Levels) -> + count_args(Tail, N, Levels); +count_args([_|Tail], N, Levels) -> + count_args(Tail, N, Levels). diff --git a/test/cowboy_test.erl b/test/cowboy_test.erl index 4cb2a33..f4a5706 100644 --- a/test/cowboy_test.erl +++ b/test/cowboy_test.erl @@ -30,12 +30,32 @@ do_start(App) -> do_start(App) end. +%% SSL certificate creation and safekeeping. + +make_certs() -> + {_, Cert, Key} = ct_helper:make_certs(), + CertOpts = [{cert, Cert}, {key, Key}], + Pid = spawn(fun() -> receive after infinity -> ok end end), + ?MODULE = ets:new(?MODULE, [ordered_set, public, named_table, + {heir, Pid, undefined}]), + ets:insert(?MODULE, {cert_opts, CertOpts}), + ok. + +get_certs() -> + ets:lookup_element(?MODULE, cert_opts, 2). + %% Quick configuration value retrieval. config(Key, Config) -> {_, Value} = lists:keyfind(Key, 1, Config), Value. +%% Test case description. + +doc(String) -> + ct:comment(String), + ct:log(String). + %% List of all test cases in the suite. all(Suite) -> @@ -60,8 +80,7 @@ init_http(Ref, ProtoOpts, Config) -> [{type, tcp}, {port, Port}, {opts, []}|Config]. init_https(Ref, ProtoOpts, Config) -> - {_, Cert, Key} = ct_helper:make_certs(), - Opts = [{cert, Cert}, {key, Key}], + Opts = get_certs(), {ok, _} = cowboy:start_https(Ref, 100, Opts ++ [{port, 0}], [ {max_keepalive, 50}, {timeout, 500} @@ -70,13 +89,62 @@ init_https(Ref, ProtoOpts, Config) -> [{type, ssl}, {port, Port}, {opts, Opts}|Config]. init_spdy(Ref, ProtoOpts, Config) -> - {_, Cert, Key} = ct_helper:make_certs(), - Opts = [{cert, Cert}, {key, Key}], + Opts = get_certs(), {ok, _} = cowboy:start_spdy(Ref, 100, Opts ++ [{port, 0}], ProtoOpts), Port = ranch:get_port(Ref), [{type, ssl}, {port, Port}, {opts, Opts}|Config]. +%% Common group of listeners used by most suites. + +common_all() -> + [ + {group, http}, + {group, https}, + {group, spdy}, + {group, http_compress}, + {group, https_compress}, + {group, spdy_compress} + ]. + +common_groups(Tests) -> + [ + {http, [parallel], Tests}, + {https, [parallel], Tests}, + {spdy, [parallel], Tests}, + {http_compress, [parallel], Tests}, + {https_compress, [parallel], Tests}, + {spdy_compress, [parallel], Tests} + ]. + +init_common_groups(Name = http, Config, Mod) -> + init_http(Name, [ + {env, [{dispatch, Mod:init_dispatch(Config)}]} + ], Config); +init_common_groups(Name = https, Config, Mod) -> + init_https(Name, [ + {env, [{dispatch, Mod:init_dispatch(Config)}]} + ], Config); +init_common_groups(Name = spdy, Config, Mod) -> + init_spdy(Name, [ + {env, [{dispatch, Mod:init_dispatch(Config)}]} + ], Config); +init_common_groups(Name = http_compress, Config, Mod) -> + init_http(Name, [ + {env, [{dispatch, Mod:init_dispatch(Config)}]}, + {compress, true} + ], Config); +init_common_groups(Name = https_compress, Config, Mod) -> + init_https(Name, [ + {env, [{dispatch, Mod:init_dispatch(Config)}]}, + {compress, true} + ], Config); +init_common_groups(Name = spdy_compress, Config, Mod) -> + init_spdy(Name, [ + {env, [{dispatch, Mod:init_dispatch(Config)}]}, + {compress, true} + ], Config). + %% Support functions for testing using Gun. gun_open(Config) -> diff --git a/test/handlers/long_polling_h.erl b/test/handlers/long_polling_h.erl new file mode 100644 index 0000000..21f1d4d --- /dev/null +++ b/test/handlers/long_polling_h.erl @@ -0,0 +1,27 @@ +%% This module implements a loop handler for long-polling. +%% It starts by sending itself a message after 200ms, +%% then sends another after that for a total of 3 messages. +%% When it receives the last message, it sends a 102 reply back. + +-module(long_polling_h). +-behaviour(cowboy_loop_handler). + +-export([init/3]). +-export([info/3]). +-export([terminate/3]). + +init(_, Req, _) -> + erlang:send_after(200, self(), timeout), + {loop, Req, 2, 5000, hibernate}. + +info(timeout, Req, 0) -> + {ok, Req2} = cowboy_req:reply(102, Req), + {ok, Req2, 0}; +info(timeout, Req, Count) -> + erlang:send_after(200, self(), timeout), + {loop, Req, Count - 1, hibernate}. + +terminate({normal, shutdown}, _, 0) -> + ok; +terminate({error, overflow}, _, _) -> + ok. diff --git a/test/handlers/loop_handler_body_h.erl b/test/handlers/loop_handler_body_h.erl new file mode 100644 index 0000000..db69b02 --- /dev/null +++ b/test/handlers/loop_handler_body_h.erl @@ -0,0 +1,24 @@ +%% This module implements a loop handler that reads +%% the request body after sending itself a message, +%% checks that its size is exactly 100000 bytes, +%% then sends a 200 reply back. + +-module(loop_handler_body_h). +-behaviour(cowboy_loop_handler). + +-export([init/3]). +-export([info/3]). +-export([terminate/3]). + +init(_, Req, _) -> + self() ! timeout, + {loop, Req, undefined, 5000, hibernate}. + +info(timeout, Req, State) -> + {ok, Body, Req2} = cowboy_req:body(Req), + 100000 = byte_size(Body), + {ok, Req3} = cowboy_req:reply(200, Req2), + {ok, Req3, State}. + +terminate({normal, shutdown}, _, _) -> + ok. diff --git a/test/handlers/loop_handler_timeout_h.erl b/test/handlers/loop_handler_timeout_h.erl new file mode 100644 index 0000000..1125046 --- /dev/null +++ b/test/handlers/loop_handler_timeout_h.erl @@ -0,0 +1,23 @@ +%% This module implements a loop handler that sends +%% itself a timeout that will intentionally arrive +%% too late, as it configures itself to only wait +%% 200ms before closing the connection in init/3. +%% This results in a 204 reply being sent back by Cowboy. + +-module(loop_handler_timeout_h). +-behaviour(cowboy_loop_handler). + +-export([init/3]). +-export([info/3]). +-export([terminate/3]). + +init(_, Req, _) -> + erlang:send_after(1000, self(), timeout), + {loop, Req, undefined, 200, hibernate}. + +info(timeout, Req, State) -> + {ok, Req2} = cowboy_req:reply(500, Req), + {ok, Req2, State}. + +terminate({normal, timeout}, _, _) -> + ok. diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index 1c89b1a..4e4c058 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -158,7 +158,6 @@ init_dispatch(Config) -> {"/chunked_response", http_chunked, []}, {"/streamed_response", http_streamed, []}, {"/init_shutdown", http_init_shutdown, []}, - {"/long_polling", http_long_polling, []}, {"/headers/dupe", http_handler, [{headers, [{<<"connection">>, <<"close">>}]}]}, {"/set_resp/header", http_set_resp, @@ -209,9 +208,7 @@ init_dispatch(Config) -> {"/resetags", rest_resource_etags, []}, {"/rest_expires", rest_expires, []}, {"/rest_empty_resource", rest_empty_resource, []}, - {"/loop_recv", http_loop_recv, []}, {"/loop_stream_recv", http_loop_stream_recv, []}, - {"/loop_timeout", http_loop_timeout, []}, {"/", http_handler, []} ]} ]). @@ -266,14 +263,10 @@ The document has moved <A HREF=\"http://www.google.co.il/\">here</A>. </BODY></HTML>", Tests = [ - {102, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n" - "Content-Length: 5000\r\n\r\n", 0:5000/unit:8 >>}, {200, ["GET / HTTP/1.0\r\nHost: localhost\r\n" "Set-Cookie: ", HugeCookie, "\r\n\r\n"]}, {200, "\r\n\r\n\r\n\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n"}, {200, "GET http://proxy/ HTTP/1.1\r\nHost: localhost\r\n\r\n"}, - {200, <<"POST /loop_recv HTTP/1.1\r\nHost: localhost\r\n" - "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>}, {400, "\n"}, {400, "Garbage\r\n\r\n"}, {400, "\r\n\r\n\r\n\r\n\r\n\r\n"}, @@ -287,8 +280,6 @@ The document has moved {408, "GET / HTTP/1.1\r\nHost: localhost\r\n\r"}, {414, Huge}, {400, "GET / HTTP/1.1\r\n" ++ Huge}, - {500, <<"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n" - "Content-Length: 100000\r\n\r\n", 0:100000/unit:8 >>}, {505, "GET / HTTP/1.2\r\nHost: localhost\r\n\r\n"}, {closed, ""}, {closed, "\r\n"}, @@ -303,10 +294,8 @@ The document has moved check_status(Config) -> Tests = [ - {102, "/long_polling"}, {200, "/"}, {200, "/simple"}, - {204, "/loop_timeout"}, {400, "/static/%2f"}, {400, "/static/%2e"}, {400, "/static/%2e%2e"}, @@ -618,12 +607,6 @@ pipeline(Config) -> _ = [{response, nofin, 200, _} = gun:await(ConnPid, Ref) || Ref <- Refs], ok. -pipeline_long_polling(Config) -> - ConnPid = gun_open(Config), - Refs = [gun:get(ConnPid, "/long_polling") || _ <- lists:seq(1, 2)], - _ = [{response, fin, 102, _} = gun:await(ConnPid, Ref) || Ref <- Refs], - ok. - rest_param_all(Config) -> ConnPid = gun_open(Config), %% Accept without param. diff --git a/test/http_SUITE_data/http_long_polling.erl b/test/http_SUITE_data/http_long_polling.erl deleted file mode 100644 index ad4e66e..0000000 --- a/test/http_SUITE_data/http_long_polling.erl +++ /dev/null @@ -1,24 +0,0 @@ -%% Feel free to use, reuse and abuse the code in this file. - --module(http_long_polling). --behaviour(cowboy_http_handler). --export([init/3, handle/2, info/3, terminate/3]). - -init({_Transport, http}, Req, _Opts) -> - erlang:send_after(500, self(), timeout), - {loop, Req, 5, 5000, hibernate}. - -handle(_Req, _State) -> - exit(badarg). - -info(timeout, Req, 0) -> - {ok, Req2} = cowboy_req:reply(102, Req), - {ok, Req2, 0}; -info(timeout, Req, State) -> - erlang:send_after(500, self(), timeout), - {loop, Req, State - 1, hibernate}. - -terminate({normal, shutdown}, _, _) -> - ok; -terminate({error, overflow}, _, _) -> - ok. diff --git a/test/http_SUITE_data/http_loop_recv.erl b/test/http_SUITE_data/http_loop_recv.erl deleted file mode 100644 index d0577f0..0000000 --- a/test/http_SUITE_data/http_loop_recv.erl +++ /dev/null @@ -1,18 +0,0 @@ -%% Feel free to use, reuse and abuse the code in this file. - --module(http_loop_recv). --behaviour(cowboy_loop_handler). --export([init/3, info/3, terminate/3]). - -init({_, http}, Req, _) -> - self() ! recv_timeout, - {loop, Req, undefined, 500, hibernate}. - -info(recv_timeout, Req, State) -> - {ok, Body, Req1} = cowboy_req:body(Req), - 100000 = byte_size(Body), - {ok, Req2} = cowboy_req:reply(200, Req1), - {ok, Req2, State}. - -terminate({normal, shutdown}, _, _) -> - ok. diff --git a/test/http_SUITE_data/http_loop_timeout.erl b/test/http_SUITE_data/http_loop_timeout.erl deleted file mode 100644 index dd3472c..0000000 --- a/test/http_SUITE_data/http_loop_timeout.erl +++ /dev/null @@ -1,16 +0,0 @@ -%% Feel free to use, reuse and abuse the code in this file. - --module(http_loop_timeout). --behaviour(cowboy_loop_handler). --export([init/3, info/3, terminate/3]). - -init({_, http}, Req, _) -> - erlang:send_after(1000, self(), error_timeout), - {loop, Req, undefined, 500, hibernate}. - -info(error_timeout, Req, State) -> - {ok, Req2} = cowboy_req:reply(500, Req), - {ok, Req2, State}. - -terminate({normal, timeout}, _, _) -> - ok. diff --git a/test/loop_handler_SUITE.erl b/test/loop_handler_SUITE.erl new file mode 100644 index 0000000..5f69490 --- /dev/null +++ b/test/loop_handler_SUITE.erl @@ -0,0 +1,87 @@ +%% Copyright (c) 2011-2014, Loïc Hoguin <[email protected]> +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(loop_handler_SUITE). +-compile(export_all). + +-import(cowboy_test, [config/2]). +-import(cowboy_test, [doc/1]). +-import(cowboy_test, [gun_open/1]). + +%% ct. + +all() -> + cowboy_test:common_all(). + +groups() -> + cowboy_test:common_groups(cowboy_test:all(?MODULE)). + +init_per_group(Name, Config) -> + cowboy_test:init_common_groups(Name, Config, ?MODULE). + +end_per_group(Name, _) -> + cowboy:stop_listener(Name). + +%% Dispatch configuration. + +init_dispatch(_) -> + cowboy_router:compile([{'_', [ + {"/long_polling", long_polling_h, []}, + {"/loop_body", loop_handler_body_h, []}, + {"/loop_timeout", loop_handler_timeout_h, []} + ]}]). + +%% Tests. + +long_polling(Config) -> + doc("Simple long-polling."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/long_polling"), + {response, fin, 102, _} = gun:await(ConnPid, Ref), + ok. + +long_polling_body(Config) -> + doc("Long-polling with a body that falls within the configurable limits."), + ConnPid = gun_open(Config), + Ref = gun:post(ConnPid, "/long_polling", [], << 0:5000/unit:8 >>), + {response, fin, 102, _} = gun:await(ConnPid, Ref), + ok. + +long_polling_body_too_large(Config) -> + doc("Long-polling with a body that exceeds the configurable limits."), + ConnPid = gun_open(Config), + Ref = gun:post(ConnPid, "/long_polling", [], << 0:100000/unit:8 >>), + {response, fin, 500, _} = gun:await(ConnPid, Ref), + ok. + +long_polling_pipeline(Config) -> + doc("Pipeline of long-polling calls."), + ConnPid = gun_open(Config), + Refs = [gun:get(ConnPid, "/long_polling") || _ <- lists:seq(1, 2)], + _ = [{response, fin, 102, _} = gun:await(ConnPid, Ref) || Ref <- Refs], + ok. + +loop_body(Config) -> + doc("Check that a loop handler can read the request body in info/3."), + ConnPid = gun_open(Config), + Ref = gun:post(ConnPid, "/loop_body", [], << 0:100000/unit:8 >>), + {response, fin, 200, _} = gun:await(ConnPid, Ref), + ok. + +loop_timeout(Config) -> + doc("Ensure that the loop handler timeout results in a 204 response."), + ConnPid = gun_open(Config), + Ref = gun:get(ConnPid, "/loop_timeout"), + {response, fin, 204, _} = gun:await(ConnPid, Ref), + ok. |