Template
feat(error): add typed errors + source-span diagnostics
This commit is contained in:
@@ -72,6 +72,9 @@ test-suite.log
|
|||||||
*.trs
|
*.trs
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# ---> unit test binaries (built by tests/run.sh, never committed)
|
||||||
|
/tests/.unit-build/
|
||||||
|
|
||||||
# ---> stupidtools binaries (built, never committed)
|
# ---> stupidtools binaries (built, never committed)
|
||||||
/stupidtools
|
/stupidtools
|
||||||
/src/stupidtools
|
/src/stupidtools
|
||||||
|
|||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
#include "error.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* C23 only: ISO/IEC 9899:2024. */
|
||||||
|
|
||||||
|
static const char *const category_names[] = {
|
||||||
|
[ST_ERR_KDL_PARSE] = "kdl-parse",
|
||||||
|
[ST_ERR_KDL_SCHEMA] = "kdl-schema",
|
||||||
|
[ST_ERR_IO] = "io",
|
||||||
|
[ST_ERR_USAGE] = "usage",
|
||||||
|
[ST_ERR_INTERNAL] = "internal",
|
||||||
|
};
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_error_new(enum st_error_category category, const char *message)
|
||||||
|
{
|
||||||
|
struct st_error *e;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (message == NULL) {
|
||||||
|
message = "";
|
||||||
|
}
|
||||||
|
e = calloc(1, sizeof(*e));
|
||||||
|
if (e == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
len = strlen(message);
|
||||||
|
e->message = malloc(len + 1);
|
||||||
|
if (e->message == NULL) {
|
||||||
|
free(e);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(e->message, message, len + 1);
|
||||||
|
e->category = category;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_error_kdl_parse(const char *message)
|
||||||
|
{
|
||||||
|
return st_error_new(ST_ERR_KDL_PARSE, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_error_kdl_schema(const char *message)
|
||||||
|
{
|
||||||
|
return st_error_new(ST_ERR_KDL_SCHEMA, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_error_io(const char *message)
|
||||||
|
{
|
||||||
|
return st_error_new(ST_ERR_IO, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_error_usage(const char *message)
|
||||||
|
{
|
||||||
|
return st_error_new(ST_ERR_USAGE, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_error_internal(const char *message)
|
||||||
|
{
|
||||||
|
return st_error_new(ST_ERR_INTERNAL, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_error_at(struct st_error *e, const struct st_span *span)
|
||||||
|
{
|
||||||
|
if (e != NULL) {
|
||||||
|
e->span = span;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
st_error_free(struct st_error *e)
|
||||||
|
{
|
||||||
|
if (e == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(e->message);
|
||||||
|
free(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum st_error_category
|
||||||
|
st_error_category_of(const struct st_error *e)
|
||||||
|
{
|
||||||
|
return e != NULL ? e->category : ST_ERR_INTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
st_error_message(const struct st_error *e)
|
||||||
|
{
|
||||||
|
return e != NULL ? e->message : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
st_error_category_name(enum st_error_category category)
|
||||||
|
{
|
||||||
|
if ((size_t)category >= sizeof(category_names) / sizeof(category_names[0])) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
return category_names[category];
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
st_error_print(FILE *out, const struct st_error *e)
|
||||||
|
{
|
||||||
|
const char *cat;
|
||||||
|
char *prefixed;
|
||||||
|
size_t len;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (out == NULL || e == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cat = st_error_category_name(e->category);
|
||||||
|
len = strlen(cat) + strlen(e->message) + 4; /* "[", "]", " ", NUL */
|
||||||
|
prefixed = malloc(len);
|
||||||
|
if (prefixed == NULL) {
|
||||||
|
fprintf(out, "%s\n", e->message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rc = snprintf(prefixed, len, "[%s] %s", cat, e->message);
|
||||||
|
if (rc < 0) {
|
||||||
|
prefixed[0] = '\0';
|
||||||
|
}
|
||||||
|
if (e->span != NULL) {
|
||||||
|
st_span_print(out, e->span, prefixed);
|
||||||
|
} else {
|
||||||
|
fprintf(out, "%s\n", prefixed);
|
||||||
|
}
|
||||||
|
free(prefixed);
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef ST_ERROR_H
|
||||||
|
#define ST_ERROR_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "span.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Typed error for all library code paths. Library code RETURNS these;
|
||||||
|
* it never calls exit()/abort() on user input (only main.c exits).
|
||||||
|
*/
|
||||||
|
enum st_error_category {
|
||||||
|
ST_ERR_KDL_PARSE = 0,
|
||||||
|
ST_ERR_KDL_SCHEMA,
|
||||||
|
ST_ERR_IO,
|
||||||
|
ST_ERR_USAGE,
|
||||||
|
ST_ERR_INTERNAL,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct st_error {
|
||||||
|
enum st_error_category category;
|
||||||
|
char *message; /* owned copy; never NULL after construction */
|
||||||
|
const struct st_span *span; /* borrowed, may be NULL */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Constructors. All duplicate `message`; NULL is treated as "". They
|
||||||
|
* return NULL only on allocation failure. */
|
||||||
|
struct st_error *st_error_new(enum st_error_category category,
|
||||||
|
const char *message);
|
||||||
|
struct st_error *st_error_kdl_parse(const char *message);
|
||||||
|
struct st_error *st_error_kdl_schema(const char *message);
|
||||||
|
struct st_error *st_error_io(const char *message);
|
||||||
|
struct st_error *st_error_usage(const char *message);
|
||||||
|
struct st_error *st_error_internal(const char *message);
|
||||||
|
|
||||||
|
/* Attaches a borrowed span and returns `e` for chaining:
|
||||||
|
* err = st_error_at(st_error_kdl_parse("bad token"), &span); */
|
||||||
|
struct st_error *st_error_at(struct st_error *e, const struct st_span *span);
|
||||||
|
|
||||||
|
/* Frees the error (message + struct). NULL is a safe no-op. */
|
||||||
|
void st_error_free(struct st_error *e);
|
||||||
|
|
||||||
|
enum st_error_category st_error_category_of(const struct st_error *e);
|
||||||
|
const char *st_error_message(const struct st_error *e);
|
||||||
|
|
||||||
|
/* Stable display name, e.g. "kdl-parse". Unknown categories yield
|
||||||
|
* "unknown" rather than indexing out of range. */
|
||||||
|
const char *st_error_category_name(enum st_error_category category);
|
||||||
|
|
||||||
|
/* Prints the diagnostic. With a span: the canonical two-line form with
|
||||||
|
* the category-prefixed message ("file:line:col: [cat] <msg>\n" +
|
||||||
|
* caret line). Without a span: "[cat] <msg>\n". NULL is tolerated. */
|
||||||
|
void st_error_print(FILE *out, const struct st_error *e);
|
||||||
|
|
||||||
|
#endif
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#include "span.h"
|
||||||
|
|
||||||
|
/* C23 only: ISO/IEC 9899:2024. */
|
||||||
|
#define ST_SPAN_CARET_PAD_CAP 60
|
||||||
|
|
||||||
|
void
|
||||||
|
st_span_print(FILE *out, const struct st_span *span, const char *message)
|
||||||
|
{
|
||||||
|
size_t pad;
|
||||||
|
|
||||||
|
if (out == NULL || span == NULL || span->file == NULL ||
|
||||||
|
message == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fprintf(out, "%s:%zu:%zu: %s\n", span->file, span->line, span->col,
|
||||||
|
message);
|
||||||
|
/*
|
||||||
|
* Caret line: 4-space indent, "..." elision, caret. The caret sits
|
||||||
|
* three cells past the reported column (the elision marker occupies
|
||||||
|
* those cells), so col 5 renders exactly " ...^". Columns below
|
||||||
|
* 5 collapse onto the elision; columns above 5 push the caret right,
|
||||||
|
* capped to keep the line bounded.
|
||||||
|
*/
|
||||||
|
fputs(" ...", out);
|
||||||
|
pad = span->col > 5 ? span->col - 5 : 0;
|
||||||
|
if (pad > ST_SPAN_CARET_PAD_CAP) {
|
||||||
|
pad = ST_SPAN_CARET_PAD_CAP;
|
||||||
|
}
|
||||||
|
while (pad-- > 0) {
|
||||||
|
fputc(' ', out);
|
||||||
|
}
|
||||||
|
fputs("^\n", out);
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef ST_SPAN_H
|
||||||
|
#define ST_SPAN_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A source span: 1-based line/column into a source file.
|
||||||
|
* `file` is borrowed (never freed); typically points into stable
|
||||||
|
* storage such as a file-name arena or a string literal.
|
||||||
|
*/
|
||||||
|
struct st_span {
|
||||||
|
const char *file;
|
||||||
|
size_t line;
|
||||||
|
size_t col;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Renders the canonical two-line diagnostic:
|
||||||
|
*
|
||||||
|
* <file>:<line>:<col>: <message>
|
||||||
|
* ...^
|
||||||
|
*
|
||||||
|
* The caret line is a fixed 4-space indent followed by a "..."
|
||||||
|
* elision marker and the caret. For the canonical span (col 5)
|
||||||
|
* this is exactly " ...^"; the caret shifts right for larger
|
||||||
|
* columns and is capped so hostile spans cannot force unbounded
|
||||||
|
* output. NULL arguments are tolerated and render nothing.
|
||||||
|
*/
|
||||||
|
void st_span_print(FILE *out, const struct st_span *span,
|
||||||
|
const char *message);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -130,6 +130,51 @@ else
|
|||||||
fail "two positionals exited rc=$rc (want 2)"
|
fail "two positionals exited rc=$rc (want 2)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- 10. unit tests (tests/unit/*.c via munit) ----------------------------
|
||||||
|
# Harness contract (see .omo/notepads/stupidtools/learnings.md):
|
||||||
|
# - each tests/unit/<name>.c declares its extra link sources on its FIRST
|
||||||
|
# line as: /* LINK: ../../src/foo.c ../../src/bar.c */
|
||||||
|
# (paths relative to tests/unit/, space-separated)
|
||||||
|
# - the harness adds -I <repo>/thirdparty/munit -I <repo>/src and
|
||||||
|
# thirdparty/munit/munit.c automatically, compiles with
|
||||||
|
# -std=c23 -Wall -Wextra -Wpedantic, and runs each binary.
|
||||||
|
# - a missing LINK line, a compile failure, or a non-zero exit all FAIL.
|
||||||
|
UNIT_DIR="$TOP_DIR/tests/unit"
|
||||||
|
UNIT_BUILD="$TOP_DIR/tests/.unit-build"
|
||||||
|
if [ -d "$UNIT_DIR" ]; then
|
||||||
|
if ! mkdir -p "$UNIT_BUILD"; then
|
||||||
|
fail "cannot create unit build dir: $UNIT_BUILD"
|
||||||
|
fi
|
||||||
|
for t in "$UNIT_DIR"/*.c; do
|
||||||
|
[ -e "$t" ] || continue
|
||||||
|
name=$(basename "$t" .c)
|
||||||
|
# first line only: /* LINK: src1.c src2.c */
|
||||||
|
link=$(sed -n '1s|^/\* LINK: \(.*\) \*/$|\1|p' "$t")
|
||||||
|
if [ -z "$link" ]; then
|
||||||
|
fail "unit test $name: missing LINK comment on line 1"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# compile with cwd=tests/unit so LINK paths resolve relative to it;
|
||||||
|
# absolute paths for everything else.
|
||||||
|
if (cd "$UNIT_DIR" && cc -std=c23 -Wall -Wextra -Wpedantic \
|
||||||
|
-I "$TOP_DIR/thirdparty/munit" -I "$TOP_DIR/src" \
|
||||||
|
"$TOP_DIR/thirdparty/munit/munit.c" $link "$t" \
|
||||||
|
-o "$UNIT_BUILD/unit_$name") 2>"$UNIT_BUILD/unit_$name.build.log"
|
||||||
|
then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
fail "unit test $name: compile failed (see $UNIT_BUILD/unit_$name.build.log)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if "$UNIT_BUILD/unit_$name" >"$UNIT_BUILD/unit_$name.log" 2>&1; then
|
||||||
|
pass "unit test $name passed"
|
||||||
|
else
|
||||||
|
rc=$?
|
||||||
|
fail "unit test $name exited rc=$rc (see $UNIT_BUILD/unit_$name.log)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
# --- summary -------------------------------------------------------------
|
# --- summary -------------------------------------------------------------
|
||||||
if [ "$TESTS_FAILED" -ne 0 ]; then
|
if [ "$TESTS_FAILED" -ne 0 ]; then
|
||||||
printf 'FAILED: %d/%d checks failed\n' "$TESTS_FAILED" "$TESTS_RUN" >&2
|
printf 'FAILED: %d/%d checks failed\n' "$TESTS_FAILED" "$TESTS_RUN" >&2
|
||||||
|
|||||||
@@ -0,0 +1,222 @@
|
|||||||
|
/* 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user