1 module during.tests.rw;
2 
3 import during;
4 import during.tests.base;
5 
6 import core.stdc.stdio;
7 import core.stdc.stdlib;
8 import core.sys.linux.fcntl;
9 import core.sys.posix.sys.uio : iovec;
10 import core.sys.posix.unistd : close, read, unlink, write;
11 import std.algorithm : copy, equal, map;
12 import std.range : iota;
13 
14 // NOTE that we're using direct linux/posix API to be able to run these tests in betterC too
15 
16 @("readv")
17 unittest
18 {
19     // read it and check if read correctly
20     Uring io;
21     auto res = io.setup(4);
22     assert(res >= 0, "Error initializing IO");
23 
24     // prepare some file
25     auto fname = getTestFileName!"readv_test";
26     ubyte[256] buf;
27     iota(0, 256).map!(a => cast(ubyte)a).copy(buf[]);
28     auto file = openFile(fname, O_CREAT | O_WRONLY);
29     auto wr = write(file, &buf[0], buf.length);
30     assert(wr == buf.length);
31     close(file);
32     scope (exit) unlink(&fname[0]);
33 
34     file = openFile(fname, O_RDONLY);
35     scope (exit) close(file);
36 
37     ubyte[32] readbuf; // small buffer to test reading in chunks
38     iovec v;
39     v.iov_base = cast(void*)&readbuf[0];
40     v.iov_len = readbuf.length;
41     foreach (i; 0..8) // 8 operations must complete to read whole file
42     {
43         io
44             .putWith!(
45                 (ref SubmissionEntry e, int f, int i, iovec* v)
46                     => e.prepReadv(f, *v, i*32))(file, i, &v)
47             .submit(1);
48         assert(io.front.res == 32); // number of bytes read
49         assert(readbuf[] == buf[i*32..(i+1)*32]);
50         io.popFront();
51     }
52 
53     // try to read after the file content too
54     assert(io.empty);
55     io
56         .putWith!(
57             (ref SubmissionEntry e, int f, iovec* v)
58                 => e.prepReadv(f, *v, 256))(file, &v)
59         .submit(1);
60     assert(io.front.res == 0); // ok we've reached the EOF
61 }
62 
63 @("writev")
64 unittest
65 {
66     // prepare uring
67     Uring io;
68     auto res = io.setup(4);
69     assert(res >= 0, "Error initializing IO");
70 
71     // prepare file to write to
72     auto fname = getTestFileName!"writev_test";
73     auto f = openFile(fname, O_CREAT | O_WRONLY);
74     scope (exit) unlink(&fname[0]);
75 
76     // prepare chunk buffer
77     ubyte[32] buffer;
78     iovec v;
79     v.iov_base = cast(void*)&buffer[0];
80     v.iov_len = buffer.length;
81 
82     {
83         scope (exit) close(f);
84 
85         // write some data to file
86         foreach (i; 0..8)
87         {
88             foreach (j; 0..32) buffer[j] = cast(ubyte)(i*32 + j);
89             SubmissionEntry entry;
90             entry.prepWritev(f, v, i*32);
91             entry.user_data = i;
92             io.put(entry).submit(1);
93 
94             assert(io.front.user_data == i);
95             assert(io.front.res == 32);
96             io.popFront();
97         }
98         assert(io.empty);
99     }
100 
101     // now check back file content
102     ubyte[257] readbuf;
103     f = openFile(fname, O_RDONLY);
104     scope (exit) close(f);
105     auto r = read(f, cast(void*)&readbuf[0], 257);
106     assert(r == 256);
107     assert(readbuf[0..256].equal(iota(0, 256)));
108 }
109 
110 @("read/write fixed")
111 unittest
112 {
113     static void readOp(ref SubmissionEntry e, int f, ulong off, ubyte[] buffer, int data)
114     {
115         e.prepReadFixed(f, off, buffer, 0);
116         e.user_data = data;
117     }
118 
119     static void writeOp(ref SubmissionEntry e, int f, ulong off, ubyte[] buffer, int data)
120     {
121         e.prepWriteFixed(f, off, buffer, 0);
122         e.user_data = data;
123     }
124 
125     // prepare uring
126     Uring io;
127     auto res = io.setup(4);
128     assert(res >= 0, "Error initializing IO");
129 
130     // prepare some file content
131     auto fname = getTestFileName!"rw_fixed_test";
132     auto tgtFname = getTestFileName!"rw_fixed_test_copy";
133     ubyte[256] buf;
134     iota(0, 256).map!(a => cast(ubyte)a).copy(buf[]);
135     auto file = openFile(fname, O_CREAT | O_WRONLY);
136     auto wr = write(file, &buf[0], buf.length);
137     assert(wr == buf.length);
138     close(file);
139 
140     scope (exit)
141     {
142         unlink(&fname[0]);
143         unlink(&tgtFname[0]);
144     }
145 
146     // register buffer
147     enum batch_size = 64;
148     ubyte* bp = (cast(ubyte*)malloc(2*batch_size));
149     assert(bp);
150     ubyte[] buffer = bp[0..batch_size*2];
151     auto r = io.registerBuffers(buffer);
152     assert(r == 0);
153 
154     // open copy files
155     auto srcFile = openFile(fname, O_RDONLY);
156     auto tgtFile = openFile(tgtFname, O_CREAT | O_WRONLY);
157 
158     // copy file
159     {
160         scope (exit)
161         {
162             close(srcFile);
163             close(tgtFile);
164         }
165 
166         ulong roff, woff;
167         bool waitWrite, waitRead;
168         uint lastRead;
169         int bidx;
170         io.putWith!readOp(srcFile, roff, buffer[bidx*batch_size..bidx*batch_size+batch_size], 1).submit(1);
171         waitRead = true;
172         while (true)
173         {
174             bool isRead;
175             if (io.front.user_data == 1) // read op
176             {
177                 assert(io.front.res >= 0);
178                 lastRead = io.front.res;
179                 isRead = true;
180                 roff += io.front.res; // move read offset
181                 waitRead = false;
182                 if (io.front.res == 0 && !waitWrite)
183                 {
184                     assert(roff == woff);
185                     break; // we are done
186                 }
187             }
188             else if (io.front.user_data == 2) // write op
189             {
190                 assert(io.front.res > 0);
191                 woff += io.front.res;
192                 waitWrite = false;
193             }
194             else assert(0, "unexpected user_data");
195             io.popFront();
196 
197             if ((!waitWrite && isRead) // we've completed reading and can write current and read next
198                 || !waitRead && !isRead) // we've completed writing and can read next and write current
199             {
200                 if (lastRead == 0)
201                 {
202                     assert(roff == woff);
203                     break; // we are done
204                 }
205 
206                 // start write op (with same buffer as used in read op)
207                 io.putWith!writeOp(tgtFile, woff, buffer[bidx*batch_size..bidx*batch_size+batch_size][0..lastRead], 2);
208                 waitWrite = true;
209 
210                 // switch buffers
211                 bidx = (bidx+1) % 2;
212 
213                 // start next read op
214                 io.putWith!readOp(srcFile, roff, buffer[bidx*batch_size..bidx*batch_size+batch_size], 1);
215                 waitRead = true;
216 
217                 // and submit both
218                 io.submit(2);
219             }
220         }
221     }
222 
223     r = io.unregisterBuffers();
224     assert(r == 0);
225 
226     // and check content of the copy
227     file = openFile(tgtFname, O_RDONLY);
228     scope (exit) close(file);
229     auto rd = read(file, cast(void*)&buf[0], 256);
230     assert(rd == 256);
231     assert(buf[0..256].equal(iota(0, 256)));
232 }