2022-10-30 21:00:11 +00:00
|
|
|
#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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 21:15:29 +00:00
|
|
|
struct apfl_string_builder sb = apfl_string_builder_init(allocator);
|
2022-10-30 21:00:11 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 21:15:29 +00:00
|
|
|
struct apfl_string_builder output = apfl_string_builder_init(allocator);
|
2022-10-30 21:00:11 +00:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-08 20:35:41 +00:00
|
|
|
if (apfl_iterative_runner_stopped_because_of_error(runner)) {
|
|
|
|
|
fprintf(stderr, "Runner stopped due to error.\n");
|
|
|
|
|
return T_FATAL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-30 21:00:11 +00:00
|
|
|
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;
|
|
|
|
|
}
|