A batteries included HTTP/1.1 client in OCaml
at main 165 lines 5.4 kB view raw
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 ] )