tracer_SUITE.erl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. %% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(tracer_SUITE).
  15. -compile(export_all).
  16. -compile(nowarn_export_all).
  17. -import(ct_helper, [config/2]).
  18. -import(ct_helper, [doc/1]).
  19. -import(cowboy_test, [gun_open/1]).
  20. -import(cowboy_test, [gun_down/1]).
  21. %% ct.
  22. suite() ->
  23. [{timetrap, 30000}].
  24. %% We initialize trace patterns here. Appropriate would be in
  25. %% init_per_suite/1, but this works just as well.
  26. all() ->
  27. case code:is_module_native(?MODULE) of
  28. true ->
  29. {skip, "The Cowboy tracer is not compatible with native code."};
  30. false ->
  31. cowboy_tracer_h:set_trace_patterns(),
  32. cowboy_test:common_all()
  33. end.
  34. %% We want tests for each group to execute sequentially
  35. %% because we need to modify the protocol options. Groups
  36. %% can run in parallel however.
  37. groups() ->
  38. Tests = ct_helper:all(?MODULE),
  39. [
  40. {http, [], Tests},
  41. {https, [], Tests},
  42. {h2, [], Tests},
  43. {h2c, [], Tests},
  44. {http_compress, [], Tests},
  45. {https_compress, [], Tests},
  46. {h2_compress, [], Tests},
  47. {h2c_compress, [], Tests}
  48. ].
  49. init_per_group(Name = http, Config) ->
  50. cowboy_test:init_http(Name, init_plain_opts(Config), Config);
  51. init_per_group(Name = https, Config) ->
  52. cowboy_test:init_http(Name, init_plain_opts(Config), Config);
  53. init_per_group(Name = h2, Config) ->
  54. cowboy_test:init_http2(Name, init_plain_opts(Config), Config);
  55. init_per_group(Name = h2c, Config) ->
  56. Config1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config),
  57. lists:keyreplace(protocol, 1, Config1, {protocol, http2});
  58. init_per_group(Name = http_compress, Config) ->
  59. cowboy_test:init_http(Name, init_compress_opts(Config), Config);
  60. init_per_group(Name = https_compress, Config) ->
  61. cowboy_test:init_http(Name, init_compress_opts(Config), Config);
  62. init_per_group(Name = h2_compress, Config) ->
  63. cowboy_test:init_http2(Name, init_compress_opts(Config), Config);
  64. init_per_group(Name = h2c_compress, Config) ->
  65. Config1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config),
  66. lists:keyreplace(protocol, 1, Config1, {protocol, http2}).
  67. end_per_group(Name, _) ->
  68. cowboy:stop_listener(Name).
  69. init_plain_opts(Config) ->
  70. #{
  71. env => #{dispatch => cowboy_router:compile(init_routes(Config))},
  72. stream_handlers => [cowboy_tracer_h, cowboy_stream_h]
  73. }.
  74. init_compress_opts(Config) ->
  75. #{
  76. env => #{dispatch => cowboy_router:compile(init_routes(Config))},
  77. stream_handlers => [cowboy_tracer_h, cowboy_compress_h, cowboy_stream_h]
  78. }.
  79. init_routes(_) -> [
  80. {"localhost", [
  81. {"/", hello_h, []},
  82. {"/longer/hello/path", hello_h, []}
  83. ]}
  84. ].
  85. do_get(Path, Config) ->
  86. %% Perform a GET request.
  87. ConnPid = gun_open(Config),
  88. Ref = gun:get(ConnPid, Path, [
  89. {<<"accept-encoding">>, <<"gzip">>},
  90. {<<"x-test-pid">>, pid_to_list(self())}
  91. ]),
  92. {response, nofin, 200, _Headers} = gun:await(ConnPid, Ref),
  93. {ok, _Body} = gun:await_body(ConnPid, Ref),
  94. gun:close(ConnPid).
  95. %% We only care about cowboy_req:reply/4 calls and init/terminate events.
  96. do_tracer_callback(Pid) ->
  97. fun
  98. (Event, _) when Event =:= init; Event =:= terminate ->
  99. Pid ! Event,
  100. 0;
  101. (Event={trace_ts, _, call, {cowboy_req, reply, _}, _}, State) ->
  102. Pid ! Event,
  103. Pid ! {state, State},
  104. State + 1;
  105. (_, State) ->
  106. State + 1
  107. end.
  108. %% Tests.
  109. init(Config) ->
  110. doc("Ensure the init event is triggered."),
  111. Ref = config(ref, Config),
  112. Opts = ranch:get_protocol_options(Ref),
  113. ranch:set_protocol_options(Ref, Opts#{
  114. tracer_callback => do_tracer_callback(self()),
  115. tracer_match_specs => [fun(_,_,_) -> true end]
  116. }),
  117. do_get("/", Config),
  118. receive
  119. init ->
  120. ok
  121. end.
  122. terminate(Config) ->
  123. doc("Ensure the terminate event is triggered."),
  124. Ref = config(ref, Config),
  125. Opts = ranch:get_protocol_options(Ref),
  126. ranch:set_protocol_options(Ref, Opts#{
  127. tracer_callback => do_tracer_callback(self()),
  128. tracer_match_specs => [fun(_,_,_) -> true end]
  129. }),
  130. do_get("/", Config),
  131. receive
  132. terminate ->
  133. ok
  134. end.
  135. state(Config) ->
  136. doc("Ensure the returned state is used."),
  137. Ref = config(ref, Config),
  138. Opts = ranch:get_protocol_options(Ref),
  139. ranch:set_protocol_options(Ref, Opts#{
  140. tracer_callback => do_tracer_callback(self()),
  141. tracer_match_specs => [fun(_,_,_) -> true end]
  142. }),
  143. do_get("/", Config),
  144. receive
  145. {state, St} ->
  146. true = St > 0,
  147. ok
  148. end.
  149. empty(Config) ->
  150. doc("Empty match specs unconditionally enable tracing."),
  151. Ref = config(ref, Config),
  152. Opts = ranch:get_protocol_options(Ref),
  153. ranch:set_protocol_options(Ref, Opts#{
  154. tracer_callback => do_tracer_callback(self()),
  155. tracer_match_specs => []
  156. }),
  157. do_get("/", Config),
  158. receive
  159. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  160. ok
  161. end.
  162. predicate_true(Config) ->
  163. doc("Predicate function returns true, unconditionally enable tracing."),
  164. Ref = config(ref, Config),
  165. Opts = ranch:get_protocol_options(Ref),
  166. ranch:set_protocol_options(Ref, Opts#{
  167. tracer_callback => do_tracer_callback(self()),
  168. tracer_match_specs => [fun(_,_,_) -> true end]
  169. }),
  170. do_get("/", Config),
  171. receive
  172. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  173. ok
  174. end.
  175. predicate_false(Config) ->
  176. doc("Predicate function returns false, unconditionally disable tracing."),
  177. Ref = config(ref, Config),
  178. Opts = ranch:get_protocol_options(Ref),
  179. ranch:set_protocol_options(Ref, Opts#{
  180. tracer_callback => do_tracer_callback(self()),
  181. tracer_match_specs => [fun(_,_,_) -> false end]
  182. }),
  183. do_get("/", Config),
  184. receive
  185. Msg when element(1, Msg) =:= trace_ts ->
  186. error(Msg)
  187. after 100 ->
  188. ok
  189. end.
  190. method(Config) ->
  191. doc("Method is the same as the request's, enable tracing."),
  192. Ref = config(ref, Config),
  193. Opts = ranch:get_protocol_options(Ref),
  194. ranch:set_protocol_options(Ref, Opts#{
  195. tracer_callback => do_tracer_callback(self()),
  196. tracer_match_specs => [{method, <<"GET">>}]
  197. }),
  198. do_get("/", Config),
  199. receive
  200. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  201. ok
  202. end.
  203. method_no_match(Config) ->
  204. doc("Method is different from the request's, disable tracing."),
  205. Ref = config(ref, Config),
  206. Opts = ranch:get_protocol_options(Ref),
  207. ranch:set_protocol_options(Ref, Opts#{
  208. tracer_callback => do_tracer_callback(self()),
  209. tracer_match_specs => [{method, <<"POST">>}]
  210. }),
  211. do_get("/", Config),
  212. receive
  213. Msg when element(1, Msg) =:= trace_ts ->
  214. error(Msg)
  215. after 100 ->
  216. ok
  217. end.
  218. host(Config) ->
  219. doc("Host is the same as the request's, enable tracing."),
  220. Ref = config(ref, Config),
  221. Opts = ranch:get_protocol_options(Ref),
  222. ranch:set_protocol_options(Ref, Opts#{
  223. tracer_callback => do_tracer_callback(self()),
  224. tracer_match_specs => [{host, <<"localhost">>}]
  225. }),
  226. do_get("/", Config),
  227. receive
  228. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  229. ok
  230. end.
  231. host_no_match(Config) ->
  232. doc("Host is different from the request's, disable tracing."),
  233. Ref = config(ref, Config),
  234. Opts = ranch:get_protocol_options(Ref),
  235. ranch:set_protocol_options(Ref, Opts#{
  236. tracer_callback => do_tracer_callback(self()),
  237. tracer_match_specs => [{host, <<"ninenines.eu">>}]
  238. }),
  239. do_get("/", Config),
  240. receive
  241. Msg when element(1, Msg) =:= trace_ts ->
  242. error(Msg)
  243. after 100 ->
  244. ok
  245. end.
  246. path(Config) ->
  247. doc("Path is the same as the request's, enable tracing."),
  248. Ref = config(ref, Config),
  249. Opts = ranch:get_protocol_options(Ref),
  250. ranch:set_protocol_options(Ref, Opts#{
  251. tracer_callback => do_tracer_callback(self()),
  252. tracer_match_specs => [{path, <<"/longer/hello/path">>}]
  253. }),
  254. do_get("/longer/hello/path", Config),
  255. receive
  256. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  257. ok
  258. end.
  259. path_no_match(Config) ->
  260. doc("Path is different from the request's, disable tracing."),
  261. Ref = config(ref, Config),
  262. Opts = ranch:get_protocol_options(Ref),
  263. ranch:set_protocol_options(Ref, Opts#{
  264. tracer_callback => do_tracer_callback(self()),
  265. tracer_match_specs => [{path, <<"/some/other/path">>}]
  266. }),
  267. do_get("/longer/hello/path", Config),
  268. receive
  269. Msg when element(1, Msg) =:= trace_ts ->
  270. error(Msg)
  271. after 100 ->
  272. ok
  273. end.
  274. path_start(Config) ->
  275. doc("Start of path is the same as request's, enable tracing."),
  276. Ref = config(ref, Config),
  277. Opts = ranch:get_protocol_options(Ref),
  278. ranch:set_protocol_options(Ref, Opts#{
  279. tracer_callback => do_tracer_callback(self()),
  280. tracer_match_specs => [{path_start, <<"/longer/hello">>}]
  281. }),
  282. do_get("/longer/hello/path", Config),
  283. receive
  284. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  285. ok
  286. end.
  287. path_start_no_match(Config) ->
  288. doc("Start of path is different from the request's, disable tracing."),
  289. Ref = config(ref, Config),
  290. Opts = ranch:get_protocol_options(Ref),
  291. ranch:set_protocol_options(Ref, Opts#{
  292. tracer_callback => do_tracer_callback(self()),
  293. tracer_match_specs => [{path_start, <<"/shorter/hello">>}]
  294. }),
  295. do_get("/longer/hello/path", Config),
  296. receive
  297. Msg when element(1, Msg) =:= trace_ts ->
  298. error(Msg)
  299. after 100 ->
  300. ok
  301. end.
  302. header_defined(Config) ->
  303. doc("Header is defined in the request, enable tracing."),
  304. Ref = config(ref, Config),
  305. Opts = ranch:get_protocol_options(Ref),
  306. ranch:set_protocol_options(Ref, Opts#{
  307. tracer_callback => do_tracer_callback(self()),
  308. tracer_match_specs => [{header, <<"accept-encoding">>}]
  309. }),
  310. do_get("/", Config),
  311. receive
  312. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  313. ok
  314. end.
  315. header_defined_no_match(Config) ->
  316. doc("Header is not defined in the request, disable tracing."),
  317. Ref = config(ref, Config),
  318. Opts = ranch:get_protocol_options(Ref),
  319. ranch:set_protocol_options(Ref, Opts#{
  320. tracer_callback => do_tracer_callback(self()),
  321. tracer_match_specs => [{header, <<"accept-language">>}]
  322. }),
  323. do_get("/", Config),
  324. receive
  325. Msg when element(1, Msg) =:= trace_ts ->
  326. error(Msg)
  327. after 100 ->
  328. ok
  329. end.
  330. header_value(Config) ->
  331. doc("Header value is the same as the request's, enable tracing."),
  332. Ref = config(ref, Config),
  333. Opts = ranch:get_protocol_options(Ref),
  334. ranch:set_protocol_options(Ref, Opts#{
  335. tracer_callback => do_tracer_callback(self()),
  336. tracer_match_specs => [{header, <<"accept-encoding">>, <<"gzip">>}]
  337. }),
  338. do_get("/", Config),
  339. receive
  340. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  341. ok
  342. end.
  343. header_value_no_match(Config) ->
  344. doc("Header value is different from the request's, disable tracing."),
  345. Ref = config(ref, Config),
  346. Opts = ranch:get_protocol_options(Ref),
  347. ranch:set_protocol_options(Ref, Opts#{
  348. tracer_callback => do_tracer_callback(self()),
  349. tracer_match_specs => [{header, <<"accept-encoding">>, <<"nope">>}]
  350. }),
  351. do_get("/", Config),
  352. receive
  353. Msg when element(1, Msg) =:= trace_ts ->
  354. error(Msg)
  355. after 100 ->
  356. ok
  357. end.
  358. peer_ip(Config) ->
  359. doc("Peer IP is the same as the request's, enable tracing."),
  360. Ref = config(ref, Config),
  361. Opts = ranch:get_protocol_options(Ref),
  362. ranch:set_protocol_options(Ref, Opts#{
  363. tracer_callback => do_tracer_callback(self()),
  364. tracer_match_specs => [{peer_ip, {127, 0, 0, 1}}]
  365. }),
  366. do_get("/", Config),
  367. receive
  368. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  369. ok
  370. end.
  371. peer_ip_no_match(Config) ->
  372. doc("Peer IP is different from the request's, disable tracing."),
  373. Ref = config(ref, Config),
  374. Opts = ranch:get_protocol_options(Ref),
  375. ranch:set_protocol_options(Ref, Opts#{
  376. tracer_callback => do_tracer_callback(self()),
  377. tracer_match_specs => [{peer_ip, {8, 8, 8, 8}}]
  378. }),
  379. do_get("/", Config),
  380. receive
  381. Msg when element(1, Msg) =:= trace_ts ->
  382. error(Msg)
  383. after 100 ->
  384. ok
  385. end.
  386. missing_callback(Config) ->
  387. doc("Ensure the request is still processed if the callback is not provided."),
  388. Ref = config(ref, Config),
  389. Opts0 = ranch:get_protocol_options(Ref),
  390. Opts = maps:remove(tracer_callback, Opts0),
  391. ranch:set_protocol_options(Ref, Opts#{
  392. tracer_match_specs => [{method, <<"GET">>}]
  393. }),
  394. do_get("/", Config),
  395. receive
  396. Msg when element(1, Msg) =:= trace_ts ->
  397. error(Msg)
  398. after 100 ->
  399. ok
  400. end.
  401. missing_match_specs(Config) ->
  402. doc("Ensure the request is still processed if match specs are not provided."),
  403. Ref = config(ref, Config),
  404. Opts0 = ranch:get_protocol_options(Ref),
  405. Opts = maps:remove(tracer_match_specs, Opts0),
  406. ranch:set_protocol_options(Ref, Opts#{
  407. tracer_callback => do_tracer_callback(self())
  408. }),
  409. do_get("/", Config),
  410. receive
  411. Msg when element(1, Msg) =:= trace_ts ->
  412. error(Msg)
  413. after 100 ->
  414. ok
  415. end.
  416. two_matching_requests(Config) ->
  417. doc("Perform two requests that enable tracing on the same connection."),
  418. Ref = config(ref, Config),
  419. Opts = ranch:get_protocol_options(Ref),
  420. ranch:set_protocol_options(Ref, Opts#{
  421. tracer_callback => do_tracer_callback(self()),
  422. tracer_match_specs => [fun(_,_,_) -> true end]
  423. }),
  424. %% Perform a GET request.
  425. ConnPid = gun_open(Config),
  426. Ref1 = gun:get(ConnPid, "/", []),
  427. {response, nofin, 200, _} = gun:await(ConnPid, Ref1),
  428. {ok, _} = gun:await_body(ConnPid, Ref1),
  429. receive
  430. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  431. ok
  432. end,
  433. %% Perform a second GET request on the same connection.
  434. Ref2 = gun:get(ConnPid, "/", []),
  435. {response, nofin, 200, _} = gun:await(ConnPid, Ref2),
  436. {ok, _} = gun:await_body(ConnPid, Ref2),
  437. receive
  438. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  439. ok
  440. end,
  441. gun:close(ConnPid).