xref: /qemu/tests/unit/io-channel-helpers.c (revision 559607ea173a0003efda7f884bec73b242f923fb)
1 /*
2  * QEMU I/O channel test helpers
3  *
4  * Copyright (c) 2015 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include "io-channel-helpers.h"
22 
23 struct QIOChannelTest {
24     QIOChannel *src;
25     QIOChannel *dst;
26     bool blocking;
27     size_t len;
28     size_t niov;
29     char *input;
30     struct iovec *inputv;
31     char *output;
32     struct iovec *outputv;
33     Error *writeerr;
34     Error *readerr;
35 };
36 
37 
38 static void test_skip_iovec(struct iovec **iov,
39                             size_t *niov,
40                             size_t skip,
41                             struct iovec *old)
42 {
43     size_t offset = 0;
44     size_t i;
45 
46     for (i = 0; i < *niov; i++) {
47         if (skip < (*iov)[i].iov_len) {
48             old->iov_len = (*iov)[i].iov_len;
49             old->iov_base = (*iov)[i].iov_base;
50 
51             (*iov)[i].iov_len -= skip;
52             (*iov)[i].iov_base += skip;
53             break;
54         } else {
55             skip -= (*iov)[i].iov_len;
56 
57             if (i == 0 && old->iov_base) {
58                 (*iov)[i].iov_len = old->iov_len;
59                 (*iov)[i].iov_base = old->iov_base;
60                 old->iov_len = 0;
61                 old->iov_base = NULL;
62             }
63 
64             offset++;
65         }
66     }
67 
68     *iov = *iov + offset;
69     *niov -= offset;
70 }
71 
72 
73 /* This thread sends all data using iovecs */
74 static gpointer test_io_thread_writer(gpointer opaque)
75 {
76     QIOChannelTest *data = opaque;
77     struct iovec *iov = data->inputv;
78     size_t niov = data->niov;
79     struct iovec old = { 0 };
80 
81     qio_channel_set_blocking(data->src, data->blocking, NULL);
82 
83     while (niov) {
84         ssize_t ret;
85         ret = qio_channel_writev(data->src,
86                                  iov,
87                                  niov,
88                                  &data->writeerr);
89         if (ret == QIO_CHANNEL_ERR_BLOCK) {
90             if (data->blocking) {
91                 error_setg(&data->writeerr,
92                            "Unexpected I/O blocking");
93                 break;
94             } else {
95                 qio_channel_wait(data->src,
96                                  G_IO_OUT);
97                 continue;
98             }
99         } else if (ret < 0) {
100             break;
101         } else if (ret == 0) {
102             error_setg(&data->writeerr,
103                        "Unexpected zero length write");
104             break;
105         }
106 
107         test_skip_iovec(&iov, &niov, ret, &old);
108     }
109 
110     return NULL;
111 }
112 
113 
114 /* This thread receives all data using iovecs */
115 static gpointer test_io_thread_reader(gpointer opaque)
116 {
117     QIOChannelTest *data = opaque;
118     struct iovec *iov = data->outputv;
119     size_t niov = data->niov;
120     struct iovec old = { 0 };
121 
122     qio_channel_set_blocking(data->dst, data->blocking, NULL);
123 
124     while (niov) {
125         ssize_t ret;
126 
127         ret = qio_channel_readv(data->dst,
128                                 iov,
129                                 niov,
130                                 &data->readerr);
131 
132         if (ret == QIO_CHANNEL_ERR_BLOCK) {
133             if (data->blocking) {
134                 error_setg(&data->writeerr,
135                            "Unexpected I/O blocking");
136                 break;
137             } else {
138                 qio_channel_wait(data->dst,
139                                  G_IO_IN);
140                 continue;
141             }
142         } else if (ret < 0) {
143             break;
144         } else if (ret == 0) {
145             break;
146         }
147 
148         test_skip_iovec(&iov, &niov, ret, &old);
149     }
150 
151     return NULL;
152 }
153 
154 
155 QIOChannelTest *qio_channel_test_new(void)
156 {
157     QIOChannelTest *data = g_new0(QIOChannelTest, 1);
158     size_t i;
159     size_t offset;
160 
161 
162     /* We'll send 1 MB of data */
163 #define CHUNK_COUNT 250
164 #define CHUNK_LEN 4194
165 
166     data->len = CHUNK_COUNT * CHUNK_LEN;
167     data->input = g_new0(char, data->len);
168     data->output = g_new0(gchar, data->len);
169 
170     /* Fill input with a pattern */
171     for (i = 0; i < data->len; i += CHUNK_LEN) {
172         memset(data->input + i, (i / CHUNK_LEN), CHUNK_LEN);
173     }
174 
175     /* We'll split the data across a bunch of IO vecs */
176     data->niov = CHUNK_COUNT;
177     data->inputv = g_new0(struct iovec, data->niov);
178     data->outputv = g_new0(struct iovec, data->niov);
179 
180     for (i = 0, offset = 0; i < data->niov; i++, offset += CHUNK_LEN) {
181         data->inputv[i].iov_base = data->input + offset;
182         data->outputv[i].iov_base = data->output + offset;
183         data->inputv[i].iov_len = CHUNK_LEN;
184         data->outputv[i].iov_len = CHUNK_LEN;
185     }
186 
187     return data;
188 }
189 
190 void qio_channel_test_run_threads(QIOChannelTest *test,
191                                   bool blocking,
192                                   QIOChannel *src,
193                                   QIOChannel *dst)
194 {
195     GThread *reader, *writer;
196 
197     test->src = src;
198     test->dst = dst;
199     test->blocking = blocking;
200 
201     reader = g_thread_new("reader",
202                           test_io_thread_reader,
203                           test);
204     writer = g_thread_new("writer",
205                           test_io_thread_writer,
206                           test);
207 
208     g_thread_join(reader);
209     g_thread_join(writer);
210 
211     test->dst = test->src = NULL;
212 }
213 
214 
215 void qio_channel_test_run_writer(QIOChannelTest *test,
216                                  QIOChannel *src)
217 {
218     test->src = src;
219     test_io_thread_writer(test);
220     test->src = NULL;
221 }
222 
223 
224 void qio_channel_test_run_reader(QIOChannelTest *test,
225                                  QIOChannel *dst)
226 {
227     test->dst = dst;
228     test_io_thread_reader(test);
229     test->dst = NULL;
230 }
231 
232 
233 void qio_channel_test_validate(QIOChannelTest *test)
234 {
235     g_assert_cmpint(memcmp(test->input,
236                            test->output,
237                            test->len), ==, 0);
238     g_assert(test->readerr == NULL);
239     g_assert(test->writeerr == NULL);
240 
241     g_free(test->inputv);
242     g_free(test->outputv);
243     g_free(test->input);
244     g_free(test->output);
245     g_free(test);
246 }
247