forked from
anil.recoil.org/ocaml-requests
A batteries included HTTP/1.1 client in OCaml
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Tests for Timing module - HTTP request timing metrics *)
7
8module Timing = Requests.Timing
9
10(** {1 Empty Timing} *)
11
12let test_empty_total () =
13 let t = Timing.empty in
14 Alcotest.(check bool) "total is 0" true (Float.abs (Timing.total t) < 0.0001)
15
16let test_empty_fields () =
17 let t = Timing.empty in
18 Alcotest.(check bool)
19 "dns_lookup is None" true
20 (Option.is_none (Timing.dns_lookup t));
21 Alcotest.(check bool)
22 "tcp_connect is None" true
23 (Option.is_none (Timing.tcp_connect t));
24 Alcotest.(check bool)
25 "tls_handshake is None" true
26 (Option.is_none (Timing.tls_handshake t));
27 Alcotest.(check bool)
28 "request_sent is None" true
29 (Option.is_none (Timing.request_sent t));
30 Alcotest.(check bool)
31 "time_to_first_byte is None" true
32 (Option.is_none (Timing.time_to_first_byte t));
33 Alcotest.(check bool)
34 "content_transfer is None" true
35 (Option.is_none (Timing.content_transfer t))
36
37(** {1 Make with Values} *)
38
39let test_make_total () =
40 let t = Timing.v ~total:1.5 () in
41 Alcotest.(check bool)
42 "total is 1.5" true
43 (Float.abs (Timing.total t -. 1.5) < 0.0001)
44
45let test_make_with_phases () =
46 let t =
47 Timing.v ~dns_lookup:0.01 ~tcp_connect:0.02 ~tls_handshake:0.05
48 ~request_sent:0.001 ~time_to_first_byte:0.1 ~content_transfer:0.5
49 ~total:0.681 ()
50 in
51 Alcotest.(check bool)
52 "dns_lookup" true
53 (match Timing.dns_lookup t with
54 | Some v -> Float.abs (v -. 0.01) < 0.0001
55 | None -> false);
56 Alcotest.(check bool)
57 "tcp_connect" true
58 (match Timing.tcp_connect t with
59 | Some v -> Float.abs (v -. 0.02) < 0.0001
60 | None -> false);
61 Alcotest.(check bool)
62 "tls_handshake" true
63 (match Timing.tls_handshake t with
64 | Some v -> Float.abs (v -. 0.05) < 0.0001
65 | None -> false);
66 Alcotest.(check bool)
67 "request_sent" true
68 (match Timing.request_sent t with
69 | Some v -> Float.abs (v -. 0.001) < 0.0001
70 | None -> false);
71 Alcotest.(check bool)
72 "time_to_first_byte" true
73 (match Timing.time_to_first_byte t with
74 | Some v -> Float.abs (v -. 0.1) < 0.0001
75 | None -> false);
76 Alcotest.(check bool)
77 "content_transfer" true
78 (match Timing.content_transfer t with
79 | Some v -> Float.abs (v -. 0.5) < 0.0001
80 | None -> false)
81
82(** {1 Computed Metrics} *)
83
84let test_connection_time () =
85 let t =
86 Timing.v ~dns_lookup:0.01 ~tcp_connect:0.02 ~tls_handshake:0.05 ~total:0.5
87 ()
88 in
89 match Timing.connection_time t with
90 | Some ct ->
91 (* connection_time = DNS + TCP + TLS = 0.01 + 0.02 + 0.05 = 0.08 *)
92 Alcotest.(check bool)
93 "connection_time" true
94 (Float.abs (ct -. 0.08) < 0.001)
95 | None -> Alcotest.fail "Expected Some connection_time"
96
97let test_connection_time_no_tls () =
98 let t = Timing.v ~dns_lookup:0.01 ~tcp_connect:0.02 ~total:0.5 () in
99 (* Without TLS, connection_time may or may not be computed *)
100 let _ = Timing.connection_time t in
101 (* Just verify it doesn't crash *)
102 ()
103
104let test_server_time () =
105 let t = Timing.v ~request_sent:0.001 ~time_to_first_byte:0.1 ~total:0.5 () in
106 match Timing.server_time t with
107 | Some st ->
108 (* server_time = TTFB - request_sent = 0.1 - 0.001 = 0.099 *)
109 Alcotest.(check bool) "server_time" true (Float.abs (st -. 0.099) < 0.001)
110 | None -> Alcotest.fail "Expected Some server_time"
111
112let test_server_time_none () =
113 let t = Timing.empty in
114 Alcotest.(check bool)
115 "server_time is None" true
116 (Option.is_none (Timing.server_time t))
117
118(** {1 Timer} *)
119
120let test_timer_basic () =
121 let timer = Timing.start () in
122 Timing.mark_dns timer;
123 Timing.mark_connect timer;
124 Timing.mark_send timer;
125 Timing.mark_ttfb timer;
126 Timing.mark_transfer_end timer;
127 let t = Timing.finish timer in
128 Alcotest.(check bool) "total >= 0" true (Timing.total t >= 0.0)
129
130let test_timer_has_phases () =
131 let timer = Timing.start () in
132 Timing.mark_dns timer;
133 Timing.mark_connect timer;
134 Timing.mark_tls timer;
135 Timing.mark_send timer;
136 Timing.mark_ttfb timer;
137 Timing.mark_transfer_end timer;
138 let t = Timing.finish timer in
139 Alcotest.(check bool)
140 "dns_lookup is Some" true
141 (Option.is_some (Timing.dns_lookup t));
142 Alcotest.(check bool)
143 "tcp_connect is Some" true
144 (Option.is_some (Timing.tcp_connect t));
145 Alcotest.(check bool)
146 "tls_handshake is Some" true
147 (Option.is_some (Timing.tls_handshake t))
148
149(** {1 Test Suite} *)
150
151let suite =
152 ( "timing",
153 [
154 Alcotest.test_case "total is 0" `Quick test_empty_total;
155 Alcotest.test_case "all fields None" `Quick test_empty_fields;
156 Alcotest.test_case "total only" `Quick test_make_total;
157 Alcotest.test_case "with all phases" `Quick test_make_with_phases;
158 Alcotest.test_case "connection time" `Quick test_connection_time;
159 Alcotest.test_case "connection time no TLS" `Quick
160 test_connection_time_no_tls;
161 Alcotest.test_case "server time" `Quick test_server_time;
162 Alcotest.test_case "server time None" `Quick test_server_time_none;
163 Alcotest.test_case "basic timer" `Quick test_timer_basic;
164 Alcotest.test_case "timer has phases" `Quick test_timer_has_phases;
165 ] )