(** Alcotest suite comparing Unix and Eio implementations *) open Alcotest let test_data_dir = "test/data" let test_files = [ "simple_waypoints.gpx"; "detailed_waypoints.gpx"; "simple_route.gpx"; "simple_track.gpx"; "multi_segment_track.gpx"; "comprehensive.gpx"; "minimal.gpx"; "edge_cases.gpx"; ] (** Helper to compare GPX documents *) let compare_gpx_basic gpx1 gpx2 = let open Gpx in Doc.creator gpx1 = Doc.creator gpx2 && List.length (Doc.waypoints gpx1) = List.length (Doc.waypoints gpx2) && List.length (Doc.routes gpx1) = List.length (Doc.routes gpx2) && List.length (Doc.tracks gpx1) = List.length (Doc.tracks gpx2) (** Test Unix implementation can read all test files *) let test_unix_parsing filename () = let path = Filename.concat test_data_dir filename in match Gpx_unix.read path with | Ok gpx -> let validation = Gpx.validate_gpx gpx in check bool "GPX is valid" true validation.is_valid; check bool "Has some content" true ( List.length (Gpx.Doc.waypoints gpx) > 0 || List.length (Gpx.Doc.routes gpx) > 0 || List.length (Gpx.Doc.tracks gpx) > 0 ) | Error err -> failf "Unix parsing failed for %s: %s" filename (Gpx.Error.to_string err) (** Test Eio implementation can read all test files *) let test_eio_parsing filename () = Eio_main.run @@ fun env -> let fs = Eio.Stdenv.fs env in let path = Filename.concat test_data_dir filename in try let gpx = Gpx_eio.read ~fs path in let validation = Gpx.validate_gpx gpx in check bool "GPX is valid" true validation.is_valid; check bool "Has some content" true ( List.length (Gpx.Doc.waypoints gpx) > 0 || List.length (Gpx.Doc.routes gpx) > 0 || List.length (Gpx.Doc.tracks gpx) > 0 ) with | Gpx.Gpx_error err -> failf "Eio parsing failed for %s: %s" filename (Gpx.Error.to_string err) (** Test Unix and Eio implementations produce equivalent results *) let test_unix_eio_equivalence filename () = let path = Filename.concat test_data_dir filename in (* Parse with Unix *) let unix_result = Gpx_unix.read path in (* Parse with Eio *) let eio_result = try Eio_main.run @@ fun env -> let fs = Eio.Stdenv.fs env in Ok (Gpx_eio.read ~fs path) with | Gpx.Gpx_error err -> Error err in match unix_result, eio_result with | Ok gpx_unix, Ok gpx_eio -> check bool "Unix and Eio produce equivalent results" true (compare_gpx_basic gpx_unix gpx_eio); check string "Creators match" (Gpx.Doc.creator gpx_unix) (Gpx.Doc.creator gpx_eio); check int "Waypoint counts match" (List.length (Gpx.Doc.waypoints gpx_unix)) (List.length (Gpx.Doc.waypoints gpx_eio)); check int "Route counts match" (List.length (Gpx.Doc.routes gpx_unix)) (List.length (Gpx.Doc.routes gpx_eio)); check int "Track counts match" (List.length (Gpx.Doc.tracks gpx_unix)) (List.length (Gpx.Doc.tracks gpx_eio)) | Error _, Error _ -> (* Both failed - that's consistent *) check bool "Both Unix and Eio failed consistently" true true | Ok _, Error _ -> failf "Unix succeeded but Eio failed for %s" filename | Error _, Ok _ -> failf "Eio succeeded but Unix failed for %s" filename (** Test write-read round-trip with Unix *) let test_unix_round_trip filename () = let path = Filename.concat test_data_dir filename in match Gpx_unix.read path with | Ok gpx_original -> (* Write to temporary string *) (match Gpx.write_string gpx_original with | Ok xml_string -> (* Parse the written string *) (match Gpx.parse_string xml_string with | Ok gpx_roundtrip -> check bool "Round-trip preserves basic structure" true (compare_gpx_basic gpx_original gpx_roundtrip); check string "Creator preserved" (Gpx.Doc.creator gpx_original) (Gpx.Doc.creator gpx_roundtrip) | Error _ -> failf "Round-trip parse failed for %s" filename) | Error _ -> failf "Round-trip write failed for %s" filename) | Error _ -> failf "Initial read failed for %s" filename (** Test write-read round-trip with Eio *) let test_eio_round_trip filename () = Eio_main.run @@ fun env -> let fs = Eio.Stdenv.fs env in let path = Filename.concat test_data_dir filename in try let gpx_original = Gpx_eio.read ~fs path in (* Write to temporary string via GPX core *) match Gpx.write_string gpx_original with | Ok xml_string -> (* Parse the written string *) (match Gpx.parse_string xml_string with | Ok gpx_roundtrip -> check bool "Round-trip preserves basic structure" true (compare_gpx_basic gpx_original gpx_roundtrip); check string "Creator preserved" gpx_original.creator gpx_roundtrip.creator | Error _ -> failf "Round-trip parse failed for %s" filename) | Error _ -> failf "Round-trip write failed for %s" filename with | Gpx.Gpx_error _ -> failf "Initial read failed for %s" filename (** Test validation works on all files *) let test_validation filename () = let path = Filename.concat test_data_dir filename in match Gpx_unix.read path with | Ok gpx -> let validation = Gpx.validate_gpx gpx in check bool "Validation runs without error" true true; (* All our test files should be valid *) if filename <> "invalid.gpx" then check bool "Test file is valid" true validation.is_valid | Error _ -> (* Invalid.gpx should fail to parse - this is expected *) if filename = "invalid.gpx" then check bool "Invalid file correctly fails to parse" true true else failf "Could not read %s for validation test" filename (** Test error handling with invalid file *) let test_error_handling () = let path = Filename.concat test_data_dir "invalid.gpx" in (* Test Unix error handling *) (match Gpx_unix.read path with | Ok _ -> failf "Unix should have failed to parse invalid.gpx" | Error _ -> check bool "Unix correctly rejects invalid file" true true); (* Test Eio error handling *) (try Eio_main.run @@ fun env -> let fs = Eio.Stdenv.fs env in let _ = Gpx_eio.read ~fs path in failf "Eio should have failed to parse invalid.gpx" with | Gpx.Gpx_error _ -> check bool "Eio correctly rejects invalid file" true true) (** Performance comparison test *) let test_performance_comparison filename () = let path = Filename.concat test_data_dir filename in (* Time Unix parsing *) let start_unix = Sys.time () in let _ = Gpx_unix.read path in let unix_time = Sys.time () -. start_unix in (* Time Eio parsing *) let start_eio = Sys.time () in let _ = Eio_main.run @@ fun env -> let fs = Eio.Stdenv.fs env in try Some (Gpx_eio.read ~fs path) with Gpx.Gpx_error _ -> None in let eio_time = Sys.time () -. start_eio in (* Both should complete reasonably quickly (under 1 second for test files) *) check bool "Unix parsing completes quickly" true (unix_time < 1.0); check bool "Eio parsing completes quickly" true (eio_time < 1.0); Printf.printf "Performance for %s: Unix=%.3fms, Eio=%.3fms\n" filename (unix_time *. 1000.) (eio_time *. 1000.) (** Generate test cases for each file *) let make_unix_tests () = List.map (fun filename -> test_case filename `Quick (test_unix_parsing filename) ) test_files let make_eio_tests () = List.map (fun filename -> test_case filename `Quick (test_eio_parsing filename) ) test_files let make_equivalence_tests () = List.map (fun filename -> test_case filename `Quick (test_unix_eio_equivalence filename) ) test_files let make_unix_round_trip_tests () = List.map (fun filename -> test_case filename `Quick (test_unix_round_trip filename) ) test_files let make_eio_round_trip_tests () = List.map (fun filename -> test_case filename `Quick (test_eio_round_trip filename) ) test_files let make_validation_tests () = List.map (fun filename -> test_case filename `Quick (test_validation filename) ) (test_files @ ["invalid.gpx"]) let make_performance_tests () = List.map (fun filename -> test_case filename `Quick (test_performance_comparison filename) ) test_files (** Main test suite *) let () = run "GPX Corpus Tests" [ "Unix parsing", make_unix_tests (); "Eio parsing", make_eio_tests (); "Unix vs Eio equivalence", make_equivalence_tests (); "Unix round-trip", make_unix_round_trip_tests (); "Eio round-trip", make_eio_round_trip_tests (); "Validation", make_validation_tests (); "Error handling", [ test_case "invalid file handling" `Quick test_error_handling; ]; "Performance", make_performance_tests (); ]