Template
223 lines
6.2 KiB
C
223 lines
6.2 KiB
C
/* LINK: ../../src/error.c ../../src/span.c */
|
|
/* tests/unit/test_error.c
|
|
*
|
|
* Unit tests for the error + source-span diagnostic infrastructure.
|
|
*
|
|
* The magic LINK comment on line 1 is REQUIRED by tests/run.sh: it lists
|
|
* the extra .c sources to compile into this test binary (paths relative
|
|
* to tests/unit/, space-separated). munit.c and the include dirs are
|
|
* added automatically by the harness.
|
|
*/
|
|
#include "munit.h"
|
|
|
|
#include "error.h"
|
|
#include "span.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* Render into a tmpfile and slurp it back into a NUL-terminated string.
|
|
* Returns NULL on any I/O failure. Caller frees with free(). */
|
|
static char *
|
|
capture(FILE *f)
|
|
{
|
|
long n;
|
|
char *buf;
|
|
|
|
if (fseek(f, 0, SEEK_END) != 0) {
|
|
return NULL;
|
|
}
|
|
n = ftell(f);
|
|
if (n < 0 || fseek(f, 0, SEEK_SET) != 0) {
|
|
return NULL;
|
|
}
|
|
buf = munit_malloc((size_t)n + 1);
|
|
if (fread(buf, 1, (size_t)n, f) != (size_t)n) {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
buf[n] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/* (a) the canonical span renders byte-for-byte per the plan's contract. */
|
|
static MunitResult
|
|
test_span_exact_rendering(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
const struct st_span span = { "file.kdl", 3, 5 };
|
|
FILE *f = tmpfile();
|
|
char *got;
|
|
|
|
munit_assert_not_null(f);
|
|
st_span_print(f, &span, "<msg>");
|
|
got = capture(f);
|
|
fclose(f);
|
|
munit_assert_not_null(got);
|
|
munit_assert_string_equal(got, "file.kdl:3:5: <msg>\n ...^\n");
|
|
free(got);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* (b) a span at line 0 / col 0 renders without underflow: no crash,
|
|
* sane byte-exact output. */
|
|
static MunitResult
|
|
test_span_zero_coords_no_underflow(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
const struct st_span span = { "zero.kdl", 0, 0 };
|
|
FILE *f = tmpfile();
|
|
char *got;
|
|
|
|
munit_assert_not_null(f);
|
|
st_span_print(f, &span, "<msg>");
|
|
got = capture(f);
|
|
fclose(f);
|
|
munit_assert_not_null(got);
|
|
munit_assert_string_equal(got, "zero.kdl:0:0: <msg>\n ...^\n");
|
|
free(got);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* (c) error categories round-trip through their constructors. */
|
|
static MunitResult
|
|
test_error_category_roundtrip(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_error *e = st_error_kdl_parse("<msg>");
|
|
|
|
munit_assert_not_null(e);
|
|
munit_assert_int(st_error_category_of(e), ==, ST_ERR_KDL_PARSE);
|
|
munit_assert_string_equal(st_error_message(e), "<msg>");
|
|
st_error_free(e);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* every category constructor tags its own category. */
|
|
static MunitResult
|
|
test_error_constructors(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_error *e;
|
|
|
|
e = st_error_kdl_schema("m");
|
|
munit_assert_int(st_error_category_of(e), ==, ST_ERR_KDL_SCHEMA);
|
|
st_error_free(e);
|
|
|
|
e = st_error_io("m");
|
|
munit_assert_int(st_error_category_of(e), ==, ST_ERR_IO);
|
|
st_error_free(e);
|
|
|
|
e = st_error_usage("m");
|
|
munit_assert_int(st_error_category_of(e), ==, ST_ERR_USAGE);
|
|
st_error_free(e);
|
|
|
|
e = st_error_internal("m");
|
|
munit_assert_int(st_error_category_of(e), ==, ST_ERR_INTERNAL);
|
|
st_error_free(e);
|
|
|
|
st_error_free(NULL); /* must be a safe no-op */
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* category names exist, are non-empty, and are pairwise distinct. */
|
|
static MunitResult
|
|
test_error_category_names(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
int c;
|
|
const char *prev = NULL;
|
|
|
|
for (c = ST_ERR_KDL_PARSE; c <= ST_ERR_INTERNAL; c++) {
|
|
const char *n =
|
|
st_error_category_name((enum st_error_category)c);
|
|
munit_assert_not_null(n);
|
|
munit_assert_int(strlen(n), >, 0);
|
|
if (prev != NULL) {
|
|
munit_assert_string_not_equal(n, prev);
|
|
}
|
|
prev = n;
|
|
}
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* st_error_print with a span renders span shape + category-prefixed msg. */
|
|
static MunitResult
|
|
test_error_print_with_span(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
const struct st_span span = { "file.kdl", 3, 5 };
|
|
struct st_error *e = st_error_at(st_error_kdl_parse("<msg>"), &span);
|
|
FILE *f = tmpfile();
|
|
char *got;
|
|
|
|
munit_assert_not_null(e);
|
|
munit_assert_not_null(f);
|
|
st_error_print(f, e);
|
|
got = capture(f);
|
|
fclose(f);
|
|
munit_assert_not_null(got);
|
|
munit_assert_string_equal(
|
|
got, "file.kdl:3:5: [kdl-parse] <msg>\n ...^\n");
|
|
free(got);
|
|
st_error_free(e);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* st_error_print without a span renders category-prefixed message only. */
|
|
static MunitResult
|
|
test_error_print_without_span(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_error *e = st_error_io("disk on fire");
|
|
FILE *f = tmpfile();
|
|
char *got;
|
|
|
|
munit_assert_not_null(e);
|
|
munit_assert_not_null(f);
|
|
st_error_print(f, e);
|
|
got = capture(f);
|
|
fclose(f);
|
|
munit_assert_not_null(got);
|
|
munit_assert_string_equal(got, "[io] disk on fire\n");
|
|
free(got);
|
|
st_error_free(e);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
static MunitTest tests[] = {
|
|
{ "/span/exact-rendering", test_span_exact_rendering, NULL, NULL,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/span/zero-coords-no-underflow", test_span_zero_coords_no_underflow,
|
|
NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/error/category-roundtrip", test_error_category_roundtrip, NULL, NULL,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/error/constructors", test_error_constructors, NULL, NULL,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/error/category-names", test_error_category_names, NULL, NULL,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/error/print-with-span", test_error_print_with_span, NULL, NULL,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/error/print-without-span", test_error_print_without_span, NULL, NULL,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
|
|
};
|
|
|
|
static const MunitSuite suite = {
|
|
"/error", tests, NULL, 1, MUNIT_SUITE_OPTION_NONE,
|
|
};
|
|
|
|
int
|
|
main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)])
|
|
{
|
|
return munit_suite_main(&suite, NULL, argc, argv);
|
|
}
|