diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b946079..836c9e6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,13 +51,19 @@ jobs: - name: Compile run: rebar3 compile - - name: EUnit tests - run: rebar3 eunit - - name: Dialyzer - run: rebar3 dialyzer + - name: XRef run: rebar3 xref + - name: Dialyzer + run: rebar3 dialyzer + + - name: Elvis + run: rebar3 lint + + - name: Common Test + run: rebar3 do ct, cover + - name: Covertool run: rebar3 covertool generate - uses: codecov/codecov-action@v4 diff --git a/.gitignore b/.gitignore index 76bfc3f..af9d429 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,6 @@ doc/*.html !doc/tpl.html _build -deps/** -ebin/** -*.beam -.eunit -*~ -#* .dialyzer.plt .rebar rebar3.crashdump diff --git a/priv/README.md b/priv/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/priv/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/rebar.config b/rebar.config index eedbaba..8143042 100644 --- a/rebar.config +++ b/rebar.config @@ -16,7 +16,20 @@ ]} ]}, {test, [ - {deps, [{hackney, "1.20.1"}]} + {deps, [{hackney, "1.20.1"}]}, + {extra_src_dirs, [ + {"test", [ + {recursive, true} + ]} + ]}, + {cover_enabled, true}, + {cover_export_enabled, true}, + {cover_excl_mods, [ + elli_handler + ]}, + {covertool, [{coverdata_files, ["ct.coverdata"]}]}, + {cover_opts, [verbose]}, + {ct_opts, [{ct_hooks, [cth_surefire]}]} ]} ]}. @@ -30,13 +43,4 @@ {provider_hooks, [{pre, [{eunit, lint}]}]}. {dialyzer, [{plt_extra_apps, [ssl]}, {warnings, [unknown]}]}. -{cover_enabled, true}. -{cover_export_enabled, true}. -{cover_excl_mods, [ - elli_handler -]}. -{covertool, [{coverdata_files, ["eunit.coverdata"]}]}. - {post_hooks, [{edoc, "doc/build.sh"}]}. - -{ct_opts, [{ct_hooks, [cth_surefire]}]}. diff --git a/src/elli_example_callback.erl b/src/elli_example_callback.erl index 355d1ed..5fb1928 100644 --- a/src/elli_example_callback.erl +++ b/src/elli_example_callback.erl @@ -135,7 +135,7 @@ handle('GET', [<<"decoded-list">>], Req) -> handle('GET', [<<"sendfile">>], _Req) -> %% Returning {file, "/path/to/file"} instead of the body results %% in Elli using sendfile. - F = "README.md", + F = filename:join(code:priv_dir(elli), "README.md"), {ok, [], {file, F}}; handle('GET', [<<"send_no_file">>], _Req) -> @@ -145,14 +145,14 @@ handle('GET', [<<"send_no_file">>], _Req) -> {ok, [], {file, F}}; handle('GET', [<<"sendfile">>, <<"error">>], _Req) -> - F = "test", + F = code:priv_dir(elli), {ok, [], {file, F}}; handle('GET', [<<"sendfile">>, <<"range">>], Req) -> %% Read the Range header of the request and use the normalized %% range with sendfile, otherwise send the entire file when %% no range is present, or respond with a 416 if the range is invalid. - F = "README.md", + F = filename:join(code:priv_dir(elli), "README.md"), {ok, [], {file, F, elli_request:get_range(Req)}}; handle('GET', [<<"compressed">>], _Req) -> diff --git a/src/elli_http.erl b/src/elli_http.erl index d733e88..aaa131d 100644 --- a/src/elli_http.erl +++ b/src/elli_http.erl @@ -27,6 +27,10 @@ -export_type([version/0]). +-ifdef(TEST). +-export([get_body/5]). +-endif. + %% @type version(). HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}'. -type version() :: {0, 9} | {1, 0} | {1, 1}. @@ -895,21 +899,3 @@ status(510) -> <<"510 Not Extended">>; status(511) -> <<"511 Network Authentication Required">>; status(I) when is_integer(I), I >= 100, I < 1000 -> list_to_binary(io_lib:format("~B Status", [I])); status(B) when is_binary(B) -> B. - - -%% -%% UNIT TESTS -%% - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - -get_body_test() -> - Socket = undefined, - Headers = [{<<"Content-Length">>, <<" 42 ">>}], - Buffer = binary:copy(<<".">>, 42), - Opts = [], - Callback = {no_mod, []}, - ?assertMatch({Buffer, <<>>}, - get_body(Socket, Headers, Buffer, Opts, Callback)). --endif. diff --git a/src/elli_test.erl b/src/elli_test.erl index 0fa1fbb..1582e49 100644 --- a/src/elli_test.erl +++ b/src/elli_test.erl @@ -25,23 +25,3 @@ call(Method, Path, Headers, Body, Opts) -> Body, {1, 1}, undefined, {Callback, CallbackArgs}), ok = Callback:handle_event(elli_startup, [], CallbackArgs), Callback:handle(Req, CallbackArgs). - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - -hello_world_test() -> - ?assertMatch({ok, [], <<"Hello World!">>}, - elli_test:call('GET', <<"/hello/world/">>, [], <<>>, - ?EXAMPLE_CONF)), - ?assertMatch({ok, [], <<"Hello Test1">>}, - elli_test:call('GET', <<"/hello/?name=Test1">>, [], <<>>, - ?EXAMPLE_CONF)), - ?assertMatch({ok, - [{<<"content-type">>, - <<"application/json; charset=ISO-8859-1">>}], - <<"{\"name\" : \"Test2\"}">>}, - elli_test:call('GET', <<"/type?name=Test2">>, - [{<<"accept">>, <<"application/json">>}], <<>>, - ?EXAMPLE_CONF)). - --endif. %% TEST diff --git a/test/elli_tests.erl b/test/elli_SUITE.erl similarity index 81% rename from test/elli_tests.erl rename to test/elli_SUITE.erl index 266808c..02326fa 100644 --- a/test/elli_tests.erl +++ b/test/elli_SUITE.erl @@ -1,79 +1,30 @@ --module(elli_tests). --include_lib("eunit/include/eunit.hrl"). +-module(elli_SUITE). +-include_lib("stdlib/include/assert.hrl"). -include("elli.hrl"). --include("elli_test.hrl"). +-include("test/support/elli_test.hrl"). --define(README, "README.md"). +-compile([export_all, nowarn_export_all]). + +-define(README, filename:join(code:priv_dir(elli), "README.md")). -define(VTB(T1, T2, LB, UB), time_diff_to_micro_seconds(T1, T2) >= LB andalso time_diff_to_micro_seconds(T1, T2) =< UB). -include_lib("kernel/include/logger.hrl"). -time_diff_to_micro_seconds(T1, T2) -> - erlang:convert_time_unit( - get_timing_value(T2) - - get_timing_value(T1), - native, - micro_seconds). - -elli_test_() -> - {setup, - fun setup/0, fun teardown/1, - [{foreach, - fun init_stats/0, fun clear_stats/1, - [?_test(hello_world()), - ?_test(keep_alive_timings()), - ?_test(not_found()), - ?_test(crash()), - ?_test(invalid_return()), - ?_test(no_compress()), - ?_test(gzip()), - ?_test(deflate()), - ?_test(exception_flow()), - ?_test(hello_iolist()), - ?_test(accept_content_type()), - ?_test(user_connection()), - ?_test(get_args()), - ?_test(decoded_get_args()), - ?_test(decoded_get_args_list()), - ?_test(post_args()), - ?_test(shorthand()), - ?_test(ip()), - ?_test(found()), - ?_test(too_many_headers()), - ?_test(too_big_body()), - ?_test(way_too_big_body()), - ?_test(bad_request_line()), - ?_test(content_length()), - ?_test(user_content_length()), - ?_test(headers()), - ?_test(chunked()), - ?_test(sendfile()), - ?_test(send_no_file()), - ?_test(sendfile_error()), - ?_test(sendfile_range()), - ?_test(slow_client()), - ?_test(post_pipeline()), - ?_test(get_pipeline()), - ?_test(head()), - ?_test(no_body()), - ?_test(sends_continue()) - ]} - ]}. +% +% Configuration. -get_timing_value(Key) -> - [{timings, Timings}] = ets:lookup(elli_stat_table, timings), - proplists:get_value(Key, Timings). - -get_size_value(Key) -> - [{sizes, Sizes}] = ets:lookup(elli_stat_table, sizes), - proplists:get_value(Key, Sizes). +all() -> + [ + Fun + || {Fun, 1} <- ?MODULE:module_info(exports), + not lists:member(Fun, [module_info, init_per_suite, end_per_suite, + get_timing_value, get_size_value, + status, body, headers]) + ]. -setup() -> - application:start(crypto), - application:start(public_key), - application:start(ssl), - {ok, _} = application:ensure_all_started(hackney), +init_per_suite(Config0) -> + {ok, StartedApps} = application:ensure_all_started(hackney), Config = [ {mods, [ @@ -86,19 +37,28 @@ setup() -> {callback_args, Config}, {port, 3001}]), unlink(P), - [P]. + [{pids, [P]}, {started_apps, StartedApps} | Config0]. -teardown(Pids) -> - [elli:stop(P) || P <- Pids]. +end_per_suite(Config) -> + Pids = proplists:get_value(pids, Config), + [elli:stop(P) || P <- Pids], + lists:foreach(fun (App) -> + application:stop(App) + end, + proplists:get_value(started_apps, Config)). -init_stats() -> - ets:new(elli_stat_table, [set, named_table, public]). +init_per_testcase(_Testcase, Config) -> + ets:new(elli_stat_table, [set, named_table, public]), + Config. -clear_stats(_) -> - ets:delete(elli_stat_table). +end_per_testcase(_Testcase, Config) -> + ets:delete(elli_stat_table), + Config. +% +% Tests. -accessors_test_() -> +accessors(_Config) -> RawPath = <<"/foo/bar">>, Headers = [{<<"content-type">>, <<"application/x-www-form-urlencoded">>}], Method = 'POST', @@ -114,30 +74,26 @@ accessors_test_() -> [ %% POST /foo/bar - ?_assertMatch(RawPath, elli_request:raw_path(Req1)), - ?_assertMatch(Headers, elli_request:headers(Req1)), - ?_assertMatch(Method, elli_request:method(Req1)), - ?_assertMatch(Body, elli_request:body(Req1)), - ?_assertMatch(Args, elli_request:post_args_decoded(Req1)), - ?_assertMatch(undefined, elli_request:post_arg(<<"foo">>, Req1)), - ?_assertMatch(undefined, elli_request:post_arg_decoded(<<"foo">>, Req1)), - ?_assertMatch(Name, elli_request:post_arg_decoded(<<"name">>, Req1)), + ?assertMatch(RawPath, elli_request:raw_path(Req1)), + ?assertMatch(Headers, elli_request:headers(Req1)), + ?assertMatch(Method, elli_request:method(Req1)), + ?assertMatch(Body, elli_request:body(Req1)), + ?assertMatch(Args, elli_request:post_args_decoded(Req1)), + ?assertMatch(undefined, elli_request:post_arg(<<"foo">>, Req1)), + ?assertMatch(undefined, elli_request:post_arg_decoded(<<"foo">>, Req1)), + ?assertMatch(Name, elli_request:post_arg_decoded(<<"name">>, Req1)), %% GET /foo/bar - ?_assertMatch(Headers, elli_request:headers(Req2)), + ?assertMatch(Headers, elli_request:headers(Req2)), - ?_assertMatch(Args, elli_request:get_args(Req2)), - ?_assertMatch(undefined, elli_request:get_arg_decoded(<<"foo">>, Req2)), - ?_assertMatch(Name, elli_request:get_arg_decoded(<<"name">>, Req2)), - ?_assertMatch([], elli_request:post_args(Req2)), + ?assertMatch(Args, elli_request:get_args(Req2)), + ?assertMatch(undefined, elli_request:get_arg_decoded(<<"foo">>, Req2)), + ?assertMatch(Name, elli_request:get_arg_decoded(<<"name">>, Req2)), + ?assertMatch([], elli_request:post_args(Req2)), - ?_assertMatch({error, not_supported}, elli_request:chunk_ref(#req{})) + ?assertMatch({error, not_supported}, elli_request:chunk_ref(#req{})) ]. - -%%% Integration tests -%%% Use hackney to actually call Elli over the network. - -hello_world() -> +hello_world(_Config) -> Response = hackney:get("http://localhost:3001/hello/world"), ?assertMatch(200, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, @@ -166,8 +122,7 @@ hello_world() -> ?assert(?VTB(user_start, user_end, 1000000, 1200000)), ?assert(?VTB(send_start, send_end, 1, 2000)). - -keep_alive_timings() -> +keep_alive_timings(_Config) -> Transport = hackney_tcp, Host = <<"localhost">>, @@ -194,47 +149,21 @@ keep_alive_timings() -> hackney:close(ConnRef). -keep_alive_timings(Status, Headers, HCRef) -> - ?assertMatch(200, Status), - ?assertHeadersEqual([{<<"connection">>,<<"Keep-Alive">>}, - {<<"content-length">>,<<"12">>}], Headers), - ?assertMatch({ok, <<"Hello World!">>}, hackney:body(HCRef)), - %% sizes - ?assertMatch(63, get_size_value(resp_headers)), - ?assertMatch(12, get_size_value(resp_body)), - %% timings - ?assertNotMatch(undefined, get_timing_value(request_start)), - ?assertNotMatch(undefined, get_timing_value(headers_start)), - ?assertNotMatch(undefined, get_timing_value(headers_end)), - ?assertNotMatch(undefined, get_timing_value(body_start)), - ?assertNotMatch(undefined, get_timing_value(body_end)), - ?assertNotMatch(undefined, get_timing_value(user_start)), - ?assertNotMatch(undefined, get_timing_value(user_end)), - ?assertNotMatch(undefined, get_timing_value(send_start)), - ?assertNotMatch(undefined, get_timing_value(send_end)), - ?assertNotMatch(undefined, get_timing_value(request_end)), - %% check timings - ?assert(?VTB(request_start, request_end, 1000000, 1200000)), - ?assert(?VTB(headers_start, headers_end, 1, 100)), - ?assert(?VTB(body_start, body_end, 1, 100)), - ?assert(?VTB(user_start, user_end, 1000000, 1200000)), - ?assert(?VTB(send_start, send_end, 1, 2000)). - -not_found() -> +not_found(_Config) -> Response = hackney:get("http://localhost:3001/foobarbaz"), ?assertMatch(404, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, {<<"content-length">>, <<"9">>}], headers(Response)), ?assertMatch(<<"Not Found">>, body(Response)). -crash() -> +crash(_Config) -> Response = hackney:get("http://localhost:3001/crash"), ?assertMatch(500, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, {<<"content-length">>, <<"21">>}], headers(Response)), ?assertMatch(<<"Internal server error">>, body(Response)). -invalid_return() -> +invalid_return(_Config) -> %% Elli should return 500 for handlers returning bogus responses. Response = hackney:get("http://localhost:3001/invalid_return"), ?assertMatch(500, status(Response)), @@ -242,7 +171,7 @@ invalid_return() -> {<<"content-length">>, <<"21">>}], headers(Response)), ?assertMatch(<<"Internal server error">>, body(Response)). -no_compress() -> +no_compress(_Config) -> Response = hackney:get("http://localhost:3001/compressed"), ?assertMatch(200, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, @@ -250,36 +179,23 @@ no_compress() -> ?assertEqual(binary:copy(<<"Hello World!">>, 86), body(Response)). -compress(Encoding, Length) -> - Response = hackney:get("http://localhost:3001/compressed", - [{<<"Accept-Encoding">>, Encoding}]), - ?assertMatch(200, status(Response)), - ?assertHeadersEqual([{<<"Content-Encoding">>, Encoding}, - {<<"connection">>, <<"Keep-Alive">>}, - {<<"content-length">>, Length}], headers(Response)), - ?assertEqual(binary:copy(<<"Hello World!">>, 86), - uncompress(Encoding, body(Response))). - -uncompress(<<"gzip">>, Data) -> zlib:gunzip(Data); -uncompress(<<"deflate">>, Data) -> zlib:uncompress(Data). +gzip(_Config) -> compress(<<"gzip">>, <<"41">>). -gzip() -> compress(<<"gzip">>, <<"41">>). +deflate(_Config) -> compress(<<"deflate">>, <<"29">>). -deflate() -> compress(<<"deflate">>, <<"29">>). - -exception_flow() -> +exception_flow(_Config) -> Response = hackney:get("http://localhost:3001/403"), ?assertMatch(403, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, {<<"content-length">>, <<"9">>}], headers(Response)), ?assertMatch(<<"Forbidden">>, body(Response)). -hello_iolist() -> +hello_iolist(_Config) -> Url = "http://localhost:3001/hello/iolist?name=knut", Response = hackney:get(Url), ?assertMatch(<<"Hello knut">>, body(Response)). -accept_content_type() -> +accept_content_type(_Config) -> Json = hackney:get("http://localhost:3001/type?name=knut", [{"Accept", "application/json"}]), ?assertMatch(<<"{\"name\" : \"knut\"}">>, body(Json)), @@ -287,7 +203,7 @@ accept_content_type() -> [{"Accept", "text/plain"}]), ?assertMatch(<<"name: knut">>, body(Text)). -user_connection() -> +user_connection(_Config) -> Url = "http://localhost:3001/user/defined/behaviour", Response = hackney:get(Url), ?assertMatch(304, status(Response)), @@ -295,22 +211,21 @@ user_connection() -> {<<"content-length">>, <<"123">>}], headers(Response)), ?assertMatch(<<>>, body(Response)). - -get_args() -> +get_args(_Config) -> Response = hackney:get("http://localhost:3001/hello?name=knut"), ?assertMatch(<<"Hello knut">>, body(Response)). -decoded_get_args() -> +decoded_get_args(_Config) -> Url = "http://localhost:3001/decoded-hello?name=knut%3D", Response = hackney:get(Url), ?assertMatch(<<"Hello knut=">>, body(Response)). -decoded_get_args_list() -> +decoded_get_args_list(_Config) -> Url = "http://localhost:3001/decoded-list?name=knut%3D&foo", Response = hackney:get(Url), ?assertMatch(<<"Hello knut=">>, body(Response)). -post_args() -> +post_args(_Config) -> Body = <<"name=foo&city=New%20York">>, ContentType = <<"application/x-www-form-urlencoded">>, @@ -320,19 +235,19 @@ post_args() -> ?assertMatch(200, status(Response)), ?assertMatch(<<"Hello foo of New York">>, body(Response)). -shorthand() -> +shorthand(_Config) -> Response = hackney:get("http://localhost:3001/shorthand"), ?assertMatch(200, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, {<<"content-length">>, <<"5">>}], headers(Response)), ?assertMatch(<<"hello">>, body(Response)). -ip() -> +ip(_Config) -> Response = hackney:get("http://localhost:3001/ip"), ?assertMatch(200, status(Response)), ?assertMatch(<<"127.0.0.1">>, body(Response)). -found() -> +found(_Config) -> Response = hackney:get("http://localhost:3001/302"), ?assertMatch(302, status(Response)), ?assertHeadersEqual([{<<"Location">>, <<"/hello/world">>}, @@ -340,23 +255,22 @@ found() -> {<<"content-length">>, <<"0">>}], headers(Response)), ?assertMatch(<<>>, body(Response)). -too_many_headers() -> +too_many_headers(_Config) -> Headers = lists:duplicate(100, {<<"X-Foo">>, <<"Bar">>}), Response = hackney:get("http://localhost:3001/foo", Headers), ?assertMatch(400, status(Response)). -too_big_body() -> +too_big_body(_Config) -> Body = binary:copy(<<"x">>, (1024 * 1000) + 1), Response = hackney:post("http://localhost:3001/foo", [], Body), ?assertMatch(413, status(Response)). -way_too_big_body() -> +way_too_big_body(_Config) -> Body = binary:copy(<<"x">>, (1024 * 2000) + 1), ?assertMatch({error, closed}, hackney:post("http://localhost:3001/foo", [], Body)). - -bad_request_line() -> +bad_request_line(_Config) -> {ok, Socket} = gen_tcp:connect("127.0.0.1", 3001, [{active, false}, binary]), @@ -366,8 +280,7 @@ bad_request_line() -> "content-length: 11\r\n\r\nBad Request">>}, gen_tcp:recv(Socket, 0)). - -content_length() -> +content_length(_Config) -> Response = hackney:get("http://localhost:3001/304"), ?assertMatch(304, status(Response)), @@ -376,7 +289,7 @@ content_length() -> {<<"content-length">>, <<"7">>}], headers(Response)), ?assertMatch(<<>>, body(Response)). -user_content_length() -> +user_content_length(_Config) -> Headers = <<"Foo: bar\n\n">>, Client = start_slow_client(3001, "/user/content-length"), send(Client, Headers, 128), @@ -387,14 +300,14 @@ user_content_length() -> "foobar">>}, gen_tcp:recv(Client, 0)). -headers() -> +headers_(_Config) -> Response = hackney:get("http://localhost:3001/headers.html"), Headers = headers(Response), ?assert(proplists:is_defined(<<"X-Custom">>, Headers)), ?assertMatch(<<"foobar">>, proplists:get_value(<<"X-Custom">>, Headers)). -chunked() -> +chunked(_Config) -> Expected = <<"chunk10chunk9chunk8chunk7chunk6chunk5chunk4chunk3chunk2chunk1">>, Response = hackney:get("http://localhost:3001/chunked"), @@ -419,7 +332,7 @@ chunked() -> ?assertNotMatch(undefined, get_timing_value(send_end)), ?assertNotMatch(undefined, get_timing_value(request_end)). -sendfile() -> +sendfile(_Config) -> Response = hackney:get("http://localhost:3001/sendfile"), F = ?README, {ok, Expected} = file:read_file(F), @@ -444,7 +357,7 @@ sendfile() -> ?assertNotMatch(undefined, get_timing_value(send_end)), ?assertNotMatch(undefined, get_timing_value(request_end)). -send_no_file() -> +send_no_file(_Config) -> Response = hackney:get("http://localhost:3001/send_no_file"), ?assertMatch(500, status(Response)), @@ -452,7 +365,7 @@ send_no_file() -> headers(Response)), ?assertMatch(<<"Server Error">>, body(Response)). -sendfile_error() -> +sendfile_error(_Config) -> Response = hackney:get("http://localhost:3001/sendfile/error"), ?assertMatch(500, status(Response)), @@ -460,7 +373,7 @@ sendfile_error() -> headers(Response)), ?assertMatch(<<"Server Error">>, body(Response)). -sendfile_range() -> +sendfile_range(_Config) -> Url = "http://localhost:3001/sendfile/range", Headers = [{"Range", "bytes=300-699"}], Response = hackney:get(Url, Headers), @@ -477,7 +390,7 @@ sendfile_range() -> headers(Response)), ?assertEqual(Expected, body(Response)). -slow_client() -> +slow_client(_Config) -> Body = <<"name=foobarbaz">>, Headers = <<"content-length: ", (?I2B(size(Body)))/binary, "\r\n\r\n">>, Client = start_slow_client(3001, "/hello"), @@ -498,8 +411,7 @@ slow_client() -> ?assert(?VTB(user_start, user_end, 1, 100)), ?assert(?VTB(send_start, send_end, 1, 200)). - -post_pipeline() -> +post_pipeline(_Config) -> Body = <<"name=elli&city=New%20York">>, Headers = <<"content-length: ", (?I2B(size(Body)))/binary, "\r\n", "Content-Type: application/x-www-form-urlencoded", "\r\n", @@ -526,7 +438,7 @@ post_pipeline() -> ?assertEqual(binary:copy(ExpectedResponse, 2), Res). -get_pipeline() -> +get_pipeline(_Config) -> Headers = <<"User-Agent: sloow\r\n\r\n">>, Req = <<"GET /hello?name=elli HTTP/1.1\r\n", Headers/binary>>, @@ -550,13 +462,13 @@ get_pipeline() -> ?assertEqual(binary:copy(ExpectedResponse, 2), Res). -head() -> +head(_Config) -> Response = hackney:head("http://localhost:3001/head"), ?assertMatch(200, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, {<<"content-length">>, <<"20">>}], headers(Response)). -no_body() -> +no_body(_Config) -> Response = hackney:get("http://localhost:3001/304"), ?assertMatch(304, status(Response)), ?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>}, @@ -564,7 +476,7 @@ no_body() -> {<<"Etag">>, <<"foobar">>}], headers(Response)), ?assertMatch(<<>>, body(Response)). -sends_continue() -> +sends_continue(_Config) -> {ok, Socket} = gen_tcp:connect("127.0.0.1", 3001, [{active, false}, binary]), Body = <<"name=elli&city=New%20York">>, @@ -590,32 +502,7 @@ sends_continue() -> ?assertMatch({ok, ExpectedResponse}, gen_tcp:recv(Socket, size(ExpectedResponse))). -%%% Slow client, sending only the specified byte size every millisecond - -start_slow_client(Port, Url) -> - case gen_tcp:connect("127.0.0.1", Port, [{active, false}, binary]) of - {ok, Socket} -> - gen_tcp:send(Socket, "GET " ++ Url ++ " HTTP/1.1\r\n"), - Socket; - {error, Reason} -> - throw({slow_client_error, Reason}) - end. - -send(_Socket, <<>>, _) -> - ok; -send(Socket, B, ChunkSize) -> - {Part, Rest} = case B of - <> -> {P, R}; - P -> {P, <<>>} - end, - %%?LOG_INFO("~p~n", [Part]), - gen_tcp:send(Socket, Part), - timer:sleep(1), - send(Socket, Rest, ChunkSize). - -%%% Unit tests - -body_qs_test() -> +body_qs(_Config) -> Expected = [{<<"foo">>, <<"bar">>}, {<<"baz">>, <<"bang">>}, {<<"found">>, true}], @@ -625,7 +512,7 @@ body_qs_test() -> original_headers = Headers, headers = Headers})). -to_proplist_test() -> +to_proplist(_Config) -> Req = #req{method = 'GET', path = [<<"crash">>], args = [], @@ -654,24 +541,22 @@ to_proplist_test() -> {callback, {mod, []}}], ?assertEqual(Prop, elli_request:to_proplist(Req)). -is_request_test() -> +is_request(_Config) -> ?assert(elli_request:is_request(#req{})), ?assertNot(elli_request:is_request({req, foobar})). - -query_str_test_() -> +query_str(_Config) -> MakeReq = fun(Path) -> #req{raw_path = Path} end, [ %% For empty query strings, expect `query_str` to return an empty binary. - ?_assertMatch(<<>>, elli_request:query_str(MakeReq(<<"/foo">>))), - ?_assertMatch(<<>>, elli_request:query_str(MakeReq(<<"/foo?">>))), + ?assertMatch(<<>>, elli_request:query_str(MakeReq(<<"/foo">>))), + ?assertMatch(<<>>, elli_request:query_str(MakeReq(<<"/foo?">>))), %% Otherwise it should return everything to the right hand side of `?`. - ?_assertMatch(<<"bar=baz&baz=bang">>, + ?assertMatch(<<"bar=baz&baz=bang">>, elli_request:query_str(MakeReq(<<"/foo?bar=baz&baz=bang">>))) ]. - -get_range_test_() -> +get_range(_Config) -> Req = #req{headers = [{<<"range">>, <<"bytes=0-99 ,500-999 , -800">>}]}, OffsetReq = #req{headers = [{<<"range">>, <<"bytes=200-">>}]}, @@ -680,12 +565,12 @@ get_range_test_() -> ByteRangeSet = [{bytes, 0, 99}, {bytes, 500, 999}, {suffix, 800}], - [?_assertMatch(ByteRangeSet, elli_request:get_range(Req)), - ?_assertMatch([{offset, 200}], elli_request:get_range(OffsetReq)), - ?_assertMatch([], elli_request:get_range(UndefReq)), - ?_assertMatch(parse_error, elli_request:get_range(BadReq))]. + [?assertMatch(ByteRangeSet, elli_request:get_range(Req)), + ?assertMatch([{offset, 200}], elli_request:get_range(OffsetReq)), + ?assertMatch([], elli_request:get_range(UndefReq)), + ?assertMatch(parse_error, elli_request:get_range(BadReq))]. -normalize_range_test_() -> +normalize_range(_Config) -> Size = 1000, Bytes1 = {bytes, 200, 400}, @@ -702,26 +587,25 @@ normalize_range_test_() -> Invalid5 = parse_error, Invalid6 = [{bytes, 0, 100}, {suffix, 42}], - [?_assertMatch({200, 201}, elli_util:normalize_range(Bytes1, Size)), - ?_assertMatch({0, Size}, elli_util:normalize_range(Bytes2, Size)), - ?_assertEqual({Size - 303, 303}, elli_util:normalize_range(Suffix, Size)), - ?_assertEqual({42, Size - 42}, elli_util:normalize_range(Offset, Size)), - ?_assertMatch({200, 400}, elli_util:normalize_range(Normal, Size)), - ?_assertMatch({0, 1000}, elli_util:normalize_range(Set, Size)), - ?_assertMatch(undefined, elli_util:normalize_range(EmptySet, Size)), - ?_assertMatch(invalid_range, elli_util:normalize_range(Invalid1, Size)), - ?_assertMatch(invalid_range, elli_util:normalize_range(Invalid2, Size)), - ?_assertMatch(invalid_range, elli_util:normalize_range(Invalid3, Size)), - ?_assertMatch(invalid_range, elli_util:normalize_range(Invalid4, Size)), - ?_assertMatch(invalid_range, elli_util:normalize_range(Invalid5, Size)), - ?_assertMatch(invalid_range, elli_util:normalize_range(Invalid6, Size))]. - - -encode_range_test() -> + [?assertMatch({200, 201}, elli_util:normalize_range(Bytes1, Size)), + ?assertMatch({0, Size}, elli_util:normalize_range(Bytes2, Size)), + ?assertEqual({Size - 303, 303}, elli_util:normalize_range(Suffix, Size)), + ?assertEqual({42, Size - 42}, elli_util:normalize_range(Offset, Size)), + ?assertMatch({200, 400}, elli_util:normalize_range(Normal, Size)), + ?assertMatch({0, 1000}, elli_util:normalize_range(Set, Size)), + ?assertMatch(undefined, elli_util:normalize_range(EmptySet, Size)), + ?assertMatch(invalid_range, elli_util:normalize_range(Invalid1, Size)), + ?assertMatch(invalid_range, elli_util:normalize_range(Invalid2, Size)), + ?assertMatch(invalid_range, elli_util:normalize_range(Invalid3, Size)), + ?assertMatch(invalid_range, elli_util:normalize_range(Invalid4, Size)), + ?assertMatch(invalid_range, elli_util:normalize_range(Invalid5, Size)), + ?assertMatch(invalid_range, elli_util:normalize_range(Invalid6, Size))]. + +encode_range(_Config) -> Expected = [<<"bytes ">>,<<"*">>,<<"/">>,<<"42">>], ?assertMatch(Expected, elli_util:encode_range(invalid_range, 42)). -register_test() -> +register(_Config) -> ?assertMatch(undefined, whereis(elli)), Config = [ {name, {local, elli}}, @@ -735,9 +619,103 @@ register_test() -> ?assertMatch(Pid, whereis(elli)), ok. -invalid_callback_test() -> +invalid_callback(_Config) -> try elli:start_link([{callback, elli}]) - catch _:E -> + catch E -> ?assertMatch(invalid_callback, E) end. + +hello_world2(_Config) -> + ?assertMatch({ok, [], <<"Hello World!">>}, + elli_test:call('GET', <<"/hello/world/">>, [], <<>>, + ?EXAMPLE_CONF)), + ?assertMatch({ok, [], <<"Hello Test1">>}, + elli_test:call('GET', <<"/hello/?name=Test1">>, [], <<>>, + ?EXAMPLE_CONF)), + ?assertMatch({ok, + [{<<"content-type">>, + <<"application/json; charset=ISO-8859-1">>}], + <<"{\"name\" : \"Test2\"}">>}, + elli_test:call('GET', <<"/type?name=Test2">>, + [{<<"accept">>, <<"application/json">>}], <<>>, + ?EXAMPLE_CONF)). + +% +% Private. + +time_diff_to_micro_seconds(T1, T2) -> + erlang:convert_time_unit( + get_timing_value(T2) - + get_timing_value(T1), + native, + micro_seconds). + +get_timing_value(Key) -> + [{timings, Timings}] = ets:lookup(elli_stat_table, timings), + proplists:get_value(Key, Timings). + +get_size_value(Key) -> + [{sizes, Sizes}] = ets:lookup(elli_stat_table, sizes), + proplists:get_value(Key, Sizes). + +keep_alive_timings(Status, Headers, HCRef) -> + ?assertMatch(200, Status), + ?assertHeadersEqual([{<<"connection">>,<<"Keep-Alive">>}, + {<<"content-length">>,<<"12">>}], Headers), + ?assertMatch({ok, <<"Hello World!">>}, hackney:body(HCRef)), + %% sizes + ?assertMatch(63, get_size_value(resp_headers)), + ?assertMatch(12, get_size_value(resp_body)), + %% timings + ?assertNotMatch(undefined, get_timing_value(request_start)), + ?assertNotMatch(undefined, get_timing_value(headers_start)), + ?assertNotMatch(undefined, get_timing_value(headers_end)), + ?assertNotMatch(undefined, get_timing_value(body_start)), + ?assertNotMatch(undefined, get_timing_value(body_end)), + ?assertNotMatch(undefined, get_timing_value(user_start)), + ?assertNotMatch(undefined, get_timing_value(user_end)), + ?assertNotMatch(undefined, get_timing_value(send_start)), + ?assertNotMatch(undefined, get_timing_value(send_end)), + ?assertNotMatch(undefined, get_timing_value(request_end)), + %% check timings + ?assert(?VTB(request_start, request_end, 1000000, 1200000)), + ?assert(?VTB(headers_start, headers_end, 1, 100)), + ?assert(?VTB(body_start, body_end, 1, 100)), + ?assert(?VTB(user_start, user_end, 1000000, 1200000)), + ?assert(?VTB(send_start, send_end, 1, 2000)). + +compress(Encoding, Length) -> + Response = hackney:get("http://localhost:3001/compressed", + [{<<"Accept-Encoding">>, Encoding}]), + ?assertMatch(200, status(Response)), + ?assertHeadersEqual([{<<"Content-Encoding">>, Encoding}, + {<<"connection">>, <<"Keep-Alive">>}, + {<<"content-length">>, Length}], headers(Response)), + ?assertEqual(binary:copy(<<"Hello World!">>, 86), + uncompress(Encoding, body(Response))). + +uncompress(<<"gzip">>, Data) -> zlib:gunzip(Data); +uncompress(<<"deflate">>, Data) -> zlib:uncompress(Data). + +start_slow_client(Port, Url) -> + case gen_tcp:connect("127.0.0.1", Port, [{active, false}, binary]) of + {ok, Socket} -> + gen_tcp:send(Socket, "GET " ++ Url ++ " HTTP/1.1\r\n"), + Socket; + {error, Reason} -> + throw({slow_client_error, Reason}) + end. + +send(_Socket, <<>>, _) -> + ok; +send(Socket, B, ChunkSize) -> + {Part, Rest} = case B of + <> -> {P, R}; + P -> {P, <<>>} + end, + %%?LOG_INFO("~p~n", [Part]), + gen_tcp:send(Socket, Part), + timer:sleep(1), + send(Socket, Rest, ChunkSize). + diff --git a/test/elli_handover_SUITE.erl b/test/elli_handover_SUITE.erl new file mode 100644 index 0000000..8417da6 --- /dev/null +++ b/test/elli_handover_SUITE.erl @@ -0,0 +1,54 @@ +-module(elli_handover_SUITE). +-include_lib("stdlib/include/assert.hrl"). +-include("test/support/elli_test.hrl"). + +-compile([export_all, nowarn_export_all]). + +% +% Configuration. + +all() -> + [ + Fun + || {Fun, 1} <- ?MODULE:module_info(exports), + not lists:member(Fun, [module_info, init_per_suite, end_per_suite, + status, body, headers]) + ]. + +init_per_suite(Config0) -> + {ok, StartedApps} = application:ensure_all_started(hackney), + + Config = [ + {mods, [ + {elli_example_callback_handover, []} + ]} + ], + + {ok, P} = elli:start_link([{callback, elli_middleware}, + {callback_args, Config}, + {port, 3003}]), + unlink(P), + [{pids, [P]}, {started_apps, StartedApps} | Config0]. + +end_per_suite(Config) -> + Pids = proplists:get_value(pids, Config), + [elli:stop(P) || P <- Pids], + lists:foreach(fun (App) -> + application:stop(App) + end, + proplists:get_value(started_apps, Config)). + +% +% Tests. + +hello_world(_Config) -> + Response = hackney:get("http://localhost:3003/hello/world"), + ?assertMatch(200, status(Response)), + ?assertMatch([{<<"Connection">>, <<"close">>}, + {<<"Content-Length">>, <<"12">>}], headers(Response)), + ?assertMatch(<<"Hello World!">>, body(Response)). + +echo(_Config) -> + Response = hackney:get("http://localhost:3003/hello?name=knut"), + ?assertMatch(200, status(Response)), + ?assertMatch(<<"Hello knut">>, body(Response)). diff --git a/test/elli_handover_tests.erl b/test/elli_handover_tests.erl deleted file mode 100644 index 7154e80..0000000 --- a/test/elli_handover_tests.erl +++ /dev/null @@ -1,52 +0,0 @@ --module(elli_handover_tests). --include_lib("eunit/include/eunit.hrl"). --include("elli_test.hrl"). - -elli_test_() -> - {setup, - fun setup/0, fun teardown/1, - [ - ?_test(hello_world()), - ?_test(echo()) - ]}. - - - -setup() -> - application:start(crypto), - application:start(public_key), - application:start(ssl), - {ok, _} = application:ensure_all_started(hackney), - - Config = [ - {mods, [ - {elli_example_callback_handover, []} - ]} - ], - - {ok, P} = elli:start_link([{callback, elli_middleware}, - {callback_args, Config}, - {port, 3003}]), - unlink(P), - [P]. - -teardown(Pids) -> - [elli:stop(P) || P <- Pids]. - - -%% -%% INTEGRATION TESTS -%% Uses hackney to actually call Elli over the network -%% - -hello_world() -> - Response = hackney:get("http://localhost:3003/hello/world"), - ?assertMatch(200, status(Response)), - ?assertMatch([{<<"Connection">>, <<"close">>}, - {<<"Content-Length">>, <<"12">>}], headers(Response)), - ?assertMatch(<<"Hello World!">>, body(Response)). - -echo() -> - Response = hackney:get("http://localhost:3003/hello?name=knut"), - ?assertMatch(200, status(Response)), - ?assertMatch(<<"Hello knut">>, body(Response)). diff --git a/test/elli_http_SUITE.erl b/test/elli_http_SUITE.erl new file mode 100644 index 0000000..794f4e7 --- /dev/null +++ b/test/elli_http_SUITE.erl @@ -0,0 +1,52 @@ +-module(elli_http_SUITE). +-include_lib("stdlib/include/assert.hrl"). + +-compile([export_all, nowarn_export_all]). + +% +% Configuration. + +all() -> + [ + Fun + || {Fun, 1} <- ?MODULE:module_info(exports), + not lists:member(Fun, [module_info, + chunk_loop_wrapper, + status, body, headers]) + ]. + +% +% Tests. + +chunk_loop(_Config) -> + Here = self(), + Pid = spawn_link(chunk_loop_wrapper(Here)), + Pid ! {tcp_closed, some_socket}, + Message = receive_message(), + ?assertMatch({error, client_closed}, Message). + +get_body(_Config) -> + Socket = undefined, + Headers = [{<<"Content-Length">>, <<" 42 ">>}], + Buffer = binary:copy(<<".">>, 42), + Opts = [], + Callback = {no_mod, []}, + ?assertMatch({Buffer, <<>>}, + elli_http:get_body(Socket, Headers, Buffer, Opts, Callback)). + +% +% Private. + +chunk_loop_wrapper(Here) -> + fun () -> + Result = elli_http:chunk_loop({some_type, some_socket}), + Here ! Result, + ok + end. + +receive_message() -> + receive + X -> X + after + 1 -> fail + end. diff --git a/test/elli_http_tests.erl b/test/elli_http_tests.erl deleted file mode 100644 index 1555b25..0000000 --- a/test/elli_http_tests.erl +++ /dev/null @@ -1,27 +0,0 @@ --module(elli_http_tests). --include_lib("eunit/include/eunit.hrl"). - -%% UNIT TESTS - -chunk_loop_test_() -> - fun () -> - Here = self(), - Pid = spawn_link(chunk_loop_wrapper(Here)), - Pid ! {tcp_closed, some_socket}, - Message = receive_message(), - ?assertMatch({error, client_closed}, Message) - end. - -chunk_loop_wrapper(Here) -> - fun () -> - Result = elli_http:chunk_loop({some_type, some_socket}), - Here ! Result, - ok - end. - -receive_message() -> - receive - X -> X - after - 1 -> fail - end. diff --git a/test/elli_middleware_tests.erl b/test/elli_middleware_SUITE.erl similarity index 76% rename from test/elli_middleware_tests.erl rename to test/elli_middleware_SUITE.erl index 8761643..a5bf208 100644 --- a/test/elli_middleware_tests.erl +++ b/test/elli_middleware_SUITE.erl @@ -1,33 +1,66 @@ --module(elli_middleware_tests). --include_lib("eunit/include/eunit.hrl"). --include("elli_test.hrl"). +-module(elli_middleware_SUITE). +-include_lib("stdlib/include/assert.hrl"). +-include("test/support/elli_test.hrl"). -elli_test_() -> - {setup, - fun setup/0, fun teardown/1, - [ - ?_test(hello_world()), - ?_test(short_circuit()), - ?_test(compress()), - ?_test(no_callbacks()) - ]}. +-compile([export_all, nowarn_export_all]). +% +% Configuration. -%% -%% TESTS -%% +all() -> + [ + Fun + || {Fun, 1} <- ?MODULE:module_info(exports), + not lists:member(Fun, [module_info, init_per_suite, end_per_suite, + status, body, headers]) + ]. -short_circuit() -> +init_per_suite(Config0) -> + {ok, StartedApps} = application:ensure_all_started(hackney), + + Config = [ + {mods, [ + {elli_access_log, [{name, elli_syslog}, + {ip, "127.0.0.1"}, + {port, 514}]}, + {elli_example_middleware, []}, + {elli_middleware_compress, []}, + {elli_example_callback, []} + ]} + ], + + {ok, P1} = elli:start_link([{callback, elli_middleware}, + {callback_args, Config}, + {port, 3002}]), + unlink(P1), + {ok, P2} = elli:start_link([{callback, elli_middleware}, + {callback_args, [{mods, []}]}, + {port, 3004}]), + unlink(P2), + [{pids, [P1, P2]}, {started_apps, StartedApps} | Config0]. + +end_per_suite(Config) -> + Pids = proplists:get_value(pids, Config), + [elli:stop(P) || P <- Pids], + lists:foreach(fun (App) -> + application:stop(App) + end, + proplists:get_value(started_apps, Config)). + +% +% Tests. + +short_circuit(_Config) -> URL = "http://localhost:3002/middleware/short-circuit", Response = hackney:get(URL), ?assertMatch(<<"short circuit!">>, body(Response)). -hello_world() -> +hello_world(_Config) -> URL = "http://localhost:3002/hello/world", Response = hackney:get(URL), ?assertMatch(<<"Hello World!">>, body(Response)). -compress() -> +compress(_Config) -> URL = "http://localhost:3002/compressed", Headers = [{<<"Accept-Encoding">>, <<"gzip">>}], Response = hackney:get(URL, Headers), @@ -61,42 +94,7 @@ compress() -> ?assertEqual(iolist_to_binary(lists:duplicate(86, "Hello World!")), body(Response3)). -no_callbacks() -> +no_callbacks(_Config) -> Response = hackney:get("http://localhost:3004/whatever"), ?assertMatch(404, status(Response)), ?assertMatch(<<"Not Found">>, body(Response)). - - -%% -%% HELPERS -%% - -setup() -> - application:start(crypto), - application:start(public_key), - application:start(ssl), - {ok, _} = application:ensure_all_started(hackney), - - Config = [ - {mods, [ - {elli_access_log, [{name, elli_syslog}, - {ip, "127.0.0.1"}, - {port, 514}]}, - {elli_example_middleware, []}, - {elli_middleware_compress, []}, - {elli_example_callback, []} - ]} - ], - - {ok, P1} = elli:start_link([{callback, elli_middleware}, - {callback_args, Config}, - {port, 3002}]), - unlink(P1), - {ok, P2} = elli:start_link([{callback, elli_middleware}, - {callback_args, [{mods, []}]}, - {port, 3004}]), - unlink(P2), - [P1, P2]. - -teardown(Pids) -> - [elli:stop(P) || P <- Pids]. diff --git a/test/elli_ssl_tests.erl b/test/elli_ssl_SUITE.erl similarity index 72% rename from test/elli_ssl_tests.erl rename to test/elli_ssl_SUITE.erl index 7a7d1d5..e7237cc 100644 --- a/test/elli_ssl_tests.erl +++ b/test/elli_ssl_SUITE.erl @@ -1,39 +1,85 @@ --module(elli_ssl_tests). --include_lib("eunit/include/eunit.hrl"). --include("elli_test.hrl"). - --define(README, "README.md"). - -elli_ssl_test_() -> - {setup, - fun setup/0, fun teardown/1, - [{foreach, - fun init_stats/0, fun clear_stats/1, - [ - ?_test(hello_world()), - ?_test(chunked()), - ?_test(sendfile()), - ?_test(acceptor_leak_regression()) - ]} - ]}. +-module(elli_ssl_SUITE). +-include_lib("stdlib/include/assert.hrl"). +-include("test/support/elli_test.hrl"). -get_size_value(Key) -> - [{sizes, Sizes}] = ets:lookup(elli_stat_table, sizes), - proplists:get_value(Key, Sizes). +-define(README, filename:join(code:priv_dir(elli), "README.md")). -get_timing_value(Key) -> - [{timings, Timings}] = ets:lookup(elli_stat_table, timings), - proplists:get_value(Key, Timings). +-compile([export_all, nowarn_export_all]). + +% +% Configuration. + +all() -> + [ + Fun + || {Fun, 1} <- ?MODULE:module_info(exports), + not lists:member(Fun, [module_info, init_per_suite, end_per_suite, + get_size_value, get_timing_value, + status, body, headers]) + ]. + +init_per_suite(Config0) -> + application:start(asn1), + application:start(crypto), + application:start(public_key), + application:start(ssl), + {ok, StartedApps} = application:ensure_all_started(hackney), + + EbinDir = filename:dirname(code:which(?MODULE)), + CertDir = filename:join([EbinDir, "pem"]), + CertFile = filename:join(CertDir, "server_cert.pem"), + KeyFile = filename:join(CertDir, "server_key.pem"), + + Config = [ + {mods, [ + {elli_metrics_middleware, []}, + {elli_middleware_compress, []}, + {elli_example_callback, []} + ]} + ], + + {ok, P} = elli:start_link([ + {port, 3443}, + ssl, + {keyfile, KeyFile}, + {certfile, CertFile}, + {callback, elli_middleware}, + {callback_args, Config} + ]), + unlink(P), + erlang:register(elli_under_test, P), + [{pids, [P]}, {started_apps, StartedApps} | Config0]. + +end_per_suite(Config) -> + Pids = proplists:get_value(pids, Config), + [elli:stop(P) || P <- Pids], + lists:foreach(fun (App) -> + application:stop(App) + end, + proplists:get_value(started_apps, Config)), + application:stop(ssl), + application:stop(public_key), + application:stop(crypto), + application:start(asn1). -%%% Tests +init_per_testcase(_Testcase, Config) -> + ets:new(elli_stat_table, [set, named_table, public]), + Config. -hello_world() -> +end_per_testcase(_Testcase, Config) -> + ets:delete(elli_stat_table), + Config. + +% +% Tests. + +hello_world(_Config) -> Response = hackney:get("https://localhost:3443/hello/world", [], <<>>, [insecure]), ?assertMatch(200, status(Response)), ?assertMatch({ok, 200, _, _}, Response). -chunked() -> +chunked(_Config) -> Expected = <<"chunk10chunk9chunk8chunk7chunk6chunk5chunk4chunk3chunk2chunk1">>, Response = hackney:get("https://localhost:3443/chunked", [], <<>>, [insecure]), @@ -44,7 +90,7 @@ chunked() -> {<<"transfer-encoding">>,<<"chunked">>}], headers(Response)), ?assertMatch(Expected, body(Response)). -sendfile() -> +sendfile(_Config) -> Response = hackney:get("https://localhost:3443/sendfile", [], <<>>, [insecure]), F = ?README, {ok, Expected} = file:read_file(F), @@ -68,7 +114,7 @@ sendfile() -> ?assertNotMatch(undefined, get_timing_value(send_end)), ?assertNotMatch(undefined, get_timing_value(request_end)). -acceptor_leak_regression() -> +acceptor_leak_regression(_Config) -> {ok, Before} = elli:get_acceptors(elli_under_test), Opts = [{verify, verify_peer}, {verify_fun, {fun(_,_) -> {fail, 23} end, []}}, @@ -77,48 +123,13 @@ acceptor_leak_regression() -> {ok, After} = elli:get_acceptors(elli_under_test), ?assertEqual(length(Before), length(After)). -%%% Internal helpers - -setup() -> - application:start(asn1), - application:start(crypto), - application:start(public_key), - application:start(ssl), - {ok, _} = application:ensure_all_started(hackney), - - EbinDir = filename:dirname(code:which(?MODULE)), - CertDir = filename:join([EbinDir, "..", "test"]), - CertFile = filename:join(CertDir, "server_cert.pem"), - KeyFile = filename:join(CertDir, "server_key.pem"), - - Config = [ - {mods, [ - {elli_metrics_middleware, []}, - {elli_middleware_compress, []}, - {elli_example_callback, []} - ]} - ], - - {ok, P} = elli:start_link([ - {port, 3443}, - ssl, - {keyfile, KeyFile}, - {certfile, CertFile}, - {callback, elli_middleware}, - {callback_args, Config} - ]), - unlink(P), - erlang:register(elli_under_test, P), - [P]. +% +% Private. -teardown(Pids) -> - [elli:stop(P) || P <- Pids], - application:stop(ssl), - application:stop(public_key), - application:stop(crypto). - -init_stats() -> - ets:new(elli_stat_table, [set, named_table, public]). +get_size_value(Key) -> + [{sizes, Sizes}] = ets:lookup(elli_stat_table, sizes), + proplists:get_value(Key, Sizes). -clear_stats(_) -> - ets:delete(elli_stat_table). +get_timing_value(Key) -> + [{timings, Timings}] = ets:lookup(elli_stat_table, timings), + proplists:get_value(Key, Timings). diff --git a/test/server_cert.pem b/test/pem/server_cert.pem similarity index 100% rename from test/server_cert.pem rename to test/pem/server_cert.pem diff --git a/test/server_key.pem b/test/pem/server_key.pem similarity index 100% rename from test/server_key.pem rename to test/pem/server_key.pem diff --git a/test/elli_metrics_middleware.erl b/test/support/elli_metrics_middleware.erl similarity index 100% rename from test/elli_metrics_middleware.erl rename to test/support/elli_metrics_middleware.erl diff --git a/test/elli_test.hrl b/test/support/elli_test.hrl similarity index 100% rename from test/elli_test.hrl rename to test/support/elli_test.hrl