0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00
cowboy/test/tracer_SUITE.erl
Loïc Hoguin ce5ab4b49a
Initialize trace patterns only once
They are global for the node for all future call trace flags,
so it's not necessary to set them repeatedly with every request.

Doing it once at startup also ensures we can't have race
conditions when the user wants to change which trace patterns
should be used (because requests are concurrent and patterns
end up overwriting themselves repeatedly), and makes this
changing of trace patterns much more straightforward: the
user can just define the ones they want. The default function
traces everything.

In addition I have also added the tracer_flags option to make
the trace flags configurable, excluding the tracer pid.
2017-11-17 13:23:38 +01:00

497 lines
14 KiB
Erlang

%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
%%
%% 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(tracer_SUITE).
-compile(export_all).
-import(ct_helper, [config/2]).
-import(ct_helper, [doc/1]).
-import(cowboy_test, [gun_open/1]).
-import(cowboy_test, [gun_down/1]).
%% ct.
%% We initialize trace patterns here. Appropriate would be in
%% init_per_suite/1, but this works just as well.
all() ->
cowboy_tracer_h:set_trace_patterns(),
cowboy_test:common_all().
%% We want tests for each group to execute sequentially
%% because we need to modify the protocol options. Groups
%% can run in parallel however.
groups() ->
Tests = ct_helper:all(?MODULE),
[
{http, [], Tests},
{https, [], Tests},
{h2, [], Tests},
{h2c, [], Tests},
{http_compress, [], Tests},
{https_compress, [], Tests},
{h2_compress, [], Tests},
{h2c_compress, [], Tests}
].
init_per_group(Name = http, Config) ->
cowboy_test:init_http(Name, init_plain_opts(Config), Config);
init_per_group(Name = https, Config) ->
cowboy_test:init_http(Name, init_plain_opts(Config), Config);
init_per_group(Name = h2, Config) ->
cowboy_test:init_http2(Name, init_plain_opts(Config), Config);
init_per_group(Name = h2c, Config) ->
Config1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config),
lists:keyreplace(protocol, 1, Config1, {protocol, http2});
init_per_group(Name = http_compress, Config) ->
cowboy_test:init_http(Name, init_compress_opts(Config), Config);
init_per_group(Name = https_compress, Config) ->
cowboy_test:init_http(Name, init_compress_opts(Config), Config);
init_per_group(Name = h2_compress, Config) ->
cowboy_test:init_http2(Name, init_compress_opts(Config), Config);
init_per_group(Name = h2c_compress, Config) ->
Config1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config),
lists:keyreplace(protocol, 1, Config1, {protocol, http2}).
end_per_group(Name, _) ->
cowboy:stop_listener(Name).
init_plain_opts(Config) ->
#{
env => #{dispatch => cowboy_router:compile(init_routes(Config))},
stream_handlers => [cowboy_tracer_h, cowboy_stream_h]
}.
init_compress_opts(Config) ->
#{
env => #{dispatch => cowboy_router:compile(init_routes(Config))},
stream_handlers => [cowboy_tracer_h, cowboy_compress_h, cowboy_stream_h]
}.
init_routes(_) -> [
{"localhost", [
{"/", hello_h, []},
{"/longer/hello/path", hello_h, []}
]}
].
do_get(Path, Config) ->
%% Perform a GET request.
ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, Path, [
{<<"accept-encoding">>, <<"gzip">>},
{<<"x-test-pid">>, pid_to_list(self())}
]),
{response, nofin, 200, _Headers} = gun:await(ConnPid, Ref),
{ok, _Body} = gun:await_body(ConnPid, Ref),
gun:close(ConnPid).
%% We only care about cowboy_req:reply/4 calls and init/terminate events.
do_tracer_callback(Pid) ->
fun
(Event, _) when Event =:= init; Event =:= terminate ->
Pid ! Event,
0;
(Event={trace_ts, _, call, {cowboy_req, reply, _}, _}, State) ->
Pid ! Event,
Pid ! {state, State},
State + 1;
(_, State) ->
State + 1
end.
%% Tests.
init(Config) ->
doc("Ensure the init event is triggered."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [fun(_,_,_) -> true end]
}),
do_get("/", Config),
receive
init ->
ok
after 100 ->
error(timeout)
end.
terminate(Config) ->
doc("Ensure the terminate event is triggered."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [fun(_,_,_) -> true end]
}),
do_get("/", Config),
receive
terminate ->
ok
after 100 ->
error(timeout)
end.
state(Config) ->
doc("Ensure the returned state is used."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [fun(_,_,_) -> true end]
}),
do_get("/", Config),
receive
{state, St} ->
true = St > 0,
ok
after 100 ->
error(timeout)
end.
empty(Config) ->
doc("Empty match specs unconditionally enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => []
}),
do_get("/", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
predicate_true(Config) ->
doc("Predicate function returns true, unconditionally enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [fun(_,_,_) -> true end]
}),
do_get("/", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
predicate_false(Config) ->
doc("Predicate function returns false, unconditionally disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [fun(_,_,_) -> false end]
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
method(Config) ->
doc("Method is the same as the request's, enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{method, <<"GET">>}]
}),
do_get("/", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
method_no_match(Config) ->
doc("Method is different from the request's, disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{method, <<"POST">>}]
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
host(Config) ->
doc("Host is the same as the request's, enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{host, <<"localhost">>}]
}),
do_get("/", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
host_no_match(Config) ->
doc("Host is different from the request's, disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{host, <<"ninenines.eu">>}]
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
path(Config) ->
doc("Path is the same as the request's, enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{path, <<"/longer/hello/path">>}]
}),
do_get("/longer/hello/path", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
path_no_match(Config) ->
doc("Path is different from the request's, disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{path, <<"/some/other/path">>}]
}),
do_get("/longer/hello/path", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
path_start(Config) ->
doc("Start of path is the same as request's, enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{path_start, <<"/longer/hello">>}]
}),
do_get("/longer/hello/path", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
path_start_no_match(Config) ->
doc("Start of path is different from the request's, disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{path_start, <<"/shorter/hello">>}]
}),
do_get("/longer/hello/path", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
header_defined(Config) ->
doc("Header is defined in the request, enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{header, <<"accept-encoding">>}]
}),
do_get("/", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
header_defined_no_match(Config) ->
doc("Header is not defined in the request, disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{header, <<"accept-language">>}]
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
header_value(Config) ->
doc("Header value is the same as the request's, enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{header, <<"accept-encoding">>, <<"gzip">>}]
}),
do_get("/", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
header_value_no_match(Config) ->
doc("Header value is different from the request's, disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{header, <<"accept-encoding">>, <<"nope">>}]
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
peer_ip(Config) ->
doc("Peer IP is the same as the request's, enable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{peer_ip, {127, 0, 0, 1}}]
}),
do_get("/", Config),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end.
peer_ip_no_match(Config) ->
doc("Peer IP is different from the request's, disable tracing."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [{peer_ip, {8, 8, 8, 8}}]
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
missing_callback(Config) ->
doc("Ensure the request is still processed if the callback is not provided."),
Ref = config(ref, Config),
Opts0 = ranch:get_protocol_options(Ref),
Opts = maps:remove(tracer_callback, Opts0),
ranch:set_protocol_options(Ref, Opts#{
tracer_match_specs => [{method, <<"GET">>}]
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
missing_match_specs(Config) ->
doc("Ensure the request is still processed if match specs are not provided."),
Ref = config(ref, Config),
Opts0 = ranch:get_protocol_options(Ref),
Opts = maps:remove(tracer_match_specs, Opts0),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self())
}),
do_get("/", Config),
receive
Msg when element(1, Msg) =:= trace_ts ->
error(Msg)
after 100 ->
ok
end.
two_matching_requests(Config) ->
doc("Perform two requests that enable tracing on the same connection."),
Ref = config(ref, Config),
Opts = ranch:get_protocol_options(Ref),
ranch:set_protocol_options(Ref, Opts#{
tracer_callback => do_tracer_callback(self()),
tracer_match_specs => [fun(_,_,_) -> true end]
}),
%% Perform a GET request.
ConnPid = gun_open(Config),
Ref1 = gun:get(ConnPid, "/", []),
{response, nofin, 200, _} = gun:await(ConnPid, Ref1),
{ok, _} = gun:await_body(ConnPid, Ref1),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end,
%% Perform a second GET request on the same connection.
Ref2 = gun:get(ConnPid, "/", []),
{response, nofin, 200, _} = gun:await(ConnPid, Ref2),
{ok, _} = gun:await_body(ConnPid, Ref2),
receive
{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
ok
after 100 ->
error(timeout)
end,
gun:close(ConnPid).