apfl/src/functional-test-runner.c

244 lines
5.8 KiB
C
Raw Normal View History

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#include <stdarg.h>
#include "apfl.h"
#include "alloc.h"
enum testresult {
T_OK,
T_ERR,
T_FATAL,
};
struct alloc_context {
struct apfl_allocator wrapped;
jmp_buf *jmp;
};
struct test_parts {
struct apfl_string_view script;
struct apfl_string_view output;
};
APFL_NORETURN static void
fatal(jmp_buf *jmp, const char *fmt, ...)
{
va_list varargs;
va_start(varargs, fmt);
vfprintf(stderr, fmt, varargs);
va_end(varargs);
longjmp(*jmp, 1);
}
static void *
alloc_cb(void *opaque, void *oldptr, size_t oldsize, size_t newsize)
{
struct alloc_context *alloc_ctx = opaque;
void *out = ALLOCATOR_CALL(alloc_ctx->wrapped, oldptr, oldsize, newsize);
if (newsize != 0 && out == NULL) {
fatal(
alloc_ctx->jmp,
"Failed to allocate: oldptr=%p, oldsize=%lld, newsize=%lld\n",
oldptr,
(long long int)oldsize,
(long long int)newsize
);
}
return out;
}
#define BUFSIZE 4096
static bool
file_get_contents(const char *filename, struct apfl_string *str, struct apfl_allocator allocator)
{
FILE *f = fopen(filename, "r");
if (f == NULL) {
return false;
}
struct apfl_string_builder sb;
apfl_string_builder_init(allocator, &sb);
char buf[BUFSIZE];
while (!feof(f)) {
size_t len = fread(buf, 1, BUFSIZE, f);
if (!apfl_string_builder_append_bytes(&sb, buf, len)) {
apfl_string_builder_deinit(&sb);
fclose(f);
return false;
}
}
if (!feof(f)) {
apfl_string_builder_deinit(&sb);
fclose(f);
return false;
}
fclose(f);
*str = apfl_string_builder_move_string(&sb);
apfl_string_builder_deinit(&sb);
return true;
}
bool
parse_test(const struct apfl_string str, struct test_parts *parts)
{
struct apfl_string_view sv = apfl_string_view_from(str);
struct apfl_string_view marker_script = apfl_string_view_from("===== script =====\n");
struct apfl_string_view marker_output = apfl_string_view_from("===== output =====\n");
ptrdiff_t script = apfl_string_view_search(sv, marker_script);
ptrdiff_t output = apfl_string_view_search(sv, marker_output);
if (script < 0 || output < 0 || script > output) {
return false;
}
parts->script = apfl_string_view_substr(
sv,
script + marker_script.len,
output - script - marker_script.len
);
parts->output = apfl_string_view_offset(sv, output + marker_output.len);
return true;
}
static void
dump_stack_error(apfl_ctx ctx, apfl_iterative_runner runner)
{
if (apfl_iterative_runner_has_error_on_stack(runner)) {
assert(apfl_debug_print_val(ctx, -1, apfl_format_file_writer(stderr)));
}
}
enum testresult
runtest(const char *filename)
{
jmp_buf jmp;
struct alloc_context alloc_ctx = {
.wrapped = apfl_allocator_default(),
.jmp = &jmp,
};
struct apfl_allocator allocator = {
.opaque = &alloc_ctx,
.alloc = alloc_cb,
};
if (setjmp(jmp) != 0) {
return T_FATAL;
}
struct apfl_string content;
if (!file_get_contents(filename, &content, allocator)) {
fprintf(stderr, "Could not read file \"%s\"\n", filename);
return T_ERR;
}
struct test_parts parts;
if (!parse_test(content, &parts)) {
fprintf(stderr, "Could not parse test \"%s\"\n", filename);
return T_ERR;
}
struct apfl_string_builder output;
apfl_string_builder_init(allocator, &output);
apfl_ctx ctx = apfl_ctx_new((struct apfl_config) {
.allocator = allocator,
.output_writer = apfl_format_string_writer(&output),
});
struct apfl_string_source_reader_data src_data = apfl_string_source_reader_create(parts.script);
apfl_iterative_runner runner = apfl_iterative_runner_new(ctx, apfl_string_source_reader(&src_data));
assert(runner != NULL);
while (apfl_iterative_runner_next(runner)) {
switch (apfl_iterative_runner_get_result(runner)) {
case APFL_RESULT_OK :
break;
case APFL_RESULT_ERR:
fprintf(stderr, "Error occurred during evaluation.\n");
dump_stack_error(ctx, runner);
return T_ERR;
case APFL_RESULT_ERR_FATAL:
fprintf(stderr, "Fatal error occurred during evaluation.\n");
dump_stack_error(ctx, runner);
return T_FATAL;
}
}
apfl_iterative_runner_destroy(runner);
apfl_ctx_destroy(ctx);
struct apfl_string output_string = apfl_string_builder_move_string(&output);
struct apfl_string_view have = apfl_string_view_trim(apfl_string_view_from(output_string));
struct apfl_string_view want = apfl_string_view_trim(parts.output);
if (!apfl_string_eq(have, want)) {
fprintf(
stderr,
"Test failed\n"
"=== WANT ===\n"
APFL_STR_FMT "\n"
"=== HAVE ===\n"
APFL_STR_FMT "\n",
APFL_STR_FMT_ARGS(want),
APFL_STR_FMT_ARGS(have)
);
apfl_string_deinit(allocator, &output_string);
apfl_string_deinit(allocator, &content);
return T_ERR;
}
apfl_string_deinit(allocator, &output_string);
apfl_string_deinit(allocator, &content);
return T_OK;
}
int
main(int argc, const char **argv)
{
if (argc < 2) {
fprintf(stderr, "%s: Need at least one argument\n", argv[0]);
return 1;
}
bool ok = true;
for (int i = 1; i < argc; i++) {
switch (runtest(argv[i])) {
case T_OK:
break;
case T_ERR:
ok = false;
break;
case T_FATAL:
return 1;
}
}
return ok ? 0 : 1;
}