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