Files

691 lines
22 KiB
C

/* LINK: ../../src/kdl/parser.c ../../src/kdl/lexer.c ../../src/error.c ../../src/span.c */
/* tests/unit/test_parser.c
*
* Unit tests for the KDL 2.0.0 recursive-descent parser (todo 7).
*
* 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). parser.c depends on lexer.c (token
* stream), error.c (typed errors) and span.c (caret rendering).
*
* These tests assert on the REAL AST structure — node counts, argument
* and property order, children nesting, spans — never just "no crash".
* AST token slices are borrowed from the source buffer, so the source
* must outlive the document (both kept alive until st_kdl_document_free).
*/
#include "munit.h"
#include "error.h"
#include "kdl/ast.h"
#include "kdl/lexer.h"
#include "span.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ---- fixture loading -------------------------------------------------- */
/* Locate tests/fixtures/basic.kdl. The harness runs the test binary with
* cwd = repo top (make check) or tests/unit (manual run); probe both. */
static const char *
fixture_path(void)
{
static const char *const candidates[] = {
"tests/fixtures/basic.kdl",
"../fixtures/basic.kdl",
};
size_t i;
for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
FILE *f = fopen(candidates[i], "rb");
if (f != NULL) {
fclose(f);
return candidates[i];
}
}
return NULL;
}
/* Slurp the fixture into a NUL-terminated buffer (the lexer requires NUL
* termination). Returns NULL on any I/O failure. Caller frees. */
static char *
slurp_fixture(const char *path)
{
FILE *f;
long n;
char *buf;
if (path == NULL) {
return NULL;
}
f = fopen(path, "rb");
if (f == NULL) {
return NULL;
}
if (fseek(f, 0, SEEK_END) != 0) {
fclose(f);
return NULL;
}
n = ftell(f);
if (n < 0 || fseek(f, 0, SEEK_SET) != 0) {
fclose(f);
return NULL;
}
buf = munit_malloc((size_t)n + 1);
if (fread(buf, 1, (size_t)n, f) != (size_t)n) {
free(buf);
fclose(f);
return NULL;
}
fclose(f);
buf[n] = '\0';
return buf;
}
/* ---- token-ref assertions --------------------------------------------- */
static void
assert_ref(const struct st_kdl_token_ref *ref, enum st_token_kind kind,
const char *text, size_t line, size_t col)
{
munit_assert_not_null(ref);
munit_assert_int(ref->kind, ==, kind);
if (text != NULL) {
munit_assert_not_null(ref->text);
munit_assert_size(ref->len, ==, strlen(text));
munit_assert_memory_equal(ref->len, ref->text, text);
}
munit_assert_size(ref->span.line, ==, line);
munit_assert_size(ref->span.col, ==, col);
}
/* ---- tests ------------------------------------------------------------ */
/* (a) tests/fixtures/basic.kdl parses to the expected AST: node count,
* names, arg order, prop key/value kinds, children nesting, spans. */
static MunitResult
test_parse_fixture(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
const char *path = fixture_path();
char *src;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
struct st_kdl_prop *p;
munit_assert_not_null(path);
src = slurp_fixture(path);
munit_assert_not_null(src);
doc = st_kdl_parse(src, path, &err);
munit_assert_not_null(doc);
munit_assert_null(err);
/* --- three top-level nodes: project, target, credits ------------- */
n = doc->nodes;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_IDENT, "project", 7, 1);
munit_assert_null(n->args);
p = n->props;
munit_assert_not_null(p);
assert_ref(&p->key, ST_TOK_IDENT, "name", 7, 9);
assert_ref(&p->value, ST_TOK_STRING, "\"stupidtools\"", 7, 14);
p = p->next;
munit_assert_not_null(p);
assert_ref(&p->key, ST_TOK_IDENT, "version", 7, 28);
assert_ref(&p->value, ST_TOK_STRING, "\"1.0.0\"", 7, 36);
munit_assert_null(p->next);
/* --- target "default" with 6 children ---------------------------- */
n = n->next;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_IDENT, "target", 9, 1);
munit_assert_not_null(n->args);
assert_ref(&n->args->value, ST_TOK_STRING, "\"default\"", 9, 8);
munit_assert_null(n->args->next);
munit_assert_null(n->props);
{
struct st_kdl_node *c = n->children;
/* child 1: src "src/main.c" */
munit_assert_not_null(c);
assert_ref(&c->name, ST_TOK_IDENT, "src", 10, 5);
munit_assert_not_null(c->args);
assert_ref(&c->args->value, ST_TOK_STRING, "\"src/main.c\"", 10, 9);
munit_assert_null(c->args->next);
/* child 2: src "src/kdl/lexer.c" (after ';') */
c = c->next;
munit_assert_not_null(c);
assert_ref(&c->name, ST_TOK_IDENT, "src", 10, 24);
assert_ref(&c->args->value, ST_TOK_STRING, "\"src/kdl/lexer.c\"", 10,
28);
munit_assert_null(c->args->next);
/* child 3: cc-flags "-std=c23" "-Wall" */
c = c->next;
munit_assert_not_null(c);
assert_ref(&c->name, ST_TOK_IDENT, "cc-flags", 11, 5);
munit_assert_not_null(c->args);
assert_ref(&c->args->value, ST_TOK_STRING, "\"-std=c23\"", 11, 14);
assert_ref(&c->args->next->value, ST_TOK_STRING, "\"-Wall\"", 11, 25);
munit_assert_null(c->args->next->next);
/* child 4: jobs 8 */
c = c->next;
munit_assert_not_null(c);
assert_ref(&c->name, ST_TOK_IDENT, "jobs", 12, 5);
assert_ref(&c->args->value, ST_TOK_NUMBER, "8", 12, 10);
munit_assert_null(c->args->next);
/* child 5: optimize #true debug=#false */
c = c->next;
munit_assert_not_null(c);
assert_ref(&c->name, ST_TOK_IDENT, "optimize", 13, 5);
assert_ref(&c->args->value, ST_TOK_TRUE, "#true", 13, 14);
munit_assert_null(c->args->next);
munit_assert_not_null(c->props);
assert_ref(&c->props->key, ST_TOK_IDENT, "debug", 13, 20);
assert_ref(&c->props->value, ST_TOK_FALSE, "#false", 13, 26);
munit_assert_null(c->props->next);
/* child 6: feature "unit-tests" { option "munit" { default #true } } */
c = c->next;
munit_assert_not_null(c);
assert_ref(&c->name, ST_TOK_IDENT, "feature", 14, 5);
assert_ref(&c->args->value, ST_TOK_STRING, "\"unit-tests\"", 14, 13);
munit_assert_null(c->args->next);
munit_assert_not_null(c->children);
assert_ref(&c->children->name, ST_TOK_IDENT, "option", 15, 9);
assert_ref(&c->children->args->value, ST_TOK_STRING, "\"munit\"", 15,
16);
munit_assert_not_null(c->children->children);
assert_ref(&c->children->children->name, ST_TOK_IDENT, "default", 15,
26);
assert_ref(&c->children->children->args->value, ST_TOK_TRUE, "#true",
15, 34);
munit_assert_null(c->children->children->args->next);
munit_assert_null(c->next);
}
/* --- credits with type annotation (meta) -------------------------- */
n = n->next;
munit_assert_not_null(n);
munit_assert_not_null(n->type);
/* the TYPE token's span starts at the opening '('; its text slice is
* the inner identifier without the parens (see lexer.h) */
assert_ref(n->type, ST_TOK_TYPE, "meta", 21, 1);
assert_ref(&n->name, ST_TOK_IDENT, "credits", 21, 7);
assert_ref(&n->args->value, ST_TOK_STRING, "\"huntedbytheirs\"", 21, 15);
munit_assert_null(n->args->next);
munit_assert_null(n->props);
munit_assert_null(n->next);
st_kdl_document_free(doc);
free(src);
return MUNIT_OK;
}
/* (b) `node {` — unterminated children block errors with the span of the
* `{` token. */
static MunitResult
test_unterminated_children(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
const char *src = "node {";
struct st_error *err = NULL;
struct st_kdl_document *doc;
doc = st_kdl_parse(src, "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
/* the span must point at the `{` (line 1, col 6), not at EOF */
munit_assert_not_null(err->span);
munit_assert_size(err->span->line, ==, 1);
munit_assert_size(err->span->col, ==, 6);
munit_assert_true(strstr(st_error_message(err), "children") != NULL);
st_error_free(err);
return MUNIT_OK;
}
/* (c) a stray `}` at top level errors with the span of the `}`. */
static MunitResult
test_stray_rbrace(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
doc = st_kdl_parse("}", "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
munit_assert_not_null(err->span);
munit_assert_size(err->span->line, ==, 1);
munit_assert_size(err->span->col, ==, 1);
st_error_free(err);
/* also stray inside a completed children block: `node { child } }` */
err = NULL;
doc = st_kdl_parse("node { child } }", "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_size(err->span->col, ==, 16);
st_error_free(err);
return MUNIT_OK;
}
/* (d) prop classification: a token followed by `=` is a key, the next
* value token is its value; bare values are args, in source order. */
static MunitResult
test_prop_classification(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
struct st_kdl_arg *a;
struct st_kdl_prop *p;
doc = st_kdl_parse("node key=value key2=\"v2\" 1 2 #true", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
n = doc->nodes;
munit_assert_not_null(n);
munit_assert_null(n->next);
p = n->props;
munit_assert_not_null(p);
assert_ref(&p->key, ST_TOK_IDENT, "key", 1, 6);
assert_ref(&p->value, ST_TOK_IDENT, "value", 1, 10);
p = p->next;
munit_assert_not_null(p);
assert_ref(&p->key, ST_TOK_IDENT, "key2", 1, 16);
assert_ref(&p->value, ST_TOK_STRING, "\"v2\"", 1, 21);
munit_assert_null(p->next);
a = n->args;
munit_assert_not_null(a);
assert_ref(&a->value, ST_TOK_NUMBER, "1", 1, 26);
a = a->next;
munit_assert_not_null(a);
assert_ref(&a->value, ST_TOK_NUMBER, "2", 1, 28);
a = a->next;
munit_assert_not_null(a);
assert_ref(&a->value, ST_TOK_TRUE, "#true", 1, 30);
munit_assert_null(a->next);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (e) interleaved args/props keep their relative order within each list:
* `node a=1 b c=2 d` -> args [b, d], props [a=1, c=2]. */
static MunitResult
test_interleaved_order(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
doc = st_kdl_parse("node a=1 b c=2 d", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
n = doc->nodes;
munit_assert_not_null(n);
/* args, in order: b then d */
munit_assert_not_null(n->args);
assert_ref(&n->args->value, ST_TOK_IDENT, "b", 1, 10);
assert_ref(&n->args->next->value, ST_TOK_IDENT, "d", 1, 16);
munit_assert_null(n->args->next->next);
/* props, in order: a=1 then c=2 */
munit_assert_not_null(n->props);
assert_ref(&n->props->key, ST_TOK_IDENT, "a", 1, 6);
assert_ref(&n->props->value, ST_TOK_NUMBER, "1", 1, 8);
assert_ref(&n->props->next->key, ST_TOK_IDENT, "c", 1, 12);
assert_ref(&n->props->next->value, ST_TOK_NUMBER, "2", 1, 14);
munit_assert_null(n->props->next->next);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (f) newline terminates a node: `a 1\nb 2` is two nodes, not one node
* with four entries. */
static MunitResult
test_newline_separates_nodes(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
doc = st_kdl_parse("a 1\nb 2", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
n = doc->nodes;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_IDENT, "a", 1, 1);
assert_ref(&n->args->value, ST_TOK_NUMBER, "1", 1, 3);
munit_assert_null(n->args->next);
n = n->next;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_IDENT, "b", 2, 1);
assert_ref(&n->args->value, ST_TOK_NUMBER, "2", 2, 3);
munit_assert_null(n->args->next);
munit_assert_null(n->next);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (g) semicolons separate nodes; repeated separators are trivia. */
static MunitResult
test_semicolon_separators(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
size_t count = 0;
doc = st_kdl_parse("node ; ; node", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
for (n = doc->nodes; n != NULL; n = n->next) {
assert_ref(&n->name, ST_TOK_IDENT, "node", 1,
(size_t)(count == 0 ? 1 : 10));
count++;
}
munit_assert_size(count, ==, 2);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (h) an empty document parses to a document with zero nodes. */
static MunitResult
test_empty_document(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
doc = st_kdl_parse("", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
munit_assert_null(doc->nodes);
st_kdl_document_free(doc);
/* whitespace/comment-only is also an empty document */
err = NULL;
doc = st_kdl_parse(" // nothing\n/* nor here */\n/- ;", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
munit_assert_null(doc->nodes);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (i) `node = x` is invalid: the node name cannot be a property key. */
static MunitResult
test_equals_after_name(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
doc = st_kdl_parse("node = x", "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
munit_assert_not_null(err->span);
munit_assert_size(err->span->line, ==, 1);
munit_assert_size(err->span->col, ==, 6);
st_error_free(err);
/* a non-string value before `=` is equally invalid: `node 1 = 2` */
err = NULL;
doc = st_kdl_parse("node 1 = 2", "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_size(err->span->col, ==, 8);
st_error_free(err);
return MUNIT_OK;
}
/* (j) a property needs a value: `node key =` errors at EOF; `node key = {`
* errors at the `{`. */
static MunitResult
test_prop_missing_value(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
doc = st_kdl_parse("node key =", "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_true(strstr(st_error_message(err), "value") != NULL);
munit_assert_not_null(err->span);
munit_assert_size(err->span->col, ==, 11);
st_error_free(err);
err = NULL;
doc = st_kdl_parse("node key = {", "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_true(strstr(st_error_message(err), "value") != NULL);
munit_assert_size(err->span->col, ==, 12);
st_error_free(err);
return MUNIT_OK;
}
/* (k) a lexer error is propagated with its span, copied into owned
* storage: the span must still be readable after st_kdl_parse returns. */
static MunitResult
test_lexer_error_propagates(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
doc = st_kdl_parse("node \"unterminated", "t.kdl", &err);
munit_assert_null(doc);
munit_assert_not_null(err);
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
munit_assert_not_null(err->span);
munit_assert_size(err->span->line, ==, 1);
munit_assert_size(err->span->col, ==, 6);
munit_assert_true(strstr(st_error_message(err), "unterminated") != NULL);
st_error_free(err);
return MUNIT_OK;
}
/* (l) type annotation attaches to the node name; quoted node names and
* string keys work. */
static MunitResult
test_annotation_and_string_names(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
doc = st_kdl_parse("(u8)size 4\n\"quoted node\" x\np \"k s\"=1", "t.kdl",
&err);
munit_assert_not_null(doc);
munit_assert_null(err);
n = doc->nodes;
munit_assert_not_null(n);
munit_assert_not_null(n->type);
assert_ref(n->type, ST_TOK_TYPE, "u8", 1, 1);
assert_ref(&n->name, ST_TOK_IDENT, "size", 1, 5);
assert_ref(&n->args->value, ST_TOK_NUMBER, "4", 1, 10);
munit_assert_null(n->args->next);
n = n->next;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_STRING, "\"quoted node\"", 2, 1);
assert_ref(&n->args->value, ST_TOK_IDENT, "x", 2, 15);
munit_assert_null(n->args->next);
n = n->next;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_IDENT, "p", 3, 1);
munit_assert_not_null(n->props);
assert_ref(&n->props->key, ST_TOK_STRING, "\"k s\"", 3, 3);
assert_ref(&n->props->value, ST_TOK_NUMBER, "1", 3, 9);
munit_assert_null(n->props->next);
munit_assert_null(n->next);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (m) single-line children blocks and nested blocks parse recursively. */
static MunitResult
test_children_nesting(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
doc = st_kdl_parse("p { a; b; c { d } }", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
n = doc->nodes;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_IDENT, "p", 1, 1);
munit_assert_not_null(n->children);
assert_ref(&n->children->name, ST_TOK_IDENT, "a", 1, 5);
assert_ref(&n->children->next->name, ST_TOK_IDENT, "b", 1, 8);
assert_ref(&n->children->next->next->name, ST_TOK_IDENT, "c", 1, 11);
munit_assert_not_null(n->children->next->next->children);
assert_ref(&n->children->next->next->children->name, ST_TOK_IDENT, "d",
1, 15);
munit_assert_null(n->children->next->next->next);
munit_assert_null(n->next);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (n) `/-` and comments are trivia: a slashdash between entries vanishes,
* and a node ends at the slashdash/semicolon regardless of line. */
static MunitResult
test_trivia_skipped(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
struct st_error *err = NULL;
struct st_kdl_document *doc;
struct st_kdl_node *n;
size_t count = 0;
doc = st_kdl_parse("node a=1 /-\n; // still trivia\nb", "t.kdl", &err);
munit_assert_not_null(doc);
munit_assert_null(err);
n = doc->nodes;
munit_assert_not_null(n);
assert_ref(&n->name, ST_TOK_IDENT, "node", 1, 1);
munit_assert_not_null(n->props);
assert_ref(&n->props->key, ST_TOK_IDENT, "a", 1, 6);
assert_ref(&n->props->value, ST_TOK_NUMBER, "1", 1, 8);
munit_assert_null(n->props->next);
munit_assert_null(n->args);
for (n = n->next; n != NULL; n = n->next) {
count++;
}
munit_assert_size(count, ==, 1);
assert_ref(&doc->nodes->next->name, ST_TOK_IDENT, "b", 3, 1);
st_kdl_document_free(doc);
return MUNIT_OK;
}
/* (o) `st_kdl_document_free(NULL)` is a safe no-op. */
static MunitResult
test_free_null(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
st_kdl_document_free(NULL);
return MUNIT_OK;
}
static MunitTest tests[] = {
{ "/parse/fixture-basic-kdl", test_parse_fixture, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/unterminated-children", test_unterminated_children, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/stray-rbrace", test_stray_rbrace, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/prop-classification", test_prop_classification, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/interleaved-order", test_interleaved_order, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/newline-separates-nodes", test_newline_separates_nodes, NULL,
NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/semicolon-separators", test_semicolon_separators, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/empty-document", test_empty_document, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/equals-after-name", test_equals_after_name, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/prop-missing-value", test_prop_missing_value, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/lexer-error-propagates", test_lexer_error_propagates, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/annotation-and-string-names", test_annotation_and_string_names,
NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/children-nesting", test_children_nesting, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/trivia-skipped", test_trivia_skipped, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ "/parse/free-null", test_free_null, NULL, NULL,
MUNIT_TEST_OPTION_NONE, NULL },
{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
};
static const MunitSuite suite = {
"/parser", 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);
}