Template
feat(ext): expose Lua API for checks and languages
This commit is contained in:
@@ -0,0 +1,402 @@
|
||||
/* 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* ---------- 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);
|
||||
}
|
||||
Reference in New Issue
Block a user