xref: /curl/tests/unit/unit2601.c (revision 2400a6c6)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 #include "curlcheck.h"
25 
26 #include "urldata.h"
27 #include "bufq.h"
28 #include "curl_trc.h"
29 
unit_setup(void)30 static CURLcode unit_setup(void)
31 {
32   CURLcode res = CURLE_OK;
33   return res;
34 }
35 
unit_stop(void)36 static void unit_stop(void)
37 {
38 }
39 
tail_err(struct bufq * q)40 static const char *tail_err(struct bufq *q)
41 {
42   struct buf_chunk *chunk;
43 
44   if(!q->tail) {
45     return q->head ? "tail is NULL, but head is not" : NULL;
46   }
47 
48   chunk = q->head;
49   while(chunk) {
50     if(chunk == q->tail) {
51       if(chunk->next) {
52         return "tail points to queue, but not at the end";
53       }
54       return NULL;
55     }
56     chunk = chunk->next;
57   }
58   return "tail not part of queue";
59 }
60 
dump_bufq(struct bufq * q,const char * msg)61 static void dump_bufq(struct bufq *q, const char *msg)
62 {
63   struct buf_chunk *chunk;
64   const char *terr;
65   size_t n;
66 
67   fprintf(stderr, "bufq[chunk_size=%zu, max_chunks=%zu] %s\n",
68           q->chunk_size, q->max_chunks, msg);
69   fprintf(stderr, "- queue[\n");
70   chunk = q->head;
71   while(chunk) {
72     fprintf(stderr, "    chunk[len=%zu, roff=%zu, woff=%zu]\n",
73             chunk->dlen, chunk->r_offset, chunk->w_offset);
74     chunk = chunk->next;
75   }
76   fprintf(stderr, "  ]\n");
77   terr = tail_err(q);
78   fprintf(stderr, "- tail: %s\n", terr ? terr : "ok");
79   n = 0;
80   chunk = q->spare;
81   while(chunk) {
82     ++n;
83     chunk = chunk->next;
84   }
85   fprintf(stderr, "- chunks: %zu\n", q->chunk_count);
86   fprintf(stderr, "- spares: %zu\n", n);
87 }
88 
89 static unsigned char test_data[32*1024];
90 
check_bufq(size_t pool_spares,size_t chunk_size,size_t max_chunks,size_t wsize,size_t rsize,int opts)91 static void check_bufq(size_t pool_spares,
92                        size_t chunk_size, size_t max_chunks,
93                        size_t wsize, size_t rsize, int opts)
94 {
95   struct bufq q;
96   struct bufc_pool pool;
97   size_t max_len = chunk_size * max_chunks;
98   CURLcode result;
99   ssize_t n, i;
100   size_t nwritten, nread;
101 
102   if(pool_spares > 0) {
103     Curl_bufcp_init(&pool, chunk_size, pool_spares);
104     Curl_bufq_initp(&q, &pool, max_chunks, opts);
105   }
106   else {
107     Curl_bufq_init2(&q, chunk_size, max_chunks, opts);
108   }
109 
110   fail_unless(q.chunk_size == chunk_size, "chunk_size init wrong");
111   fail_unless(q.max_chunks == max_chunks, "max_chunks init wrong");
112   fail_unless(q.head == NULL, "init: head not NULL");
113   fail_unless(q.tail == NULL, "init: tail not NULL");
114   fail_unless(q.spare == NULL, "init: spare not NULL");
115   fail_unless(Curl_bufq_len(&q) == 0, "init: bufq length != 0");
116 
117   n = Curl_bufq_write(&q, test_data, wsize, &result);
118   fail_unless(n >= 0, "write: negative size returned");
119   fail_unless((size_t)n <= wsize, "write: wrong size returned");
120   fail_unless(result == CURLE_OK, "write: wrong result returned");
121 
122   /* write empty bufq full */
123   nwritten = 0;
124   Curl_bufq_reset(&q);
125   while(!Curl_bufq_is_full(&q)) {
126     n = Curl_bufq_write(&q, test_data, wsize, &result);
127     if(n >= 0) {
128       nwritten += (size_t)n;
129     }
130     else if(result != CURLE_AGAIN) {
131       fail_unless(result == CURLE_AGAIN, "write-loop: unexpected result");
132       break;
133     }
134   }
135   if(nwritten != max_len) {
136     fprintf(stderr, "%zu bytes written, but max_len=%zu\n",
137             nwritten, max_len);
138     dump_bufq(&q, "after writing full");
139     fail_if(TRUE, "write: bufq full but nwritten wrong");
140   }
141 
142   /* read full bufq empty */
143   nread = 0;
144   while(!Curl_bufq_is_empty(&q)) {
145     n = Curl_bufq_read(&q, test_data, rsize, &result);
146     if(n >= 0) {
147       nread += (size_t)n;
148     }
149     else if(result != CURLE_AGAIN) {
150       fail_unless(result == CURLE_AGAIN, "read-loop: unexpected result");
151       break;
152     }
153   }
154   if(nread != max_len) {
155     fprintf(stderr, "%zu bytes read, but max_len=%zu\n",
156             nwritten, max_len);
157     dump_bufq(&q, "after reading empty");
158     fail_if(TRUE, "read: bufq empty but nread wrong");
159   }
160   if(q.tail) {
161     dump_bufq(&q, "after reading empty");
162     fail_if(TRUE, "read empty, but tail is not NULL");
163   }
164 
165   for(i = 0; i < 1000; ++i) {
166     n = Curl_bufq_write(&q, test_data, wsize, &result);
167     if(n < 0 && result != CURLE_AGAIN) {
168       fail_unless(result == CURLE_AGAIN, "rw-loop: unexpected write result");
169       break;
170     }
171     n = Curl_bufq_read(&q, test_data, rsize, &result);
172     if(n < 0 && result != CURLE_AGAIN) {
173       fail_unless(result == CURLE_AGAIN, "rw-loop: unexpected read result");
174       break;
175     }
176   }
177 
178   /* Test SOFT_LIMIT option */
179   Curl_bufq_free(&q);
180   Curl_bufq_init2(&q, chunk_size, max_chunks, (opts|BUFQ_OPT_SOFT_LIMIT));
181   nwritten = 0;
182   while(!Curl_bufq_is_full(&q)) {
183     n = Curl_bufq_write(&q, test_data, wsize, &result);
184     if(n < 0 || (size_t)n != wsize) {
185       fail_unless(n > 0 && (size_t)n == wsize, "write should be complete");
186       break;
187     }
188     nwritten += (size_t)n;
189   }
190   if(nwritten < max_len) {
191     fprintf(stderr, "%zu bytes written, but max_len=%zu\n",
192             nwritten, max_len);
193     dump_bufq(&q, "after writing full");
194     fail_if(TRUE, "write: bufq full but nwritten wrong");
195   }
196   /* do one more write on a full bufq, should work */
197   n = Curl_bufq_write(&q, test_data, wsize, &result);
198   fail_unless(n > 0 && (size_t)n == wsize, "write should be complete");
199   nwritten += (size_t)n;
200   /* see that we get all out again */
201   nread = 0;
202   while(!Curl_bufq_is_empty(&q)) {
203     n = Curl_bufq_read(&q, test_data, rsize, &result);
204     if(n <= 0) {
205       fail_unless(n > 0, "read-loop: unexpected fail");
206       break;
207     }
208     nread += (size_t)n;
209   }
210   fail_unless(nread == nwritten, "did not get the same out as put in");
211 
212   /* CHECK bufq_unwrite: write a string repeatedly into the second chunk.
213    * bufq_unwrite() 1 byte. Read strings again and check for content.
214    * We had a bug that unwrite used the head chunk instead of tail, which
215    * did corrupt the read values. */
216   if(TRUE) {
217     const unsigned char buf[] = "0123456789--";
218     size_t roffset;
219     Curl_bufq_reset(&q);
220     while(Curl_bufq_len(&q) < chunk_size) {
221       n = Curl_bufq_write(&q, buf, sizeof(buf), &result);
222       fail_unless(n > 0 && (size_t)n == sizeof(buf), "write incomplete");
223       if(result)
224         break;
225     }
226     result = Curl_bufq_unwrite(&q, 1);
227     roffset = 0;
228     while(!Curl_bufq_is_empty(&q)) {
229       unsigned char rbuf[sizeof(buf)];
230       n = Curl_bufq_read(&q, rbuf, sizeof(rbuf), &result);
231       fail_unless(n > 0, "read should work");
232       if(result)
233         break;
234       if(n != sizeof(rbuf)) {
235         fail_unless(Curl_bufq_is_empty(&q), "should be last read");
236       }
237       if(memcmp(buf, rbuf, n)) {
238         fprintf(stderr, "at offset %zu expected '%.*s', got '%.*s'\n",
239                 roffset, (int)n, buf, (int)n, rbuf);
240         fail("read buf content wrong");
241       }
242       roffset += n;
243     }
244     Curl_bufq_reset(&q);
245   }
246 
247   dump_bufq(&q, "at end of test");
248   Curl_bufq_free(&q);
249   if(pool_spares > 0)
250     Curl_bufcp_free(&pool);
251 }
252 
253 UNITTEST_START
254   struct bufq q;
255   ssize_t n;
256   CURLcode result;
257   unsigned char buf[16*1024];
258 
259   Curl_bufq_init(&q, 8*1024, 12);
260   n = Curl_bufq_read(&q, buf, 128, &result);
261   fail_unless(n < 0 && result == CURLE_AGAIN, "read empty fail");
262   Curl_bufq_free(&q);
263 
264   check_bufq(0, 1024, 4, 128, 128, BUFQ_OPT_NONE);
265   check_bufq(0, 1024, 4, 129, 127, BUFQ_OPT_NONE);
266   check_bufq(0, 1024, 4, 2000, 16000, BUFQ_OPT_NONE);
267   check_bufq(0, 1024, 4, 16000, 3000, BUFQ_OPT_NONE);
268 
269   check_bufq(0, 8000, 10, 1234, 1234, BUFQ_OPT_NONE);
270   check_bufq(0, 8000, 10, 8*1024, 4*1024, BUFQ_OPT_NONE);
271 
272   check_bufq(0, 1024, 4, 128, 128, BUFQ_OPT_NO_SPARES);
273   check_bufq(0, 1024, 4, 129, 127, BUFQ_OPT_NO_SPARES);
274   check_bufq(0, 1024, 4, 2000, 16000, BUFQ_OPT_NO_SPARES);
275   check_bufq(0, 1024, 4, 16000, 3000, BUFQ_OPT_NO_SPARES);
276 
277   check_bufq(8, 1024, 4, 128, 128, BUFQ_OPT_NONE);
278   check_bufq(8, 8000, 10, 1234, 1234, BUFQ_OPT_NONE);
279   check_bufq(8, 1024, 4, 129, 127, BUFQ_OPT_NO_SPARES);
280 
281 UNITTEST_STOP
282