tracer_SUITE.erl 14 KB

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