···
1
+
(*---------------------------------------------------------------------------
2
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
3
+
SPDX-License-Identifier: ISC
4
+
---------------------------------------------------------------------------*)
6
+
(* Test reading from a mock flow *)
7
+
let 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
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);
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;
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)
27
+
(* Test reading with custom slice length *)
28
+
let 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
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)
40
+
(* Test reading empty flow *)
41
+
let 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
46
+
let slice = Bytesrw.Bytes.Reader.read reader in
47
+
Alcotest.(check bool) "empty flow returns eod" true (Bytesrw.Bytes.Slice.is_eod slice)
49
+
(* Test writing to a mock flow *)
50
+
let 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
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
60
+
Bytesrw.Bytes.Writer.write writer slice;
62
+
let written = Buffer.contents buf in
63
+
Alcotest.(check string) "written data matches" test_data written
65
+
(* Test writing with custom slice length *)
66
+
let 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
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
77
+
Bytesrw.Bytes.Writer.write writer slice;
79
+
let written = Buffer.contents buf in
80
+
Alcotest.(check string) "written data matches regardless of slice_length" test_data written
82
+
(* Test writing eod slice (should be no-op) *)
83
+
let 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
89
+
Bytesrw.Bytes.Writer.write writer Bytesrw.Bytes.Slice.eod;
91
+
let written = Buffer.contents buf in
92
+
Alcotest.(check string) "eod writes nothing" "" written
94
+
(* Test writing partial slice *)
95
+
let 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
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
106
+
Bytesrw.Bytes.Writer.write writer slice;
108
+
let written = Buffer.contents buf in
109
+
Alcotest.(check string) "partial slice written" "World" written
111
+
(* Test multiple reads to ensure data isolation - buffers from previous reads
112
+
should not be corrupted by subsequent reads *)
113
+
let 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
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
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
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
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
143
+
(* Test round-trip: write then read *)
144
+
let test_roundtrip () =
145
+
Eio_main.run @@ fun _env ->
146
+
let test_data = "Round-trip test data" in
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
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;
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
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
167
+
Alcotest.(check string) "round-trip data matches" test_data read_data
170
+
Alcotest.run "Bytesrw_eio" [
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;
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;
184
+
Alcotest.test_case "round-trip" `Quick test_roundtrip;