#include #include #include #include #include #include #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); 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_get_result(runner) == APFL_RESULT_ERR) { assert(apfl_debug_print_val(ctx, -1, apfl_io_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); apfl_ctx ctx = apfl_ctx_new((struct apfl_config) { .allocator = allocator, .output_writer = apfl_io_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_ERRERR: fprintf(stderr, "Error while handling error.\n"); return T_ERR; case APFL_RESULT_ERR_ALLOC: fprintf(stderr, "Fatal: Could not allocate memory.\n"); return T_FATAL; } } if (apfl_iterative_runner_stopped_because_of_error(runner)) { fprintf(stderr, "Runner stopped due to error.\n"); 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; }