blob: aca8335a4730cb4ba7eb7ec21409b9056e82c075 [file] [log] [blame]
// Copyright 2017 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#define MIMICLIB_SCRATCH_BUFFER_ARRAY_SIZE (64 * 1024 * 1024)
#define IO_BUFFER_ARRAY_SIZE (64 * 1024 * 1024)
#define PIXEL_BUFFER_ARRAY_SIZE (64 * 1024 * 1024)
#define TOKEN_BUFFER_ARRAY_SIZE (128 * 1024)
#define WUFFS_TESTLIB_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
uint8_t g_have_array_u8[IO_BUFFER_ARRAY_SIZE];
uint8_t g_want_array_u8[IO_BUFFER_ARRAY_SIZE];
uint8_t g_work_array_u8[IO_BUFFER_ARRAY_SIZE];
uint8_t g_src_array_u8[IO_BUFFER_ARRAY_SIZE];
uint8_t g_mimiclib_scratch_array_u8[MIMICLIB_SCRATCH_BUFFER_ARRAY_SIZE];
uint8_t g_pixel_array_u8[PIXEL_BUFFER_ARRAY_SIZE];
wuffs_base__token g_have_array_token[TOKEN_BUFFER_ARRAY_SIZE];
wuffs_base__token g_want_array_token[TOKEN_BUFFER_ARRAY_SIZE];
wuffs_base__slice_u8 g_have_slice_u8;
wuffs_base__slice_u8 g_want_slice_u8;
wuffs_base__slice_u8 g_work_slice_u8;
wuffs_base__slice_u8 g_src_slice_u8;
wuffs_base__slice_u8 g_mimiclib_scratch_slice_u8;
wuffs_base__slice_u8 g_pixel_slice_u8;
wuffs_base__slice_token g_have_slice_token;
wuffs_base__slice_token g_want_slice_token;
void //
wuffs_testlib__initialize_global_xxx_slices() {
g_have_slice_u8 = ((wuffs_base__slice_u8){
.ptr = g_have_array_u8,
.len = IO_BUFFER_ARRAY_SIZE,
});
g_want_slice_u8 = ((wuffs_base__slice_u8){
.ptr = g_want_array_u8,
.len = IO_BUFFER_ARRAY_SIZE,
});
g_work_slice_u8 = ((wuffs_base__slice_u8){
.ptr = g_work_array_u8,
.len = IO_BUFFER_ARRAY_SIZE,
});
g_src_slice_u8 = ((wuffs_base__slice_u8){
.ptr = g_src_array_u8,
.len = IO_BUFFER_ARRAY_SIZE,
});
g_mimiclib_scratch_slice_u8 = ((wuffs_base__slice_u8){
.ptr = g_mimiclib_scratch_array_u8,
.len = MIMICLIB_SCRATCH_BUFFER_ARRAY_SIZE,
});
g_pixel_slice_u8 = ((wuffs_base__slice_u8){
.ptr = g_pixel_array_u8,
.len = PIXEL_BUFFER_ARRAY_SIZE,
});
g_have_slice_token = ((wuffs_base__slice_token){
.ptr = g_have_array_token,
.len = TOKEN_BUFFER_ARRAY_SIZE,
});
g_want_slice_token = ((wuffs_base__slice_token){
.ptr = g_want_array_token,
.len = TOKEN_BUFFER_ARRAY_SIZE,
});
}
uint8_t //
unhex(uint8_t b) {
if (('0' <= b) && (b <= '9')) {
return b - '0';
}
if (('A' <= b) && (b <= 'F')) {
return b + 10 - 'A';
}
if (('a' <= b) && (b <= 'f')) {
return b + 10 - 'a';
}
return 0;
}
char g_fail_msg[65536] = {0};
#define RETURN_FAIL(...) \
return (snprintf(g_fail_msg, sizeof(g_fail_msg), ##__VA_ARGS__) >= 0) \
? g_fail_msg \
: "unknown failure (snprintf-related)"
#define INCR_FAIL(msg, ...) \
msg += snprintf(msg, sizeof(g_fail_msg) - (msg - g_fail_msg), ##__VA_ARGS__)
#define CHECK_STATUS(prefix, status) \
do { \
wuffs_base__status z = status; \
if (z.repr) { \
RETURN_FAIL("%s: \"%s\"", prefix, z.repr); \
} \
} while (0)
#define CHECK_STRING(string) \
do { \
const char* z = string; \
if (z) { \
return z; \
} \
} while (0)
int g_tests_run = 0;
struct {
int remaining_argc;
char** remaining_argv;
bool bench;
const char* focus;
uint64_t iterscale;
int reps;
} g_flags = {0};
const char* //
parse_flags(int argc, char** argv) {
g_flags.iterscale = 100;
g_flags.reps = 5;
int c = (argc > 0) ? 1 : 0; // Skip argv[0], the program name.
for (; c < argc; c++) {
char* arg = argv[c];
if (*arg++ != '-') {
break;
}
// A double-dash "--foo" is equivalent to a single-dash "-foo". As special
// cases, a bare "-" is not a flag (some programs may interpret it as
// stdin) and a bare "--" means to stop parsing flags.
if (*arg == '\x00') {
break;
} else if (*arg == '-') {
arg++;
if (*arg == '\x00') {
c++;
break;
}
}
if (!strcmp(arg, "bench")) {
g_flags.bench = true;
continue;
}
if (!strncmp(arg, "focus=", 6)) {
g_flags.focus = arg + 6;
continue;
}
if (!strncmp(arg, "iterscale=", 10)) {
arg += 10;
if (!*arg) {
return "missing -iterscale=N value";
}
char* end = NULL;
long int n = strtol(arg, &end, 10);
if (*end) {
return "invalid -iterscale=N value";
}
if ((n < 0) || (1000000 < n)) {
return "out-of-range -iterscale=N value";
}
g_flags.iterscale = n;
continue;
}
if (!strncmp(arg, "reps=", 5)) {
arg += 5;
if (!*arg) {
return "missing -reps=N value";
}
char* end = NULL;
long int n = strtol(arg, &end, 10);
if (*end) {
return "invalid -reps=N value";
}
if ((n < 0) || (1000000 < n)) {
return "out-of-range -reps=N value";
}
g_flags.reps = n;
continue;
}
return "unrecognized flag argument";
}
g_flags.remaining_argc = argc - c;
g_flags.remaining_argv = argv + c;
return NULL;
}
const char* g_proc_package_name = "unknown_package_name";
const char* g_proc_func_name = "unknown_func_name";
bool g_in_focus = false;
#define CHECK_FOCUS(func_name) \
g_proc_func_name = func_name; \
g_in_focus = check_focus(); \
if (!g_in_focus) { \
return NULL; \
}
bool //
check_focus() {
const char* p = g_flags.focus;
if (!p || !*p) {
return true;
}
size_t n = strlen(g_proc_func_name);
// On each iteration of the loop, set p and q so that p (inclusive) and q
// (exclusive) bracket the interesting fragment of the comma-separated
// elements of focus.
while (true) {
const char* r = p;
const char* q = NULL;
while ((*r != ',') && (*r != '\x00')) {
if ((*r == '/') && (q == NULL)) {
q = r;
}
r++;
}
if (q == NULL) {
q = r;
}
// At this point, r points to the comma or NUL byte that ends this element
// of the comma-separated list. q points to the first slash in that
// element, or if there are no slashes, q equals r.
//
// The pointers are named so that (p <= q) && (q <= r).
if (p == q) {
// No-op. Skip empty focus targets, which makes it convenient to
// copy/paste a string with a trailing comma.
} else {
// Strip a leading "Benchmark", if present, from the [p, q) string.
// Idiomatic C function names look like "test_wuffs_gif_lzw_decode_pi"
// and won't start with "Benchmark". Stripping lets us conveniently
// copy/paste a string like "Benchmarkwuffs_gif_decode_10k/gcc" from the
// "wuffs bench std/gif" output.
if ((q - p >= 9) && !strncmp(p, "Benchmark", 9)) {
p += 9;
}
// See if g_proc_func_name (with or without a "test_" or "bench_" prefix)
// starts with the [p, q) string.
if ((n >= q - p) && !strncmp(g_proc_func_name, p, q - p)) {
return true;
}
const char* unprefixed_proc_func_name = NULL;
size_t unprefixed_n = 0;
if ((n >= q - p) && !strncmp(g_proc_func_name, "test_", 5)) {
unprefixed_proc_func_name = g_proc_func_name + 5;
unprefixed_n = n - 5;
} else if ((n >= q - p) && !strncmp(g_proc_func_name, "bench_", 6)) {
unprefixed_proc_func_name = g_proc_func_name + 6;
unprefixed_n = n - 6;
}
if (unprefixed_proc_func_name && (unprefixed_n >= q - p) &&
!strncmp(unprefixed_proc_func_name, p, q - p)) {
return true;
}
}
if (*r == '\x00') {
break;
}
p = r + 1;
}
return false;
}
// https://www.guyrutenberg.com/2008/12/20/expanding-macros-into-string-constants-in-c/
#define WUFFS_TESTLIB_QUOTE_EXPAND(x) #x
#define WUFFS_TESTLIB_QUOTE(x) WUFFS_TESTLIB_QUOTE_EXPAND(x)
// The order matters here. Clang also defines "__GNUC__".
#if defined(__clang__)
const char* g_cc = "clang" WUFFS_TESTLIB_QUOTE(__clang_major__);
const char* g_cc_version = __clang_version__;
#elif defined(__GNUC__)
const char* g_cc = "gcc" WUFFS_TESTLIB_QUOTE(__GNUC__);
const char* g_cc_version = __VERSION__;
#elif defined(_MSC_VER)
const char* g_cc = "cl";
const char* g_cc_version = "???";
#else
const char* g_cc = "cc";
const char* g_cc_version = "???";
#endif
typedef struct {
const char* want_filename;
const char* src_filename;
size_t src_offset0;
size_t src_offset1;
} golden_test;
bool g_bench_warm_up;
struct timeval g_bench_start_tv;
void //
bench_start() {
gettimeofday(&g_bench_start_tv, NULL);
}
void //
bench_finish(uint64_t iters, uint64_t n_bytes) {
struct timeval bench_finish_tv;
gettimeofday(&bench_finish_tv, NULL);
int64_t micros =
(int64_t)(bench_finish_tv.tv_sec - g_bench_start_tv.tv_sec) * 1000000 +
(int64_t)(bench_finish_tv.tv_usec - g_bench_start_tv.tv_usec);
uint64_t nanos = 1;
if (micros > 0) {
nanos = (uint64_t)(micros)*1000;
}
uint64_t kb_per_s = n_bytes * 1000000 / nanos;
const char* name = g_proc_func_name;
if ((strlen(name) >= 6) && !strncmp(name, "bench_", 6)) {
name += 6;
}
if (g_bench_warm_up) {
printf("# (warm up) %s/%s\t%8" PRIu64 ".%06" PRIu64 " seconds\n", //
name, g_cc, nanos / 1000000000, (nanos % 1000000000) / 1000);
} else if (!n_bytes) {
printf("Benchmark%s/%s\t%8" PRIu64 "\t%8" PRIu64 " ns/op\n", //
name, g_cc, iters, nanos / iters);
} else {
printf("Benchmark%s/%s\t%8" PRIu64 "\t%8" PRIu64
" ns/op\t%8d.%03d MB/s\n", //
name, g_cc, iters, nanos / iters, //
(int)(kb_per_s / 1000), (int)(kb_per_s % 1000));
}
// Flush stdout so that "wuffs bench | tee etc" still prints its numbers as
// soon as they are available.
fflush(stdout);
}
const char* //
chdir_to_the_wuffs_root_directory() {
// Chdir to the Wuffs root directory, assuming that we're starting from
// somewhere in the Wuffs repository, so we can find the root directory by
// running chdir("..") a number of times.
int n;
for (n = 0; n < 64; n++) {
if (access("wuffs-root-directory.txt", F_OK) == 0) {
return NULL;
}
// If we're at the root "/", chdir("..") won't change anything.
char cwd_buffer[4];
char* cwd = getcwd(cwd_buffer, 4);
if ((cwd != NULL) && (cwd[0] == '/') && (cwd[1] == '\x00')) {
break;
}
if (chdir("..")) {
return "could not chdir(\"..\")";
}
}
return "could not find Wuffs root directory; chdir there before running this "
"program";
}
typedef const char* (*proc)();
int //
test_main(int argc, char** argv, proc* tests, proc* benches) {
wuffs_testlib__initialize_global_xxx_slices();
const char* status = chdir_to_the_wuffs_root_directory();
if (status) {
fprintf(stderr, "%s\n", status);
return 1;
}
status = parse_flags(argc, argv);
if (status) {
fprintf(stderr, "%s\n", status);
return 1;
}
if (g_flags.remaining_argc > 0) {
fprintf(stderr, "unexpected (non-flag) argument\n");
return 1;
}
int reps = 1;
proc* procs = tests;
if (g_flags.bench) {
reps = g_flags.reps + 1; // +1 for the warm up run.
procs = benches;
printf("# %s\n# %s version %s\n#\n", g_proc_package_name, g_cc,
g_cc_version);
printf(
"# The output format, including the \"Benchmark\" prefixes, is "
"compatible with the\n"
"# https://godoc.org/golang.org/x/perf/cmd/benchstat tool. To install "
"it, first\n"
"# install Go, then run \"go get golang.org/x/perf/cmd/benchstat\".\n");
}
int i;
for (i = 0; i < reps; i++) {
g_bench_warm_up = i == 0;
proc* p;
for (p = procs; *p; p++) {
g_proc_func_name = "unknown_func_name";
g_fail_msg[0] = 0;
g_in_focus = false;
const char* status = (*p)();
if (!g_in_focus) {
continue;
}
if (status) {
printf("%-16s%-8sFAIL %s: %s\n", g_proc_package_name, g_cc,
g_proc_func_name, status);
return 1;
}
if (i == 0) {
g_tests_run++;
}
}
if (i != 0) {
continue;
}
if (g_flags.bench) {
printf("# %d benchmarks, 1+%d reps per benchmark, iterscale=%d\n",
g_tests_run, g_flags.reps, (int)(g_flags.iterscale));
} else {
printf("%-16s%-8sPASS (%d tests)\n", g_proc_package_name, g_cc,
g_tests_run);
}
}
return 0;
}
wuffs_base__io_buffer //
make_io_buffer_from_string(const char* ptr, size_t len) {
return ((wuffs_base__io_buffer){
.data = ((wuffs_base__slice_u8){
.ptr = ((uint8_t*)(ptr)),
.len = len,
}),
.meta = ((wuffs_base__io_buffer_meta){
.wi = len,
.closed = true,
}),
});
}
wuffs_base__rect_ie_u32 //
make_rect_ie_u32(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1) {
wuffs_base__rect_ie_u32 ret;
ret.min_incl_x = x0;
ret.min_incl_y = y0;
ret.max_excl_x = x1;
ret.max_excl_y = y1;
return ret;
}
wuffs_base__io_buffer //
make_limited_reader(wuffs_base__io_buffer b, uint64_t limit) {
uint64_t n = b.meta.wi - b.meta.ri;
bool closed = b.meta.closed;
if (n > limit) {
n = limit;
closed = false;
}
wuffs_base__io_buffer ret;
ret.data.ptr = b.data.ptr + b.meta.ri;
ret.data.len = n;
ret.meta.wi = n;
ret.meta.ri = 0;
ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.ri);
ret.meta.closed = closed;
return ret;
}
wuffs_base__io_buffer //
make_limited_writer(wuffs_base__io_buffer b, uint64_t limit) {
uint64_t n = b.data.len - b.meta.wi;
if (n > limit) {
n = limit;
}
wuffs_base__io_buffer ret;
ret.data.ptr = b.data.ptr + b.meta.wi;
ret.data.len = n;
ret.meta.wi = 0;
ret.meta.ri = 0;
ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.wi);
ret.meta.closed = b.meta.closed;
return ret;
}
wuffs_base__token_buffer //
make_limited_token_writer(wuffs_base__token_buffer b, uint64_t limit) {
uint64_t n = b.data.len - b.meta.wi;
if (n > limit) {
n = limit;
}
wuffs_base__token_buffer ret;
ret.data.ptr = b.data.ptr + b.meta.wi;
ret.data.len = n;
ret.meta.wi = 0;
ret.meta.ri = 0;
ret.meta.pos = wuffs_base__u64__sat_add(b.meta.pos, b.meta.wi);
ret.meta.closed = b.meta.closed;
return ret;
}
// TODO: we shouldn't need to pass the rect. Instead, pass a subset pixbuf.
const char* //
copy_to_io_buffer_from_pixel_buffer(wuffs_base__io_buffer* dst,
wuffs_base__pixel_buffer* src,
wuffs_base__rect_ie_u32 r) {
if (!src) {
return "copy_to_io_buffer_from_pixel_buffer: NULL src";
}
wuffs_base__pixel_format pixfmt =
wuffs_base__pixel_config__pixel_format(&src->pixcfg);
if (wuffs_base__pixel_format__is_planar(&pixfmt)) {
// If we want to support planar pixel buffers, in the future, be concious
// of pixel subsampling.
return "copy_to_io_buffer_from_pixel_buffer: cannot copy from planar src";
}
uint32_t bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&pixfmt);
if (bits_per_pixel == 0) {
return "copy_to_io_buffer_from_pixel_buffer: invalid bits_per_pixel";
} else if ((bits_per_pixel % 8) != 0) {
return "copy_to_io_buffer_from_pixel_buffer: cannot copy fractional bytes";
}
size_t bytes_per_pixel = bits_per_pixel / 8;
uint32_t p;
for (p = 0; p < 1; p++) {
wuffs_base__table_u8 tab = wuffs_base__pixel_buffer__plane(src, p);
uint32_t y;
for (y = r.min_incl_y; y < r.max_excl_y; y++) {
wuffs_base__slice_u8 row = wuffs_base__table_u8__row(tab, y);
if ((r.min_incl_x >= r.max_excl_x) ||
(r.max_excl_x > (row.len / bytes_per_pixel))) {
break;
}
size_t n = r.max_excl_x - r.min_incl_x;
if (n > (SIZE_MAX / bytes_per_pixel)) {
return "copy_to_io_buffer_from_pixel_buffer: n is too large";
}
n *= bytes_per_pixel;
if (n > (dst->data.len - dst->meta.wi)) {
return "copy_to_io_buffer_from_pixel_buffer: dst buffer is too small";
}
memmove(dst->data.ptr + dst->meta.wi, row.ptr + r.min_incl_x, n);
dst->meta.wi += n;
}
}
return NULL;
}
bool //
skip_read_file_patches(const char** path) {
const char* p = *path;
while (*p == '@') {
p++;
while (*p != ';') {
if (*p == '\x00') {
return false;
}
p++;
}
p++;
}
*path = p;
return true;
}
bool //
apply_read_file_patches(wuffs_base__io_buffer* dst,
const char* original_path,
size_t original_wi) {
const char* p = original_path;
while (*p == '@') {
p++;
uint64_t offset = 0;
while (*p != '=') {
if (*p == '\x00') {
return false;
}
offset = (offset << 4) | unhex(*p);
p++;
}
p++;
uint8_t before = 0;
while (*p != '=') {
if (*p == '\x00') {
return false;
}
before = (before << 4) | unhex(*p);
p++;
}
p++;
uint8_t after = 0;
while (*p != ';') {
if (*p == '\x00') {
return false;
}
after = (after << 4) | unhex(*p);
p++;
}
p++;
size_t i = original_wi + offset;
if ((i >= dst->meta.wi) || (dst->data.ptr[i] != before)) {
return false;
}
dst->data.ptr[i] = after;
}
return true;
}
// read_file loads path into dst.
//
// The path may be preceded by zero or more "@123=45=67;" sub-strings, which
// denote a one-byte patch at offset 0x123, changing the byte at that offset
// from 0x45 to 0x67.
const char* //
read_file(wuffs_base__io_buffer* dst, const char* path) {
if (!dst || !path) {
RETURN_FAIL("read_file: NULL argument");
}
if (dst->meta.closed) {
RETURN_FAIL("read_file: dst buffer closed for writes");
}
const char* original_path = path;
size_t original_wi = dst->meta.wi;
if (!skip_read_file_patches(&path)) {
RETURN_FAIL("read_file(\"%s\"): invalid \"@123=45=67;\" patch",
original_path);
}
FILE* f = fopen(path, "r");
if (!f) {
RETURN_FAIL("read_file(\"%s\"): %s (errno=%d)", path, strerror(errno),
errno);
}
uint8_t placeholder[1];
uint8_t* ptr = dst->data.ptr + dst->meta.wi;
size_t len = dst->data.len - dst->meta.wi;
while (true) {
if (!len) {
// We have read all that dst can hold. Check that we have read the full
// file by trying to read one more byte, which should fail with EOF.
ptr = placeholder;
len = 1;
}
size_t n = fread(ptr, 1, len, f);
if (ptr != placeholder) {
ptr += n;
len -= n;
dst->meta.wi += n;
} else if (n) {
fclose(f);
RETURN_FAIL("read_file(\"%s\"): EOF not reached", path);
}
if (feof(f)) {
break;
}
int err = ferror(f);
if (!err) {
continue;
}
if (err == EINTR) {
clearerr(f);
continue;
}
fclose(f);
RETURN_FAIL("read_file(\"%s\"): %s (errno=%d)", path, strerror(err), err);
}
fclose(f);
dst->meta.pos = 0;
dst->meta.closed = true;
if (!apply_read_file_patches(dst, original_path, original_wi)) {
RETURN_FAIL("read_file(\"%s\"): invalid \"@123=45=67;\" patch",
original_path);
}
return NULL;
}
const char* //
read_file_fragment(wuffs_base__io_buffer* dst,
const char* path,
size_t ri_min,
size_t wi_max) {
CHECK_STRING(read_file(dst, path));
if (dst->meta.ri < ri_min) {
dst->meta.ri = ri_min;
}
if (dst->meta.wi > wi_max) {
dst->meta.wi = wi_max;
}
if (dst->meta.ri > dst->meta.wi) {
RETURN_FAIL("read_file_fragment(\"%s\"): ri > wi", path);
}
return NULL;
}
char* //
hex_dump(char* msg, wuffs_base__io_buffer* buf, size_t i) {
if (!msg || !buf) {
RETURN_FAIL("hex_dump: NULL argument");
}
if (buf->meta.wi == 0) {
return msg;
}
size_t base = i - (i & 15);
int j;
for (j = -3 * 16; j <= +3 * 16; j += 16) {
if ((j < 0) && (base < (size_t)(-j))) {
continue;
}
size_t b = base + j;
if (b >= buf->meta.wi) {
break;
}
size_t n = buf->meta.wi - b;
INCR_FAIL(msg, " %06zx:", b);
size_t k;
for (k = 0; k < 16; k++) {
if (k % 2 == 0) {
INCR_FAIL(msg, " ");
}
if (k < n) {
INCR_FAIL(msg, "%02x", buf->data.ptr[b + k]);
} else {
INCR_FAIL(msg, " ");
}
}
INCR_FAIL(msg, " ");
for (k = 0; k < 16; k++) {
char c = ' ';
if (k < n) {
c = buf->data.ptr[b + k];
if ((c < 0x20) || (0x7F <= c)) {
c = '.';
}
}
INCR_FAIL(msg, "%c", c);
}
INCR_FAIL(msg, "\n");
if (n < 16) {
break;
}
}
return msg;
}
const char* //
check_io_buffers_equal(const char* prefix,
wuffs_base__io_buffer* have,
wuffs_base__io_buffer* want) {
if (!have || !want) {
RETURN_FAIL("%sio_buffers_equal: NULL argument", prefix);
}
char* msg = g_fail_msg;
size_t i;
size_t n = have->meta.wi < want->meta.wi ? have->meta.wi : want->meta.wi;
for (i = 0; i < n; i++) {
if (have->data.ptr[i] != want->data.ptr[i]) {
break;
}
}
if (have->meta.wi != want->meta.wi) {
INCR_FAIL(msg, "%sio_buffers_equal: wi: have %zu, want %zu.\n", prefix,
have->meta.wi, want->meta.wi);
} else if (i < have->meta.wi) {
INCR_FAIL(msg, "%sio_buffers_equal: wi=%zu:\n", prefix, n);
} else {
return NULL;
}
INCR_FAIL(msg, "contents differ at byte %zu (in hex: 0x%06zx):\n", i, i);
msg = hex_dump(msg, have, i);
INCR_FAIL(msg, "excerpts of have (above) versus want (below):\n");
msg = hex_dump(msg, want, i);
return g_fail_msg;
}
bool //
strings_are_equal(const char* s, const char* t) {
if (s == t) {
return true;
} else if ((s == NULL) || (t == NULL)) {
return false;
}
return strcmp(s, t) == 0;
}
// throughput_counter is whether to count dst or src bytes, or neither, when
// calculating a benchmark's MB/s throughput number.
//
// Decoders typically use tcounter_dst. Encoders and hashes typically use
// tcounter_src.
typedef enum {
tcounter_neither = 0,
tcounter_dst = 1,
tcounter_src = 2,
} throughput_counter;
const char* //
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 tcounter,
golden_test* gt,
uint64_t wlimit,
uint64_t rlimit,
uint64_t iters,
bool bench) {
if (!codec_func) {
RETURN_FAIL("NULL codec_func");
}
if (!gt) {
RETURN_FAIL("NULL golden_test");
}
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
.data = g_src_slice_u8,
});
wuffs_base__io_buffer have = ((wuffs_base__io_buffer){
.data = g_have_slice_u8,
});
wuffs_base__io_buffer want = ((wuffs_base__io_buffer){
.data = g_want_slice_u8,
});
if (!gt->src_filename) {
src.meta.closed = true;
} else {
const char* status = read_file(&src, gt->src_filename);
if (status) {
return status;
}
}
if (gt->src_offset0 || gt->src_offset1) {
if (gt->src_offset0 > gt->src_offset1) {
RETURN_FAIL("inconsistent src_offsets");
}
if (gt->src_offset1 > src.meta.wi) {
RETURN_FAIL("src_offset1 too large");
}
src.meta.ri = gt->src_offset0;
src.meta.wi = gt->src_offset1;
}
if (bench) {
bench_start();
}
uint64_t n_bytes = 0;
uint64_t i;
for (i = 0; i < iters; i++) {
have.meta.wi = 0;
src.meta.ri = gt->src_offset0;
const char* status =
codec_func(&have, &src, wuffs_initialize_flags, wlimit, rlimit);
if (status) {
return status;
}
switch (tcounter) {
case tcounter_neither:
break;
case tcounter_dst:
n_bytes += have.meta.wi;
break;
case tcounter_src:
n_bytes += src.meta.ri - gt->src_offset0;
break;
}
}
if (bench) {
bench_finish(iters, n_bytes);
return NULL;
}
if (!gt->want_filename) {
want.meta.closed = true;
} else {
const char* status = read_file(&want, gt->want_filename);
if (status) {
return status;
}
}
return check_io_buffers_equal("", &have, &want);
}
const char* //
proc_token_decoder(const char* (*codec_func)(wuffs_base__token_buffer*,
wuffs_base__io_buffer*,
uint32_t,
uint64_t,
uint64_t),
uint32_t wuffs_initialize_flags,
throughput_counter tcounter,
golden_test* gt,
uint64_t wlimit,
uint64_t rlimit,
uint64_t iters,
bool bench) {
if (!codec_func) {
RETURN_FAIL("NULL codec_func");
}
if (!gt) {
RETURN_FAIL("NULL golden_test");
}
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
.data = g_src_slice_u8,
});
wuffs_base__token_buffer have = ((wuffs_base__token_buffer){
.data = g_have_slice_token,
});
if (!gt->src_filename) {
src.meta.closed = true;
} else {
const char* status = read_file(&src, gt->src_filename);
if (status) {
return status;
}
}
if (gt->src_offset0 || gt->src_offset1) {
if (gt->src_offset0 > gt->src_offset1) {
RETURN_FAIL("inconsistent src_offsets");
}
if (gt->src_offset1 > src.meta.wi) {
RETURN_FAIL("src_offset1 too large");
}
src.meta.ri = gt->src_offset0;
src.meta.wi = gt->src_offset1;
}
if (bench) {
bench_start();
}
uint64_t n_bytes = 0;
uint64_t i;
for (i = 0; i < iters; i++) {
have.meta.wi = 0;
src.meta.ri = gt->src_offset0;
const char* status =
codec_func(&have, &src, wuffs_initialize_flags, wlimit, rlimit);
if (status) {
return status;
}
switch (tcounter) {
case tcounter_neither:
break;
case tcounter_dst:
RETURN_FAIL("cannot use tcounter_dst for token decoders");
break;
case tcounter_src:
n_bytes += src.meta.ri - gt->src_offset0;
break;
}
}
if (bench) {
bench_finish(iters, n_bytes);
}
return NULL;
}
const char* //
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 tcounter,
golden_test* gt,
uint64_t wlimit,
uint64_t rlimit,
uint64_t iters_unscaled) {
return proc_io_buffers(codec_func, wuffs_initialize_flags, tcounter, gt,
wlimit, rlimit, iters_unscaled * g_flags.iterscale,
true);
}
const char* //
do_bench_token_decoder(const char* (*codec_func)(wuffs_base__token_buffer*,
wuffs_base__io_buffer*,
uint32_t,
uint64_t,
uint64_t),
uint32_t wuffs_initialize_flags,
throughput_counter tcounter,
golden_test* gt,
uint64_t wlimit,
uint64_t rlimit,
uint64_t iters_unscaled) {
return proc_token_decoder(codec_func, wuffs_initialize_flags, tcounter, gt,
wlimit, rlimit, iters_unscaled * g_flags.iterscale,
true);
}
const char* //
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) {
return proc_io_buffers(codec_func,
WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED,
tcounter_neither, gt, wlimit, rlimit, 1, false);
}
// --------
const char* //
do_run__wuffs_base__image_decoder(wuffs_base__image_decoder* b,
uint64_t* n_bytes_out,
wuffs_base__io_buffer* dst,
wuffs_base__pixel_format pixfmt,
uint32_t* quirks_ptr,
size_t quirks_len,
wuffs_base__io_buffer* src) {
wuffs_base__image_config ic = ((wuffs_base__image_config){});
wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
size_t i;
for (i = 0; i < quirks_len; i++) {
wuffs_base__image_decoder__set_quirk_enabled(b, quirks_ptr[i], true);
}
uint32_t bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&pixfmt);
if (bits_per_pixel == 0) {
return "do_run__wuffs_base__image_decoder: invalid bits_per_pixel";
} else if ((bits_per_pixel % 8) != 0) {
return "do_run__wuffs_base__image_decoder: cannot bench fractional bytes";
}
uint64_t bytes_per_pixel = bits_per_pixel / 8;
CHECK_STATUS("decode_image_config",
wuffs_base__image_decoder__decode_image_config(b, &ic, src));
wuffs_base__pixel_config__set(&ic.pixcfg, pixfmt.repr,
WUFFS_BASE__PIXEL_SUBSAMPLING__NONE,
wuffs_base__pixel_config__width(&ic.pixcfg),
wuffs_base__pixel_config__height(&ic.pixcfg));
CHECK_STATUS("set_from_slice", wuffs_base__pixel_buffer__set_from_slice(
&pb, &ic.pixcfg, g_pixel_slice_u8));
while (true) {
wuffs_base__status status =
wuffs_base__image_decoder__decode_frame_config(b, &fc, src);
if (status.repr == wuffs_base__note__end_of_data) {
break;
} else {
CHECK_STATUS("decode_frame_config", status);
}
wuffs_base__pixel_blend blend =
((wuffs_base__frame_config__index(&fc) == 0) ||
wuffs_base__frame_config__overwrite_instead_of_blend(&fc) ||
wuffs_base__pixel_format__is_indexed(&pixfmt))
? WUFFS_BASE__PIXEL_BLEND__SRC
: WUFFS_BASE__PIXEL_BLEND__SRC_OVER;
CHECK_STATUS("decode_frame",
wuffs_base__image_decoder__decode_frame(
b, &pb, src, blend, g_work_slice_u8, NULL));
if (n_bytes_out) {
uint64_t frame_width = wuffs_base__frame_config__width(&fc);
uint64_t frame_height = wuffs_base__frame_config__height(&fc);
*n_bytes_out += frame_width * frame_height * bytes_per_pixel;
}
if (dst) {
CHECK_STRING(copy_to_io_buffer_from_pixel_buffer(
dst, &pb, wuffs_base__frame_config__bounds(&fc)));
}
}
return NULL;
}
const char* //
do_bench_image_decode(
const char* (*decode_func)(uint64_t* n_bytes_out,
wuffs_base__io_buffer* dst,
uint32_t wuffs_initialize_flags,
wuffs_base__pixel_format pixfmt,
uint32_t* quirks_ptr,
size_t quirks_len,
wuffs_base__io_buffer* src),
uint32_t wuffs_initialize_flags,
wuffs_base__pixel_format pixfmt,
uint32_t* quirks_ptr,
size_t quirks_len,
const char* src_filename,
size_t src_ri,
size_t src_wi,
uint64_t iters_unscaled) {
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
.data = g_src_slice_u8,
});
CHECK_STRING(read_file_fragment(&src, src_filename, src_ri, src_wi));
bench_start();
uint64_t n_bytes = 0;
uint64_t i;
uint64_t iters = iters_unscaled * g_flags.iterscale;
for (i = 0; i < iters; i++) {
src.meta.ri = src_ri;
CHECK_STRING((*decode_func)(&n_bytes, NULL, wuffs_initialize_flags, pixfmt,
quirks_ptr, quirks_len, &src));
}
bench_finish(iters, n_bytes);
return NULL;
}
// --------
const char* //
do_test__wuffs_base__hasher_u32(wuffs_base__hasher_u32* b,
const char* src_filename,
size_t src_ri,
size_t src_wi,
uint32_t want) {
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
.data = g_src_slice_u8,
});
CHECK_STRING(read_file_fragment(&src, src_filename, src_ri, src_wi));
uint32_t have = wuffs_base__hasher_u32__update_u32(
b, ((wuffs_base__slice_u8){
.ptr = (uint8_t*)(src.data.ptr + src.meta.ri),
.len = (size_t)(src.meta.wi - src.meta.ri),
}));
if (have != want) {
RETURN_FAIL("have 0x%08" PRIX32 ", want 0x%08" PRIX32, have, want);
}
return NULL;
}
const char* //
do_test__wuffs_base__image_decoder(
wuffs_base__image_decoder* b,
const char* src_filename,
size_t src_ri,
size_t src_wi,
uint32_t want_width,
uint32_t want_height,
wuffs_base__color_u32_argb_premul want_final_pixel) {
if ((want_width > 16384) || (want_height > 16384) ||
((want_width * want_height * 4) > PIXEL_BUFFER_ARRAY_SIZE)) {
return "want dimensions are too large";
}
wuffs_base__image_config ic = ((wuffs_base__image_config){});
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
.data = g_src_slice_u8,
});
CHECK_STRING(read_file_fragment(&src, src_filename, src_ri, src_wi));
CHECK_STATUS("decode_image_config",
wuffs_base__image_decoder__decode_image_config(b, &ic, &src));
uint32_t have_width = wuffs_base__pixel_config__width(&ic.pixcfg);
if (have_width != want_width) {
RETURN_FAIL("width: have %" PRIu32 ", want %" PRIu32, have_width,
want_width);
}
uint32_t have_height = wuffs_base__pixel_config__height(&ic.pixcfg);
if (have_height != want_height) {
RETURN_FAIL("height: have %" PRIu32 ", want %" PRIu32, have_height,
want_height);
}
wuffs_base__pixel_config__set(
&ic.pixcfg, WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL,
WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, want_width, want_height);
wuffs_base__pixel_buffer pb = ((wuffs_base__pixel_buffer){});
CHECK_STATUS("set_from_slice", wuffs_base__pixel_buffer__set_from_slice(
&pb, &ic.pixcfg, g_pixel_slice_u8));
CHECK_STATUS("decode_frame", wuffs_base__image_decoder__decode_frame(
b, &pb, &src, WUFFS_BASE__PIXEL_BLEND__SRC,
g_work_slice_u8, NULL));
uint64_t n = wuffs_base__pixel_config__pixbuf_len(&ic.pixcfg);
if (n < 4) {
RETURN_FAIL("pixbuf_len too small");
} else if (n > PIXEL_BUFFER_ARRAY_SIZE) {
RETURN_FAIL("pixbuf_len too large");
} else {
wuffs_base__color_u32_argb_premul have_final_pixel =
wuffs_base__peek_u32le__no_bounds_check(&g_pixel_array_u8[n - 4]);
if (have_final_pixel != want_final_pixel) {
RETURN_FAIL("final pixel: have 0x%08" PRIX32 ", want 0x%08" PRIX32,
have_final_pixel, want_final_pixel);
}
}
if ((have_width > 0) && (have_height > 0)) {
wuffs_base__color_u32_argb_premul have_final_pixel =
wuffs_base__pixel_buffer__color_u32_at(&pb, have_width - 1,
have_height - 1);
if (have_final_pixel != want_final_pixel) {
RETURN_FAIL("final pixel: have 0x%08" PRIX32 ", want 0x%08" PRIX32,
have_final_pixel, want_final_pixel);
}
}
return NULL;
}
const char* //
do_test__wuffs_base__io_transformer(wuffs_base__io_transformer* b,
const char* src_filename,
size_t src_ri,
size_t src_wi,
size_t want_wi,
uint8_t want_final_byte) {
if (want_wi > IO_BUFFER_ARRAY_SIZE) {
return "want_wi is too large";
}
wuffs_base__range_ii_u64 workbuf_len =
wuffs_base__io_transformer__workbuf_len(b);
if (workbuf_len.min_incl > workbuf_len.max_incl) {
return "inconsistent workbuf_len";
}
if (workbuf_len.max_incl > IO_BUFFER_ARRAY_SIZE) {
return "workbuf_len is too large";
}
wuffs_base__io_buffer have = ((wuffs_base__io_buffer){
.data = g_have_slice_u8,
});
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
.data = g_src_slice_u8,
});
CHECK_STRING(read_file_fragment(&src, src_filename, src_ri, src_wi));
CHECK_STATUS("transform_io", wuffs_base__io_transformer__transform_io(
b, &have, &src, g_work_slice_u8));
if (have.meta.wi != want_wi) {
RETURN_FAIL("dst wi: have %zu, want %zu", have.meta.wi, want_wi);
}
if ((have.meta.wi > 0) &&
(have.data.ptr[have.meta.wi - 1] != want_final_byte)) {
RETURN_FAIL("final byte: have 0x%02X, want 0x%02X",
have.data.ptr[have.meta.wi - 1], want_final_byte);
}
return NULL;
}
const char* //
do_test__wuffs_base__token_decoder(wuffs_base__token_decoder* b,
golden_test* gt) {
wuffs_base__io_buffer have = ((wuffs_base__io_buffer){
.data = g_have_slice_u8,
});
wuffs_base__io_buffer want = ((wuffs_base__io_buffer){
.data = g_want_slice_u8,
});
wuffs_base__token_buffer tok = ((wuffs_base__token_buffer){
.data = g_have_slice_token,
});
wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
.data = g_src_slice_u8,
});
if (gt->src_filename) {
CHECK_STRING(
read_file_fragment(&src, gt->src_filename, gt->src_offset0,
gt->src_offset1 ? gt->src_offset1 : SIZE_MAX));
} else {
src.meta.closed = true;
}
CHECK_STATUS("decode_tokens", wuffs_base__token_decoder__decode_tokens(
b, &tok, &src, g_work_slice_u8));
uint64_t pos = 0;
while (tok.meta.ri < tok.meta.wi) {
wuffs_base__token* t = &tok.data.ptr[tok.meta.ri++];
uint16_t len = wuffs_base__token__length(t);
if (wuffs_base__token__value(t) != 0) {
uint16_t con = wuffs_base__token__continued(t) ? 1 : 0;
int32_t vmajor = wuffs_base__token__value_major(t);
if ((have.data.len - have.meta.wi) < 16) {
return "testlib: output is too long";
}
// This 16-bytes-per-token debug format is the same one used by
// `script/print-json-token-debug-format.c`.
uint8_t* ptr = have.data.ptr + have.meta.wi;
wuffs_base__poke_u32be__no_bounds_check(ptr + 0x0, (uint32_t)(pos));
wuffs_base__poke_u16be__no_bounds_check(ptr + 0x4, len);
wuffs_base__poke_u16be__no_bounds_check(ptr + 0x6, con);
if (vmajor > 0) {
wuffs_base__poke_u32be__no_bounds_check(ptr + 0x8, vmajor);
uint32_t vminor = wuffs_base__token__value_minor(t);
wuffs_base__poke_u32be__no_bounds_check(ptr + 0xC, vminor);
} else if (vmajor == 0) {
uint8_t vbc = wuffs_base__token__value_base_category(t);
uint32_t vbd = wuffs_base__token__value_base_detail(t);
wuffs_base__poke_u32be__no_bounds_check(ptr + 0x8, 0);
wuffs_base__poke_u8__no_bounds_check(ptr + 0x000C, vbc);
wuffs_base__poke_u24be__no_bounds_check(ptr + 0xD, vbd);
} else {
wuffs_base__poke_u8__no_bounds_check(ptr + 0x0008, 0x01);
wuffs_base__poke_u56be__no_bounds_check(
ptr + 0x9, wuffs_base__token__value_extension(t));
}
have.meta.wi += 16;
}
pos += len;
if (pos > 0xFFFFFFFF) {
return "testlib: input is too long";
}
}
if (gt->want_filename) {
CHECK_STRING(read_file(&want, gt->want_filename));
} else {
want.meta.closed = true;
}
return check_io_buffers_equal("", &have, &want);
}