1(** Alcotest suite comparing Unix and Eio implementations *)
2
3open Alcotest
4
5let ( / ) = Eio.Path. ( / )
6
7let test_data_dir = "test/data"
8
9let test_files = [
10 "simple_waypoints.gpx";
11 "detailed_waypoints.gpx";
12 "simple_route.gpx";
13 "simple_track.gpx";
14 "multi_segment_track.gpx";
15 "comprehensive.gpx";
16 "minimal.gpx";
17 "edge_cases.gpx";
18]
19
20(** Helper to compare GPX documents *)
21let compare_gpx_basic gpx1 gpx2 =
22 let open Gpx in
23 Doc.creator gpx1 = Doc.creator gpx2 &&
24 List.length (Doc.waypoints gpx1) = List.length (Doc.waypoints gpx2) &&
25 List.length (Doc.routes gpx1) = List.length (Doc.routes gpx2) &&
26 List.length (Doc.tracks gpx1) = List.length (Doc.tracks gpx2)
27
28(** Test Unix implementation can read all test files *)
29let test_unix_parsing filename () =
30 let path = Filename.concat test_data_dir filename in
31 match Gpx_unix.read path with
32 | Ok gpx ->
33 let validation = Gpx.validate_gpx gpx in
34 check bool "GPX is valid" true validation.is_valid;
35 check bool "Has some content" true (
36 List.length (Gpx.Doc.waypoints gpx) > 0 ||
37 List.length (Gpx.Doc.routes gpx) > 0 ||
38 List.length (Gpx.Doc.tracks gpx) > 0
39 )
40 | Error err ->
41 failf "Unix parsing failed for %s: %s" filename (Gpx.Error.to_string err)
42
43(** Test Eio implementation can read all test files *)
44let test_eio_parsing filename () =
45 Eio_main.run @@ fun env ->
46 let fs = Eio.Stdenv.fs env in
47 let path = Filename.concat test_data_dir filename in
48 try
49 let gpx = Gpx_eio.read (fs / path) in
50 let validation = Gpx.validate_gpx gpx in
51 check bool "GPX is valid" true validation.is_valid;
52 check bool "Has some content" true (
53 List.length (Gpx.Doc.waypoints gpx) > 0 ||
54 List.length (Gpx.Doc.routes gpx) > 0 ||
55 List.length (Gpx.Doc.tracks gpx) > 0
56 )
57 with
58 | Gpx.Gpx_error err ->
59 failf "Eio parsing failed for %s: %s" filename (Gpx.Error.to_string err)
60
61(** Test Unix and Eio implementations produce equivalent results *)
62let test_unix_eio_equivalence filename () =
63 let path = Filename.concat test_data_dir filename in
64
65 (* Parse with Unix *)
66 let unix_result = Gpx_unix.read path in
67
68 (* Parse with Eio *)
69 let eio_result =
70 try
71 Eio_main.run @@ fun env ->
72 let fs = Eio.Stdenv.fs env in
73 Ok (Gpx_eio.read (fs / path))
74 with
75 | Gpx.Gpx_error err -> Error err
76 in
77
78 match unix_result, eio_result with
79 | Ok gpx_unix, Ok gpx_eio ->
80 check bool "Unix and Eio produce equivalent results" true
81 (compare_gpx_basic gpx_unix gpx_eio);
82 check string "Creators match" (Gpx.Doc.creator gpx_unix) (Gpx.Doc.creator gpx_eio);
83 check int "Waypoint counts match"
84 (List.length (Gpx.Doc.waypoints gpx_unix)) (List.length (Gpx.Doc.waypoints gpx_eio));
85 check int "Route counts match"
86 (List.length (Gpx.Doc.routes gpx_unix)) (List.length (Gpx.Doc.routes gpx_eio));
87 check int "Track counts match"
88 (List.length (Gpx.Doc.tracks gpx_unix)) (List.length (Gpx.Doc.tracks gpx_eio))
89 | Error _, Error _ ->
90 (* Both failed - that's consistent *)
91 check bool "Both Unix and Eio failed consistently" true true
92 | Ok _, Error _ ->
93 failf "Unix succeeded but Eio failed for %s" filename
94 | Error _, Ok _ ->
95 failf "Eio succeeded but Unix failed for %s" filename
96
97(** Test write-read round-trip with Unix *)
98let test_unix_round_trip filename () =
99 let path = Filename.concat test_data_dir filename in
100 match Gpx_unix.read path with
101 | Ok gpx_original ->
102 (* Write to temporary string *)
103 (match Gpx.write_string gpx_original with
104 | Ok xml_string ->
105 (* Parse the written string *)
106 (match Gpx.parse_string xml_string with
107 | Ok gpx_roundtrip ->
108 check bool "Round-trip preserves basic structure" true
109 (compare_gpx_basic gpx_original gpx_roundtrip);
110 check string "Creator preserved"
111 (Gpx.Doc.creator gpx_original) (Gpx.Doc.creator gpx_roundtrip)
112 | Error _ ->
113 failf "Round-trip parse failed for %s" filename)
114 | Error _ ->
115 failf "Round-trip write failed for %s" filename)
116 | Error _ ->
117 failf "Initial read failed for %s" filename
118
119(** Test write-read round-trip with Eio *)
120let test_eio_round_trip filename () =
121 Eio_main.run @@ fun env ->
122 let fs = Eio.Stdenv.fs env in
123 let path = Filename.concat test_data_dir filename in
124 try
125 let gpx_original = Gpx_eio.read (fs / path) in
126 (* Write to temporary string via GPX core *)
127 match Gpx.write_string gpx_original with
128 | Ok xml_string ->
129 (* Parse the written string *)
130 (match Gpx.parse_string xml_string with
131 | Ok gpx_roundtrip ->
132 check bool "Round-trip preserves basic structure" true
133 (compare_gpx_basic gpx_original gpx_roundtrip);
134 check string "Creator preserved"
135 gpx_original.creator gpx_roundtrip.creator
136 | Error _ ->
137 failf "Round-trip parse failed for %s" filename)
138 | Error _ ->
139 failf "Round-trip write failed for %s" filename
140 with
141 | Gpx.Gpx_error _ ->
142 failf "Initial read failed for %s" filename
143
144(** Test validation works on all files *)
145let test_validation filename () =
146 let path = Filename.concat test_data_dir filename in
147 match Gpx_unix.read path with
148 | Ok gpx ->
149 let validation = Gpx.validate_gpx gpx in
150 check bool "Validation runs without error" true true;
151 (* All our test files should be valid *)
152 if filename <> "invalid.gpx" then
153 check bool "Test file is valid" true validation.is_valid
154 | Error _ ->
155 (* Invalid.gpx should fail to parse - this is expected *)
156 if filename = "invalid.gpx" then
157 check bool "Invalid file correctly fails to parse" true true
158 else
159 failf "Could not read %s for validation test" filename
160
161(** Test error handling with invalid file *)
162let test_error_handling () =
163 let path = Filename.concat test_data_dir "invalid.gpx" in
164
165 (* Test Unix error handling *)
166 (match Gpx_unix.read path with
167 | Ok _ ->
168 failf "Unix should have failed to parse invalid.gpx"
169 | Error _ ->
170 check bool "Unix correctly rejects invalid file" true true);
171
172 (* Test Eio error handling *)
173 (try
174 Eio_main.run @@ fun env ->
175 let fs = Eio.Stdenv.fs env in
176 let _ = Gpx_eio.read (fs / path) in
177 failf "Eio should have failed to parse invalid.gpx"
178 with
179 | Gpx.Gpx_error _ ->
180 check bool "Eio correctly rejects invalid file" true true)
181
182(** Performance comparison test *)
183let test_performance_comparison filename () =
184 let path = Filename.concat test_data_dir filename in
185
186 (* Time Unix parsing *)
187 let start_unix = Sys.time () in
188 let _ = Gpx_unix.read path in
189 let unix_time = Sys.time () -. start_unix in
190
191 (* Time Eio parsing *)
192 let start_eio = Sys.time () in
193 let _ = Eio_main.run @@ fun env ->
194 let fs = Eio.Stdenv.fs env in
195 try Some (Gpx_eio.read (fs / path))
196 with Gpx.Gpx_error _ -> None
197 in
198 let eio_time = Sys.time () -. start_eio in
199
200 (* Both should complete reasonably quickly (under 1 second for test files) *)
201 check bool "Unix parsing completes quickly" true (unix_time < 1.0);
202 check bool "Eio parsing completes quickly" true (eio_time < 1.0);
203
204 Printf.printf "Performance for %s: Unix=%.3fms, Eio=%.3fms\n"
205 filename (unix_time *. 1000.) (eio_time *. 1000.)
206
207(** Generate test cases for each file *)
208let make_unix_tests () =
209 List.map (fun filename ->
210 test_case filename `Quick (test_unix_parsing filename)
211 ) test_files
212
213let make_eio_tests () =
214 List.map (fun filename ->
215 test_case filename `Quick (test_eio_parsing filename)
216 ) test_files
217
218let make_equivalence_tests () =
219 List.map (fun filename ->
220 test_case filename `Quick (test_unix_eio_equivalence filename)
221 ) test_files
222
223let make_unix_round_trip_tests () =
224 List.map (fun filename ->
225 test_case filename `Quick (test_unix_round_trip filename)
226 ) test_files
227
228let make_eio_round_trip_tests () =
229 List.map (fun filename ->
230 test_case filename `Quick (test_eio_round_trip filename)
231 ) test_files
232
233let make_validation_tests () =
234 List.map (fun filename ->
235 test_case filename `Quick (test_validation filename)
236 ) (test_files @ ["invalid.gpx"])
237
238let make_performance_tests () =
239 List.map (fun filename ->
240 test_case filename `Quick (test_performance_comparison filename)
241 ) test_files
242
243(** Main test suite *)
244let () =
245 run "GPX Corpus Tests" [
246 "Unix parsing", make_unix_tests ();
247 "Eio parsing", make_eio_tests ();
248 "Unix vs Eio equivalence", make_equivalence_tests ();
249 "Unix round-trip", make_unix_round_trip_tests ();
250 "Eio round-trip", make_eio_round_trip_tests ();
251 "Validation", make_validation_tests ();
252 "Error handling", [
253 test_case "invalid file handling" `Quick test_error_handling;
254 ];
255 "Performance", make_performance_tests ();
256 ]