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