Bytesrw adapter for Eio
ocaml
codec
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(* Test reading from a mock flow *)
7let test_reader_basic () =
8 Eio_main.run @@ fun _env ->
9 let test_data = "Hello, World!" in
10 let flow = Eio.Flow.string_source test_data in
11 let reader = Bytesrw_eio.bytes_reader_of_flow flow in
12
13 (* Read first slice *)
14 let slice1 = Bytesrw.Bytes.Reader.read reader in
15 Alcotest.(check bool) "slice is not eod" false (Bytesrw.Bytes.Slice.is_eod slice1);
16
17 let read_data = Bytes.sub_string
18 (Bytesrw.Bytes.Slice.bytes slice1)
19 (Bytesrw.Bytes.Slice.first slice1)
20 (Bytesrw.Bytes.Slice.length slice1) in
21 Alcotest.(check string) "data matches" test_data read_data;
22
23 (* Next read should be eod *)
24 let slice2 = Bytesrw.Bytes.Reader.read reader in
25 Alcotest.(check bool) "second read is eod" true (Bytesrw.Bytes.Slice.is_eod slice2)
26
27(* Test reading with custom slice length *)
28let test_reader_custom_slice_length () =
29 Eio_main.run @@ fun _env ->
30 let test_data = "Hello, World!" in
31 let flow = Eio.Flow.string_source test_data in
32 let slice_length = 5 in
33 let reader = Bytesrw_eio.bytes_reader_of_flow ~slice_length flow in
34
35 (* Read should respect slice_length as maximum *)
36 let slice = Bytesrw.Bytes.Reader.read reader in
37 Alcotest.(check bool) "slice length <= custom size" true
38 (Bytesrw.Bytes.Slice.length slice <= slice_length)
39
40(* Test reading empty flow *)
41let test_reader_empty () =
42 Eio_main.run @@ fun _env ->
43 let flow = Eio.Flow.string_source "" in
44 let reader = Bytesrw_eio.bytes_reader_of_flow flow in
45
46 let slice = Bytesrw.Bytes.Reader.read reader in
47 Alcotest.(check bool) "empty flow returns eod" true (Bytesrw.Bytes.Slice.is_eod slice)
48
49(* Test writing to a mock flow *)
50let test_writer_basic () =
51 Eio_main.run @@ fun _env ->
52 let buf = Buffer.create 100 in
53 let flow = Eio.Flow.buffer_sink buf in
54 let writer = Bytesrw_eio.bytes_writer_of_flow flow in
55
56 let test_data = "Hello, World!" in
57 let bytes = Bytes.of_string test_data in
58 let slice = Bytesrw.Bytes.Slice.make bytes ~first:0 ~length:(Bytes.length bytes) in
59
60 Bytesrw.Bytes.Writer.write writer slice;
61
62 let written = Buffer.contents buf in
63 Alcotest.(check string) "written data matches" test_data written
64
65(* Test writing with custom slice length *)
66let test_writer_custom_slice_length () =
67 Eio_main.run @@ fun _env ->
68 let buf = Buffer.create 100 in
69 let flow = Eio.Flow.buffer_sink buf in
70 let slice_length = 8 in
71 let writer = Bytesrw_eio.bytes_writer_of_flow ~slice_length flow in
72
73 let test_data = "Hello, World!" in
74 let bytes = Bytes.of_string test_data in
75 let slice = Bytesrw.Bytes.Slice.make bytes ~first:0 ~length:(Bytes.length bytes) in
76
77 Bytesrw.Bytes.Writer.write writer slice;
78
79 let written = Buffer.contents buf in
80 Alcotest.(check string) "written data matches regardless of slice_length" test_data written
81
82(* Test writing eod slice (should be no-op) *)
83let test_writer_eod () =
84 Eio_main.run @@ fun _env ->
85 let buf = Buffer.create 100 in
86 let flow = Eio.Flow.buffer_sink buf in
87 let writer = Bytesrw_eio.bytes_writer_of_flow flow in
88
89 Bytesrw.Bytes.Writer.write writer Bytesrw.Bytes.Slice.eod;
90
91 let written = Buffer.contents buf in
92 Alcotest.(check string) "eod writes nothing" "" written
93
94(* Test writing partial slice *)
95let test_writer_partial_slice () =
96 Eio_main.run @@ fun _env ->
97 let buf = Buffer.create 100 in
98 let flow = Eio.Flow.buffer_sink buf in
99 let writer = Bytesrw_eio.bytes_writer_of_flow flow in
100
101 let test_data = "Hello, World!" in
102 let bytes = Bytes.of_string test_data in
103 (* Write only "World" *)
104 let slice = Bytesrw.Bytes.Slice.make bytes ~first:7 ~length:5 in
105
106 Bytesrw.Bytes.Writer.write writer slice;
107
108 let written = Buffer.contents buf in
109 Alcotest.(check string) "partial slice written" "World" written
110
111(* Test multiple reads to ensure data isolation - buffers from previous reads
112 should not be corrupted by subsequent reads *)
113let test_reader_multiple_reads () =
114 Eio_main.run @@ fun _env ->
115 let test_data = "ABCDEFGHIJ" in (* 10 bytes *)
116 let flow = Eio.Flow.string_source test_data in
117 let reader = Bytesrw_eio.bytes_reader_of_flow ~slice_length:5 flow in
118
119 (* Read first 5 bytes *)
120 let slice1 = Bytesrw.Bytes.Reader.read reader in
121 let bytes1 = Bytesrw.Bytes.Slice.bytes slice1 in
122 let data1 = Bytes.sub_string bytes1
123 (Bytesrw.Bytes.Slice.first slice1)
124 (Bytesrw.Bytes.Slice.length slice1) in
125
126 (* Read next 5 bytes *)
127 let slice2 = Bytesrw.Bytes.Reader.read reader in
128 let data2 = Bytes.sub_string
129 (Bytesrw.Bytes.Slice.bytes slice2)
130 (Bytesrw.Bytes.Slice.first slice2)
131 (Bytesrw.Bytes.Slice.length slice2) in
132
133 (* Critical test: verify first read's data is STILL intact after second read
134 This would fail if we were reusing buffers or if Cstruct.to_bytes created a view *)
135 let data1_check = Bytes.sub_string bytes1
136 (Bytesrw.Bytes.Slice.first slice1)
137 (Bytesrw.Bytes.Slice.length slice1) in
138
139 Alcotest.(check string) "first read" "ABCDE" data1;
140 Alcotest.(check string) "second read" "FGHIJ" data2;
141 Alcotest.(check string) "first read still intact after second" "ABCDE" data1_check
142
143(* Test round-trip: write then read *)
144let test_roundtrip () =
145 Eio_main.run @@ fun _env ->
146 let test_data = "Round-trip test data" in
147
148 (* Write to buffer *)
149 let buf = Buffer.create 100 in
150 let write_flow = Eio.Flow.buffer_sink buf in
151 let writer = Bytesrw_eio.bytes_writer_of_flow write_flow in
152
153 let bytes = Bytes.of_string test_data in
154 let slice = Bytesrw.Bytes.Slice.make bytes ~first:0 ~length:(Bytes.length bytes) in
155 Bytesrw.Bytes.Writer.write writer slice;
156
157 (* Read back from buffer *)
158 let read_flow = Eio.Flow.string_source (Buffer.contents buf) in
159 let reader = Bytesrw_eio.bytes_reader_of_flow read_flow in
160
161 let read_slice = Bytesrw.Bytes.Reader.read reader in
162 let read_data = Bytes.sub_string
163 (Bytesrw.Bytes.Slice.bytes read_slice)
164 (Bytesrw.Bytes.Slice.first read_slice)
165 (Bytesrw.Bytes.Slice.length read_slice) in
166
167 Alcotest.(check string) "round-trip data matches" test_data read_data
168
169let () =
170 Alcotest.run "Bytesrw_eio" [
171 "reader", [
172 Alcotest.test_case "basic read" `Quick test_reader_basic;
173 Alcotest.test_case "custom slice length" `Quick test_reader_custom_slice_length;
174 Alcotest.test_case "empty flow" `Quick test_reader_empty;
175 Alcotest.test_case "multiple reads data isolation" `Quick test_reader_multiple_reads;
176 ];
177 "writer", [
178 Alcotest.test_case "basic write" `Quick test_writer_basic;
179 Alcotest.test_case "custom slice length" `Quick test_writer_custom_slice_length;
180 Alcotest.test_case "eod write" `Quick test_writer_eod;
181 Alcotest.test_case "partial slice" `Quick test_writer_partial_slice;
182 ];
183 "integration", [
184 Alcotest.test_case "round-trip" `Quick test_roundtrip;
185 ];
186 ]