/* LINK: ../../src/ext/api.c ../../src/ext/lua.c ../../src/ext/abi.c ../../src/ext/lang_c.c ../../src/ext/lang_cpp.c ../../src/error.c ../../src/span.c ../../thirdparty/lua/lapi.c ../../thirdparty/lua/lauxlib.c ../../thirdparty/lua/lbaselib.c ../../thirdparty/lua/lcode.c ../../thirdparty/lua/lctype.c ../../thirdparty/lua/ldebug.c ../../thirdparty/lua/ldo.c ../../thirdparty/lua/ldump.c ../../thirdparty/lua/lfunc.c ../../thirdparty/lua/lgc.c ../../thirdparty/lua/llex.c ../../thirdparty/lua/lmem.c ../../thirdparty/lua/lobject.c ../../thirdparty/lua/lopcodes.c ../../thirdparty/lua/lparser.c ../../thirdparty/lua/lstate.c ../../thirdparty/lua/lstring.c ../../thirdparty/lua/ltable.c ../../thirdparty/lua/ltm.c ../../thirdparty/lua/lundump.c ../../thirdparty/lua/lvm.c ../../thirdparty/lua/lzio.c ../../thirdparty/lua/lstrlib.c ../../thirdparty/lua/ltablib.c */ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L /* setenv/unsetenv, access */ #endif /* * tests/unit/test_api.c * * Unit tests for the Lua API over the extension ABI (src/ext/api.h, * plan todo 21): st_ext_bridge_lua flushes a Lua runtime's registrations * into a struct st_ext_ctx, and st_lua_probe_run resolves a probe spec * against a detected C toolchain. * * The magic LINK comment on line 1 is REQUIRED by tests/run.sh: it lists * the extra .c sources compiled into this test binary (paths relative to * tests/unit/). It links api.c, the sandboxed Lua runtime (lua.c + the * same 27 vendored Lua sources as test_lua.c, minus linit/lmathlib/ * loadlib/liolib/loslib/lua.c/luac.c -- see the -lm note there), the * extension ABI + builtin C/C++ modules, and error/span. */ #include "munit.h" #include "ext/abi.h" #include "ext/api.h" #include "ext/lua.h" #include #include #include /* ---------- helpers ---------- */ static void reset_cc_env(void) { unsetenv("CC"); unsetenv("CFLAGS"); unsetenv("CXX"); unsetenv("CXXFLAGS"); } /* Fresh ctx + rt pair with the builtin C/C++ modules already registered * (a bridge flushes Lua registrations on top of them). */ static int fresh_setup(struct st_ext_ctx **ctx, struct st_lua_rt **rt) { *ctx = st_ext_ctx_new(); *rt = st_lua_rt_new(); if (*ctx == NULL || *rt == NULL) { return -1; } if (st_ext_init_builtins(*ctx) != NULL) { return -1; } return 0; } /* ---------- (a) a Lua check lands in the ctx after bridging ---------- */ static MunitResult test_bridge_check_visible(const MunitParameter params[], void *data) { struct st_ext_ctx *ctx = NULL; struct st_lua_rt *rt = NULL; struct st_error *err; size_t i, n; int found = 0; (void)params; (void)data; munit_assert_int(fresh_setup(&ctx, &rt), ==, 0); err = st_lua_run(rt, "st.register_check(\"magic\", {kind=\"compile\", " "source=\"int main(void){return 0;}\"})", "=api-check"); munit_assert_null(err); err = st_ext_bridge_lua(ctx, rt); munit_assert_null(err); /* builtin "c"/header + "cxx"/header + the bridged "c"/magic = 3 */ munit_assert_size(st_ext_check_count(ctx), ==, 3); n = st_ext_check_count(ctx); for (i = 0; i < n; i++) { if (strcmp(st_ext_check_kind(ctx, i), "magic") != 0) { continue; } const struct st_lua_probe_spec *spec = st_ext_check_probe_spec(ctx, i); found = 1; munit_assert_string_equal(st_ext_check_language(ctx, i), "c"); munit_assert_not_null(spec); munit_assert_string_equal(spec->kind, "compile"); munit_assert_not_null(strstr(spec->source, "int main")); } munit_assert_int(found, ==, 1); /* the ctx borrows the spec from the runtime: free ctx first, rt last */ st_ext_ctx_free(ctx); st_lua_rt_free(rt); return MUNIT_OK; } /* ---------- (b) probe resolves true/false against the detected CC ---------- */ static MunitResult test_probe_true_false(const MunitParameter params[], void *data) { struct st_ext_ctx *ctx = NULL; struct st_lua_rt *rt = NULL; struct st_error *err; const struct st_toolchain *tc; struct st_lua_probe_spec valid; struct st_lua_probe_spec invalid; char *valid_eb = NULL; char *invalid_eb = NULL; int rv, ri; (void)params; (void)data; munit_assert_int(fresh_setup(&ctx, &rt), ==, 0); reset_cc_env(); err = st_ext_detect_language(ctx, "c"); munit_assert_null(err); tc = st_ext_language_toolchain_named(ctx, "c"); munit_assert_not_null(tc); munit_assert_not_null(tc->path); valid.kind = "compile"; valid.source = "int main(void){return 0;}"; valid.link = NULL; invalid.kind = "compile"; invalid.source = "int main(void){this is not C;}"; invalid.link = NULL; rv = st_lua_probe_run(tc, &valid, &valid_eb); ri = st_lua_probe_run(tc, &invalid, &invalid_eb); munit_assert_int(rv, ==, 1); munit_assert_null(valid_eb); /* a pass carries no error text */ munit_assert_int(ri, ==, 0); /* the false probe must have ACTUALLY failed the compile: the captured * compiler stderr is non-empty (misleading-success guard) */ munit_assert_not_null(invalid_eb); munit_assert_int(strlen(invalid_eb), >, 0); free(valid_eb); free(invalid_eb); st_ext_ctx_free(ctx); st_lua_rt_free(rt); return MUNIT_OK; } /* ---------- (c) a Lua language lands in the ctx after bridging ---------- */ static MunitResult test_language_lands(const MunitParameter params[], void *data) { struct st_ext_ctx *ctx = NULL; struct st_lua_rt *rt = NULL; struct st_error *err; size_t i, n; int found = 0; (void)params; (void)data; munit_assert_int(fresh_setup(&ctx, &rt), ==, 0); err = st_lua_run(rt, "st.register_language(\"fortran\", {})", "=api-lang"); munit_assert_null(err); err = st_ext_bridge_lua(ctx, rt); munit_assert_null(err); n = st_ext_language_count(ctx); munit_assert_size(n, ==, 3); /* c, cxx, fortran */ for (i = 0; i < n; i++) { if (strcmp(st_ext_language_name(ctx, i), "fortran") == 0) { found = 1; } } munit_assert_int(found, ==, 1); st_ext_ctx_free(ctx); st_lua_rt_free(rt); return MUNIT_OK; } /* ---------- (d) a Lua registration colliding with a builtin errors ---------- */ static MunitResult test_duplicate_error(const MunitParameter params[], void *data) { struct st_ext_ctx *ctx = NULL; struct st_lua_rt *rt = NULL; struct st_error *err; const char *msg; (void)params; (void)data; /* a Lua check named "header" collides with the builtin "c"/"header" */ munit_assert_int(fresh_setup(&ctx, &rt), ==, 0); err = st_lua_run(rt, "st.register_check(\"header\")", "=api-dup"); munit_assert_null(err); err = st_ext_bridge_lua(ctx, rt); munit_assert_not_null(err); msg = st_error_message(err); munit_assert_not_null(strstr(msg, "already registered")); st_error_free(err); st_ext_ctx_free(ctx); st_lua_rt_free(rt); /* a Lua language named "c" collides with the builtin language */ munit_assert_int(fresh_setup(&ctx, &rt), ==, 0); err = st_lua_run(rt, "st.register_language(\"c\")", "=api-dup2"); munit_assert_null(err); err = st_ext_bridge_lua(ctx, rt); munit_assert_not_null(err); msg = st_error_message(err); munit_assert_not_null(strstr(msg, "already registered")); st_error_free(err); st_ext_ctx_free(ctx); st_lua_rt_free(rt); return MUNIT_OK; } /* ---------- (e) malformed probe-spec tables are clean errors ---------- */ static MunitResult test_malformed_spec_errors(const MunitParameter params[], void *data) { struct st_lua_rt *rt; struct st_error *err; const char *msg; (void)params; (void)data; rt = st_lua_rt_new(); munit_assert_not_null(rt); /* non-table second argument */ err = st_lua_run(rt, "st.register_check(\"x\", \"not-a-table\")", "=api-bad1"); munit_assert_not_null(err); msg = st_error_message(err); munit_assert_not_null(strstr(msg, "table")); st_error_free(err); /* unknown kind */ err = st_lua_run(rt, "st.register_check(\"x\", {kind=\"bogus\", " "source=\"int x;\"})", "=api-bad2"); munit_assert_not_null(err); msg = st_error_message(err); munit_assert_not_null(strstr(msg, "kind")); st_error_free(err); /* missing source */ err = st_lua_run(rt, "st.register_check(\"x\", {kind=\"compile\"})", "=api-bad3"); munit_assert_not_null(err); msg = st_error_message(err); munit_assert_not_null(strstr(msg, "source")); st_error_free(err); /* the runtime stays usable after errors */ err = st_lua_run(rt, "st.register_check(\"ok\")", "=api-ok"); munit_assert_null(err); st_lua_rt_free(rt); return MUNIT_OK; } /* ---------- (f) shell metacharacters in a probe source stay inert ---------- */ static MunitResult test_injection_inert(const MunitParameter params[], void *data) { struct st_ext_ctx *ctx = NULL; struct st_lua_rt *rt = NULL; struct st_error *err; const struct st_toolchain *tc; struct st_lua_probe_spec spec; static const char *marker = "/tmp/stprobe_injected_marker"; char *eb = NULL; int rc; (void)params; (void)data; munit_assert_int(fresh_setup(&ctx, &rt), ==, 0); reset_cc_env(); err = st_ext_detect_language(ctx, "c"); munit_assert_null(err); tc = st_ext_language_toolchain_named(ctx, "c"); munit_assert_not_null(tc); (void)unlink(marker); spec.kind = "compile"; spec.source = "int main(void){return 0;}\" ; touch " "/tmp/stprobe_injected_marker ; \""; spec.link = NULL; /* not valid C -> the probe fails; the injection must NOT have run */ rc = st_lua_probe_run(tc, &spec, &eb); munit_assert_int(rc, ==, 0); munit_assert_int(access(marker, F_OK), ==, -1); free(eb); (void)unlink(marker); st_ext_ctx_free(ctx); st_lua_rt_free(rt); return MUNIT_OK; } /* ---------- (g) link/run probe modes and the link[] flag passthrough ---------- */ static MunitResult test_probe_modes(const MunitParameter params[], void *data) { struct st_ext_ctx *ctx = NULL; struct st_lua_rt *rt = NULL; struct st_error *err; const struct st_toolchain *tc; struct st_lua_probe_spec link; struct st_lua_probe_spec run; struct st_lua_probe_spec runfail; struct st_lua_probe_spec badlink; static char *bad_link[] = { "-l__stprobe_nonexistent_lib__", NULL }; (void)params; (void)data; munit_assert_int(fresh_setup(&ctx, &rt), ==, 0); reset_cc_env(); err = st_ext_detect_language(ctx, "c"); munit_assert_null(err); tc = st_ext_language_toolchain_named(ctx, "c"); munit_assert_not_null(tc); link.kind = "link"; link.source = "int main(void){return 0;}"; link.link = NULL; run.kind = "run"; run.source = "int main(void){return 0;}"; run.link = NULL; runfail.kind = "run"; runfail.source = "int main(void){return 7;}"; runfail.link = NULL; badlink.kind = "link"; badlink.source = "int main(void){return 0;}"; badlink.link = bad_link; munit_assert_int(st_lua_probe_run(tc, &link, NULL), ==, 1); munit_assert_int(st_lua_probe_run(tc, &run, NULL), ==, 1); munit_assert_int(st_lua_probe_run(tc, &runfail, NULL), ==, 0); /* a bogus -l flag reaches the linker and fails -> the link[] argv * elements are genuinely passed through */ munit_assert_int(st_lua_probe_run(tc, &badlink, NULL), ==, 0); st_ext_ctx_free(ctx); st_lua_rt_free(rt); return MUNIT_OK; } static MunitTest tests[] = { { "/api/bridge-check-visible", test_bridge_check_visible, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, { "/api/probe-true-false", test_probe_true_false, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, { "/api/language-lands", test_language_lands, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, { "/api/duplicate-error", test_duplicate_error, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, { "/api/malformed-spec-errors", test_malformed_spec_errors, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, { "/api/injection-inert", test_injection_inert, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, { "/api/probe-modes", test_probe_modes, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, { NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }, }; static const MunitSuite suite = { "/api", 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); }