• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Wuffs Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <errno.h>
16 #include <inttypes.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/time.h>
21 #include <unistd.h>
22 
23 #define BUFFER_SIZE (64 * 1024 * 1024)
24 
25 #define WUFFS_TESTLIB_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
26 
27 uint8_t global_got_array[BUFFER_SIZE];
28 uint8_t global_want_array[BUFFER_SIZE];
29 uint8_t global_work_array[BUFFER_SIZE];
30 uint8_t global_src_array[BUFFER_SIZE];
31 uint8_t global_pixel_array[BUFFER_SIZE];
32 
33 wuffs_base__slice_u8 global_got_slice = ((wuffs_base__slice_u8){
34     .ptr = global_got_array,
35     .len = BUFFER_SIZE,
36 });
37 
38 wuffs_base__slice_u8 global_want_slice = ((wuffs_base__slice_u8){
39     .ptr = global_want_array,
40     .len = BUFFER_SIZE,
41 });
42 
43 wuffs_base__slice_u8 global_work_slice = ((wuffs_base__slice_u8){
44     .ptr = global_work_array,
45     .len = BUFFER_SIZE,
46 });
47 
48 wuffs_base__slice_u8 global_src_slice = ((wuffs_base__slice_u8){
49     .ptr = global_src_array,
50     .len = BUFFER_SIZE,
51 });
52 
53 wuffs_base__slice_u8 global_pixel_slice = ((wuffs_base__slice_u8){
54     .ptr = global_pixel_array,
55     .len = BUFFER_SIZE,
56 });
57 
58 char fail_msg[65536] = {0};
59 
60 #define RETURN_FAIL(...)                               \
61   snprintf(fail_msg, sizeof(fail_msg), ##__VA_ARGS__); \
62   return fail_msg
63 #define INCR_FAIL(msg, ...) \
64   msg += snprintf(msg, sizeof(fail_msg) - (msg - fail_msg), ##__VA_ARGS__)
65 
66 int tests_run = 0;
67 
68 uint64_t iterscale = 100;
69 
70 const char* proc_package_name = "unknown_package_name";
71 const char* proc_func_name = "unknown_func_name";
72 const char* focus = "";
73 bool in_focus = false;
74 
75 #define CHECK_FOCUS(func_name) \
76   proc_func_name = func_name;  \
77   in_focus = check_focus();    \
78   if (!in_focus) {             \
79     return NULL;               \
80   }
81 
check_focus()82 bool check_focus() {
83   const char* p = focus;
84   if (!*p) {
85     return true;
86   }
87   size_t n = strlen(proc_func_name);
88 
89   // On each iteration of the loop, set p and q so that p (inclusive) and q
90   // (exclusive) bracket the interesting fragment of the comma-separated
91   // elements of focus.
92   while (true) {
93     const char* r = p;
94     const char* q = NULL;
95     while ((*r != ',') && (*r != '\x00')) {
96       if ((*r == '/') && (q == NULL)) {
97         q = r;
98       }
99       r++;
100     }
101     if (q == NULL) {
102       q = r;
103     }
104     // At this point, r points to the comma or NUL byte that ends this element
105     // of the comma-separated list. q points to the first slash in that
106     // element, or if there are no slashes, q equals r.
107     //
108     // The pointers are named so that (p <= q) && (q <= r).
109 
110     if (p == q) {
111       // No-op. Skip empty focus targets, which makes it convenient to
112       // copy/paste a string with a trailing comma.
113     } else {
114       // Strip a leading "Benchmark", if present, from the [p, q) string.
115       // Idiomatic C function names look like "test_wuffs_gif_lzw_decode_pi"
116       // and won't start with "Benchmark". Stripping lets us conveniently
117       // copy/paste a string like "Benchmarkwuffs_gif_decode_10k/gcc" from the
118       // "wuffs bench std/gif" output.
119       if ((q - p >= 9) && !strncmp(p, "Benchmark", 9)) {
120         p += 9;
121       }
122 
123       // See if proc_func_name (with or without a "test_" or "bench_" prefix)
124       // starts with the [p, q) string.
125       if ((n >= q - p) && !strncmp(proc_func_name, p, q - p)) {
126         return true;
127       }
128       const char* unprefixed_proc_func_name = NULL;
129       size_t unprefixed_n = 0;
130       if ((n >= q - p) && !strncmp(proc_func_name, "test_", 5)) {
131         unprefixed_proc_func_name = proc_func_name + 5;
132         unprefixed_n = n - 5;
133       } else if ((n >= q - p) && !strncmp(proc_func_name, "bench_", 6)) {
134         unprefixed_proc_func_name = proc_func_name + 6;
135         unprefixed_n = n - 6;
136       }
137       if (unprefixed_proc_func_name && (unprefixed_n >= q - p) &&
138           !strncmp(unprefixed_proc_func_name, p, q - p)) {
139         return true;
140       }
141     }
142 
143     if (*r == '\x00') {
144       break;
145     }
146     p = r + 1;
147   }
148   return false;
149 }
150 
151 // https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/
152 #define WUFFS_TESTLIB_QUOTE_EXPAND(x) #x
153 #define WUFFS_TESTLIB_QUOTE(x) WUFFS_TESTLIB_QUOTE_EXPAND(x)
154 
155 // The order matters here. Clang also defines "__GNUC__".
156 #if defined(__clang__)
157 const char* cc = "clang" WUFFS_TESTLIB_QUOTE(__clang_major__);
158 const char* cc_version = __clang_version__;
159 #elif defined(__GNUC__)
160 const char* cc = "gcc" WUFFS_TESTLIB_QUOTE(__GNUC__);
161 const char* cc_version = __VERSION__;
162 #elif defined(_MSC_VER)
163 const char* cc = "cl";
164 const char* cc_version = "???";
165 #else
166 const char* cc = "cc";
167 const char* cc_version = "???";
168 #endif
169 
170 typedef struct {
171   const char* want_filename;
172   const char* src_filename;
173   size_t src_offset0;
174   size_t src_offset1;
175 } golden_test;
176 
177 bool bench_warm_up;
178 struct timeval bench_start_tv;
179 
bench_start()180 void bench_start() {
181   gettimeofday(&bench_start_tv, NULL);
182 }
183 
bench_finish(uint64_t iters,uint64_t n_bytes)184 void bench_finish(uint64_t iters, uint64_t n_bytes) {
185   struct timeval bench_finish_tv;
186   gettimeofday(&bench_finish_tv, NULL);
187   int64_t micros =
188       (int64_t)(bench_finish_tv.tv_sec - bench_start_tv.tv_sec) * 1000000 +
189       (int64_t)(bench_finish_tv.tv_usec - bench_start_tv.tv_usec);
190   uint64_t nanos = 1;
191   if (micros > 0) {
192     nanos = (uint64_t)(micros)*1000;
193   }
194   uint64_t kb_per_s = n_bytes * 1000000 / nanos;
195 
196   const char* name = proc_func_name;
197   if ((strlen(name) >= 6) && !strncmp(name, "bench_", 6)) {
198     name += 6;
199   }
200   if (bench_warm_up) {
201     printf("# (warm up) %s/%s\t%8" PRIu64 ".%06" PRIu64 " seconds\n",  //
202            name, cc, nanos / 1000000000, (nanos % 1000000000) / 1000);
203   } else if (!n_bytes) {
204     printf("Benchmark%s/%s\t%8" PRIu64 "\t%8" PRIu64 " ns/op\n",  //
205            name, cc, iters, nanos / iters);
206   } else {
207     printf("Benchmark%s/%s\t%8" PRIu64 "\t%8" PRIu64
208            " ns/op\t%8d.%03d MB/s\n",       //
209            name, cc, iters, nanos / iters,  //
210            (int)(kb_per_s / 1000), (int)(kb_per_s % 1000));
211   }
212   // Flush stdout so that "wuffs bench | tee etc" still prints its numbers as
213   // soon as they are available.
214   fflush(stdout);
215 }
216 
chdir_to_the_wuffs_root_directory()217 const char* chdir_to_the_wuffs_root_directory() {
218   // Chdir to the Wuffs root directory, assuming that we're starting from
219   // somewhere in the Wuffs repository, so we can find the root directory by
220   // running chdir("..") a number of times.
221   int n;
222   for (n = 0; n < 64; n++) {
223     if (access("wuffs-root-directory.txt", F_OK) == 0) {
224       return NULL;
225     }
226 
227     // If we're at the root "/", chdir("..") won't change anything.
228     char cwd_buffer[4];
229     char* cwd = getcwd(cwd_buffer, 4);
230     if ((cwd != NULL) && (cwd[0] == '/') && (cwd[1] == '\x00')) {
231       break;
232     }
233 
234     if (chdir("..")) {
235       return "could not chdir(\"..\")";
236     }
237   }
238   return "could not find Wuffs root directory; chdir there before running this "
239          "program";
240 }
241 
242 typedef const char* (*proc)();
243 
test_main(int argc,char ** argv,proc * tests,proc * benches)244 int test_main(int argc, char** argv, proc* tests, proc* benches) {
245   const char* status = chdir_to_the_wuffs_root_directory();
246   if (status) {
247     fprintf(stderr, "%s\n", status);
248     return 1;
249   }
250 
251   bool bench = false;
252   int reps = 5;
253 
254   int i;
255   for (i = 1; i < argc; i++) {
256     const char* arg = argv[i];
257     size_t arg_len = strlen(arg);
258     if (!strcmp(arg, "-bench")) {
259       bench = true;
260 
261     } else if ((arg_len >= 7) && !strncmp(arg, "-focus=", 7)) {
262       focus = arg + 7;
263 
264     } else if ((arg_len >= 11) && !strncmp(arg, "-iterscale=", 11)) {
265       arg += 11;
266       if (!*arg) {
267         fprintf(stderr, "missing -iterscale=N value\n");
268         return 1;
269       }
270       char* end = NULL;
271       long int n = strtol(arg, &end, 10);
272       if (*end) {
273         fprintf(stderr, "invalid -iterscale=N value\n");
274         return 1;
275       }
276       if ((n < 0) || (1000000 < n)) {
277         fprintf(stderr, "out-of-range -iterscale=N value\n");
278         return 1;
279       }
280       iterscale = n;
281 
282     } else if ((arg_len >= 6) && !strncmp(arg, "-reps=", 6)) {
283       arg += 6;
284       if (!*arg) {
285         fprintf(stderr, "missing -reps=N value\n");
286         return 1;
287       }
288       char* end = NULL;
289       long int n = strtol(arg, &end, 10);
290       if (*end) {
291         fprintf(stderr, "invalid -reps=N value\n");
292         return 1;
293       }
294       if ((n < 0) || (1000000 < n)) {
295         fprintf(stderr, "out-of-range -reps=N value\n");
296         return 1;
297       }
298       reps = n;
299 
300     } else {
301       fprintf(stderr, "unknown flag \"%s\"\n", arg);
302       return 1;
303     }
304   }
305 
306   proc* procs = tests;
307   if (!bench) {
308     reps = 1;
309   } else {
310     reps++;  // +1 for the warm up run.
311     procs = benches;
312     printf("# %s\n# %s version %s\n#\n", proc_package_name, cc, cc_version);
313     printf(
314         "# The output format, including the \"Benchmark\" prefixes, is "
315         "compatible with the\n"
316         "# https://godoc.org/golang.org/x/perf/cmd/benchstat tool. To install "
317         "it, first\n"
318         "# install Go, then run \"go get golang.org/x/perf/cmd/benchstat\".\n");
319   }
320 
321   for (i = 0; i < reps; i++) {
322     bench_warm_up = i == 0;
323     proc* p;
324     for (p = procs; *p; p++) {
325       proc_func_name = "unknown_func_name";
326       fail_msg[0] = 0;
327       in_focus = false;
328       const char* status = (*p)();
329       if (!in_focus) {
330         continue;
331       }
332       if (status) {
333         printf("%-16s%-8sFAIL %s: %s\n", proc_package_name, cc, proc_func_name,
334                status);
335         return 1;
336       }
337       if (i == 0) {
338         tests_run++;
339       }
340     }
341     if (i != 0) {
342       continue;
343     }
344     if (bench) {
345       printf("# %d benchmarks, 1+%d reps per benchmark, iterscale=%d\n",
346              tests_run, reps - 1, (int)(iterscale));
347     } else {
348       printf("%-16s%-8sPASS (%d tests)\n", proc_package_name, cc, tests_run);
349     }
350   }
351   return 0;
352 }
353 
354 // WUFFS_INCLUDE_GUARD is where wuffs_base__foo_bar are defined.
355 #ifdef WUFFS_INCLUDE_GUARD
356 
make_rect_ie_u32(uint32_t x0,uint32_t y0,uint32_t x1,uint32_t y1)357 wuffs_base__rect_ie_u32 make_rect_ie_u32(uint32_t x0,
358                                          uint32_t y0,
359                                          uint32_t x1,
360                                          uint32_t y1) {
361   wuffs_base__rect_ie_u32 ret;
362   ret.min_incl_x = x0;
363   ret.min_incl_y = y0;
364   ret.max_excl_x = x1;
365   ret.max_excl_y = y1;
366   return ret;
367 }
368 
make_limited_reader(wuffs_base__io_buffer b,uint64_t limit)369 wuffs_base__io_buffer make_limited_reader(wuffs_base__io_buffer b,
370                                           uint64_t limit) {
371   uint64_t n = b.meta.wi - b.meta.ri;
372   bool closed = b.meta.closed;
373   if (n > limit) {
374     n = limit;
375     closed = false;
376   }
377 
378   wuffs_base__io_buffer ret;
379   ret.data.ptr = b.data.ptr + b.meta.ri;
380   ret.data.len = n;
381   ret.meta.wi = n;
382   ret.meta.ri = 0;
383   ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.ri);
384   ret.meta.closed = closed;
385   return ret;
386 }
387 
make_limited_writer(wuffs_base__io_buffer b,uint64_t limit)388 wuffs_base__io_buffer make_limited_writer(wuffs_base__io_buffer b,
389                                           uint64_t limit) {
390   uint64_t n = b.data.len - b.meta.wi;
391   if (n > limit) {
392     n = limit;
393   }
394 
395   wuffs_base__io_buffer ret;
396   ret.data.ptr = b.data.ptr + b.meta.wi;
397   ret.data.len = n;
398   ret.meta.wi = 0;
399   ret.meta.ri = 0;
400   ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.wi);
401   ret.meta.closed = b.meta.closed;
402   return ret;
403 }
404 
405 // TODO: we shouldn't need to pass the rect. Instead, pass a subset pixbuf.
copy_to_io_buffer_from_pixel_buffer(wuffs_base__io_buffer * dst,wuffs_base__pixel_buffer * src,wuffs_base__rect_ie_u32 r)406 const char* copy_to_io_buffer_from_pixel_buffer(wuffs_base__io_buffer* dst,
407                                                 wuffs_base__pixel_buffer* src,
408                                                 wuffs_base__rect_ie_u32 r) {
409   if (!src) {
410     return "copy_to_io_buffer_from_pixel_buffer: NULL src";
411   }
412 
413   wuffs_base__pixel_format pixfmt =
414       wuffs_base__pixel_config__pixel_format(&src->pixcfg);
415   if (wuffs_base__pixel_format__is_planar(pixfmt)) {
416     // If we want to support planar pixel buffers, in the future, be concious
417     // of pixel subsampling.
418     return "copy_to_io_buffer_from_pixel_buffer: cannot copy from planar src";
419   }
420   uint32_t bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(pixfmt);
421   if (bits_per_pixel == 0) {
422     return "copy_to_io_buffer_from_pixel_buffer: invalid bits_per_pixel";
423   } else if ((bits_per_pixel % 8) != 0) {
424     return "copy_to_io_buffer_from_pixel_buffer: cannot copy fractional bytes";
425   }
426   size_t bytes_per_pixel = bits_per_pixel / 8;
427 
428   uint32_t p;
429   for (p = 0; p < 1; p++) {
430     wuffs_base__table_u8 tab = wuffs_base__pixel_buffer__plane(src, p);
431     uint32_t y;
432     for (y = r.min_incl_y; y < r.max_excl_y; y++) {
433       wuffs_base__slice_u8 row = wuffs_base__table_u8__row(tab, y);
434       if ((r.min_incl_x >= r.max_excl_x) ||
435           (r.max_excl_x > (row.len / bytes_per_pixel))) {
436         break;
437       }
438 
439       size_t n = r.max_excl_x - r.min_incl_x;
440       if (n > (SIZE_MAX / bytes_per_pixel)) {
441         return "copy_to_io_buffer_from_pixel_buffer: n is too large";
442       }
443       n *= bytes_per_pixel;
444 
445       if (n > (dst->data.len - dst->meta.wi)) {
446         return "copy_to_io_buffer_from_pixel_buffer: dst buffer is too small";
447       }
448       memmove(dst->data.ptr + dst->meta.wi, row.ptr + r.min_incl_x, n);
449       dst->meta.wi += n;
450     }
451   }
452   return NULL;
453 }
454 
read_file(wuffs_base__io_buffer * dst,const char * path)455 const char* read_file(wuffs_base__io_buffer* dst, const char* path) {
456   if (!dst || !path) {
457     RETURN_FAIL("read_file: NULL argument");
458   }
459   if (dst->meta.closed) {
460     RETURN_FAIL("read_file: dst buffer closed for writes");
461   }
462   FILE* f = fopen(path, "r");
463   if (!f) {
464     RETURN_FAIL("read_file(\"%s\"): %s (errno=%d)", path, strerror(errno),
465                 errno);
466   }
467 
468   uint8_t dummy[1];
469   uint8_t* ptr = dst->data.ptr + dst->meta.wi;
470   size_t len = dst->data.len - dst->meta.wi;
471   while (true) {
472     if (!len) {
473       // We have read all that dst can hold. Check that we have read the full
474       // file by trying to read one more byte, which should fail with EOF.
475       ptr = dummy;
476       len = 1;
477     }
478     size_t n = fread(ptr, 1, len, f);
479     if (ptr != dummy) {
480       ptr += n;
481       len -= n;
482       dst->meta.wi += n;
483     } else if (n) {
484       fclose(f);
485       RETURN_FAIL("read_file(\"%s\"): EOF not reached", path);
486     }
487     if (feof(f)) {
488       break;
489     }
490     int err = ferror(f);
491     if (!err) {
492       continue;
493     }
494     if (err == EINTR) {
495       clearerr(f);
496       continue;
497     }
498     fclose(f);
499     RETURN_FAIL("read_file(\"%s\"): %s (errno=%d)", path, strerror(err), err);
500   }
501   fclose(f);
502   dst->meta.pos = 0;
503   dst->meta.closed = true;
504   return NULL;
505 }
506 
hex_dump(char * msg,wuffs_base__io_buffer * buf,size_t i)507 char* hex_dump(char* msg, wuffs_base__io_buffer* buf, size_t i) {
508   if (!msg || !buf) {
509     RETURN_FAIL("hex_dump: NULL argument");
510   }
511   if (buf->meta.wi == 0) {
512     return msg;
513   }
514   size_t base = i - (i & 15);
515   int j;
516   for (j = -3 * 16; j <= +3 * 16; j += 16) {
517     if ((j < 0) && (base < (size_t)(-j))) {
518       continue;
519     }
520     size_t b = base + j;
521     if (b >= buf->meta.wi) {
522       break;
523     }
524     size_t n = buf->meta.wi - b;
525     INCR_FAIL(msg, "  %06zx:", b);
526     size_t k;
527     for (k = 0; k < 16; k++) {
528       if (k % 2 == 0) {
529         INCR_FAIL(msg, " ");
530       }
531       if (k < n) {
532         INCR_FAIL(msg, "%02x", buf->data.ptr[b + k]);
533       } else {
534         INCR_FAIL(msg, "  ");
535       }
536     }
537     INCR_FAIL(msg, "  ");
538     for (k = 0; k < 16; k++) {
539       char c = ' ';
540       if (k < n) {
541         c = buf->data.ptr[b + k];
542         if ((c < 0x20) || (0x7F <= c)) {
543           c = '.';
544         }
545       }
546       INCR_FAIL(msg, "%c", c);
547     }
548     INCR_FAIL(msg, "\n");
549     if (n < 16) {
550       break;
551     }
552   }
553   return msg;
554 }
555 
check_io_buffers_equal(const char * prefix,wuffs_base__io_buffer * got,wuffs_base__io_buffer * want)556 const char* check_io_buffers_equal(const char* prefix,
557                                    wuffs_base__io_buffer* got,
558                                    wuffs_base__io_buffer* want) {
559   if (!got || !want) {
560     RETURN_FAIL("%sio_buffers_equal: NULL argument", prefix);
561   }
562   char* msg = fail_msg;
563   size_t i;
564   size_t n = got->meta.wi < want->meta.wi ? got->meta.wi : want->meta.wi;
565   for (i = 0; i < n; i++) {
566     if (got->data.ptr[i] != want->data.ptr[i]) {
567       break;
568     }
569   }
570   if (got->meta.wi != want->meta.wi) {
571     INCR_FAIL(msg, "%sio_buffers_equal: wi: got %zu, want %zu.\n", prefix,
572               got->meta.wi, want->meta.wi);
573   } else if (i < got->meta.wi) {
574     INCR_FAIL(msg, "%sio_buffers_equal: wi=%zu:\n", prefix, n);
575   } else {
576     return NULL;
577   }
578   INCR_FAIL(msg, "contents differ at byte %zu (in hex: 0x%06zx):\n", i, i);
579   msg = hex_dump(msg, got, i);
580   INCR_FAIL(msg, "excerpts of got (above) versus want (below):\n");
581   msg = hex_dump(msg, want, i);
582   return fail_msg;
583 }
584 
585 // throughput_counter is whether to count dst or src bytes, or neither, when
586 // calculating a benchmark's MB/s throughput number.
587 //
588 // Decoders typically use tc_dst. Encoders and hashes typically use tc_src.
589 typedef enum {
590   tc_neither = 0,
591   tc_dst = 1,
592   tc_src = 2,
593 } throughput_counter;
594 
proc_io_buffers(const char * (* codec_func)(wuffs_base__io_buffer *,wuffs_base__io_buffer *,uint32_t,uint64_t,uint64_t),uint32_t wuffs_initialize_flags,throughput_counter tc,golden_test * gt,uint64_t wlimit,uint64_t rlimit,uint64_t iters,bool bench)595 const char* proc_io_buffers(const char* (*codec_func)(wuffs_base__io_buffer*,
596                                                       wuffs_base__io_buffer*,
597                                                       uint32_t,
598                                                       uint64_t,
599                                                       uint64_t),
600                             uint32_t wuffs_initialize_flags,
601                             throughput_counter tc,
602                             golden_test* gt,
603                             uint64_t wlimit,
604                             uint64_t rlimit,
605                             uint64_t iters,
606                             bool bench) {
607   if (!codec_func) {
608     RETURN_FAIL("NULL codec_func");
609   }
610   if (!gt) {
611     RETURN_FAIL("NULL golden_test");
612   }
613 
614   wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
615       .data = global_src_slice,
616   });
617   wuffs_base__io_buffer got = ((wuffs_base__io_buffer){
618       .data = global_got_slice,
619   });
620   wuffs_base__io_buffer want = ((wuffs_base__io_buffer){
621       .data = global_want_slice,
622   });
623 
624   if (!gt->src_filename) {
625     src.meta.closed = true;
626   } else {
627     const char* status = read_file(&src, gt->src_filename);
628     if (status) {
629       return status;
630     }
631   }
632   if (gt->src_offset0 || gt->src_offset1) {
633     if (gt->src_offset0 > gt->src_offset1) {
634       RETURN_FAIL("inconsistent src_offsets");
635     }
636     if (gt->src_offset1 > src.meta.wi) {
637       RETURN_FAIL("src_offset1 too large");
638     }
639     src.meta.ri = gt->src_offset0;
640     src.meta.wi = gt->src_offset1;
641   }
642 
643   if (bench) {
644     bench_start();
645   }
646   uint64_t n_bytes = 0;
647   uint64_t i;
648   for (i = 0; i < iters; i++) {
649     got.meta.wi = 0;
650     src.meta.ri = gt->src_offset0;
651     const char* status =
652         codec_func(&got, &src, wuffs_initialize_flags, wlimit, rlimit);
653     if (status) {
654       return status;
655     }
656     switch (tc) {
657       case tc_neither:
658         break;
659       case tc_dst:
660         n_bytes += got.meta.wi;
661         break;
662       case tc_src:
663         n_bytes += src.meta.ri - gt->src_offset0;
664         break;
665     }
666   }
667   if (bench) {
668     bench_finish(iters, n_bytes);
669     return NULL;
670   }
671 
672   if (!gt->want_filename) {
673     want.meta.closed = true;
674   } else {
675     const char* status = read_file(&want, gt->want_filename);
676     if (status) {
677       return status;
678     }
679   }
680   return check_io_buffers_equal("", &got, &want);
681 }
682 
do_bench_io_buffers(const char * (* codec_func)(wuffs_base__io_buffer *,wuffs_base__io_buffer *,uint32_t,uint64_t,uint64_t),uint32_t wuffs_initialize_flags,throughput_counter tc,golden_test * gt,uint64_t wlimit,uint64_t rlimit,uint64_t iters_unscaled)683 const char* do_bench_io_buffers(
684     const char* (*codec_func)(wuffs_base__io_buffer*,
685                               wuffs_base__io_buffer*,
686                               uint32_t,
687                               uint64_t,
688                               uint64_t),
689     uint32_t wuffs_initialize_flags,
690     throughput_counter tc,
691     golden_test* gt,
692     uint64_t wlimit,
693     uint64_t rlimit,
694     uint64_t iters_unscaled) {
695   return proc_io_buffers(codec_func, wuffs_initialize_flags, tc, gt, wlimit,
696                          rlimit, iters_unscaled * iterscale, true);
697 }
698 
do_test_io_buffers(const char * (* codec_func)(wuffs_base__io_buffer *,wuffs_base__io_buffer *,uint32_t,uint64_t,uint64_t),golden_test * gt,uint64_t wlimit,uint64_t rlimit)699 const char* do_test_io_buffers(const char* (*codec_func)(wuffs_base__io_buffer*,
700                                                          wuffs_base__io_buffer*,
701                                                          uint32_t,
702                                                          uint64_t,
703                                                          uint64_t),
704                                golden_test* gt,
705                                uint64_t wlimit,
706                                uint64_t rlimit) {
707   return proc_io_buffers(codec_func,
708                          WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED,
709                          tc_neither, gt, wlimit, rlimit, 1, false);
710 }
711 
712 #endif  // WUFFS_INCLUDE_GUARD
713