(*--------------------------------------------------------------------------- Copyright (c) 2025 Anil Madhavapeddy . All rights reserved. SPDX-License-Identifier: ISC ---------------------------------------------------------------------------*) (** Tests for Timing module - HTTP request timing metrics *) module Timing = Requests.Timing (** {1 Empty Timing} *) let test_empty_total () = let t = Timing.empty in Alcotest.(check bool) "total is 0" true (Float.abs (Timing.total t) < 0.0001) let test_empty_fields () = let t = Timing.empty in Alcotest.(check bool) "dns_lookup is None" true (Option.is_none (Timing.dns_lookup t)); Alcotest.(check bool) "tcp_connect is None" true (Option.is_none (Timing.tcp_connect t)); Alcotest.(check bool) "tls_handshake is None" true (Option.is_none (Timing.tls_handshake t)); Alcotest.(check bool) "request_sent is None" true (Option.is_none (Timing.request_sent t)); Alcotest.(check bool) "time_to_first_byte is None" true (Option.is_none (Timing.time_to_first_byte t)); Alcotest.(check bool) "content_transfer is None" true (Option.is_none (Timing.content_transfer t)) (** {1 Make with Values} *) let test_make_total () = let t = Timing.v ~total:1.5 () in Alcotest.(check bool) "total is 1.5" true (Float.abs (Timing.total t -. 1.5) < 0.0001) let test_make_with_phases () = let t = Timing.v ~dns_lookup:0.01 ~tcp_connect:0.02 ~tls_handshake:0.05 ~request_sent:0.001 ~time_to_first_byte:0.1 ~content_transfer:0.5 ~total:0.681 () in Alcotest.(check bool) "dns_lookup" true (match Timing.dns_lookup t with | Some v -> Float.abs (v -. 0.01) < 0.0001 | None -> false); Alcotest.(check bool) "tcp_connect" true (match Timing.tcp_connect t with | Some v -> Float.abs (v -. 0.02) < 0.0001 | None -> false); Alcotest.(check bool) "tls_handshake" true (match Timing.tls_handshake t with | Some v -> Float.abs (v -. 0.05) < 0.0001 | None -> false); Alcotest.(check bool) "request_sent" true (match Timing.request_sent t with | Some v -> Float.abs (v -. 0.001) < 0.0001 | None -> false); Alcotest.(check bool) "time_to_first_byte" true (match Timing.time_to_first_byte t with | Some v -> Float.abs (v -. 0.1) < 0.0001 | None -> false); Alcotest.(check bool) "content_transfer" true (match Timing.content_transfer t with | Some v -> Float.abs (v -. 0.5) < 0.0001 | None -> false) (** {1 Computed Metrics} *) let test_connection_time () = let t = Timing.v ~dns_lookup:0.01 ~tcp_connect:0.02 ~tls_handshake:0.05 ~total:0.5 () in match Timing.connection_time t with | Some ct -> (* connection_time = DNS + TCP + TLS = 0.01 + 0.02 + 0.05 = 0.08 *) Alcotest.(check bool) "connection_time" true (Float.abs (ct -. 0.08) < 0.001) | None -> Alcotest.fail "Expected Some connection_time" let test_connection_time_no_tls () = let t = Timing.v ~dns_lookup:0.01 ~tcp_connect:0.02 ~total:0.5 () in (* Without TLS, connection_time may or may not be computed *) let _ = Timing.connection_time t in (* Just verify it doesn't crash *) () let test_server_time () = let t = Timing.v ~request_sent:0.001 ~time_to_first_byte:0.1 ~total:0.5 () in match Timing.server_time t with | Some st -> (* server_time = TTFB - request_sent = 0.1 - 0.001 = 0.099 *) Alcotest.(check bool) "server_time" true (Float.abs (st -. 0.099) < 0.001) | None -> Alcotest.fail "Expected Some server_time" let test_server_time_none () = let t = Timing.empty in Alcotest.(check bool) "server_time is None" true (Option.is_none (Timing.server_time t)) (** {1 Timer} *) let test_timer_basic () = let timer = Timing.start () in Timing.mark_dns timer; Timing.mark_connect timer; Timing.mark_send timer; Timing.mark_ttfb timer; Timing.mark_transfer_end timer; let t = Timing.finish timer in Alcotest.(check bool) "total >= 0" true (Timing.total t >= 0.0) let test_timer_has_phases () = let timer = Timing.start () in Timing.mark_dns timer; Timing.mark_connect timer; Timing.mark_tls timer; Timing.mark_send timer; Timing.mark_ttfb timer; Timing.mark_transfer_end timer; let t = Timing.finish timer in Alcotest.(check bool) "dns_lookup is Some" true (Option.is_some (Timing.dns_lookup t)); Alcotest.(check bool) "tcp_connect is Some" true (Option.is_some (Timing.tcp_connect t)); Alcotest.(check bool) "tls_handshake is Some" true (Option.is_some (Timing.tls_handshake t)) (** {1 Test Suite} *) let suite = ( "timing", [ Alcotest.test_case "total is 0" `Quick test_empty_total; Alcotest.test_case "all fields None" `Quick test_empty_fields; Alcotest.test_case "total only" `Quick test_make_total; Alcotest.test_case "with all phases" `Quick test_make_with_phases; Alcotest.test_case "connection time" `Quick test_connection_time; Alcotest.test_case "connection time no TLS" `Quick test_connection_time_no_tls; Alcotest.test_case "server time" `Quick test_server_time; Alcotest.test_case "server time None" `Quick test_server_time_none; Alcotest.test_case "basic timer" `Quick test_timer_basic; Alcotest.test_case "timer has phases" `Quick test_timer_has_phases; ] )