Template
feat(ext): expose Lua API for checks and languages
This commit is contained in:
+266
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* src/ext/api.c - the Lua API over the extension ABI (plan todo 21).
|
||||||
|
*
|
||||||
|
* See src/ext/api.h for the full contract: st_ext_bridge_lua() flushes a
|
||||||
|
* Lua runtime's registrations into a struct st_ext_ctx (thin shim over
|
||||||
|
* the todo-13 ABI -- no registry reimplementation, no DSL mutation), and
|
||||||
|
* st_lua_probe_run() resolves a probe spec by driving the detected C
|
||||||
|
* toolchain through st_ext_run_capture (argv only, never a shell).
|
||||||
|
*
|
||||||
|
* The probe run here is a DIRECT compile/link/run for the acceptance
|
||||||
|
* test only. Feature resolution (todo 14) and configure-script probe
|
||||||
|
* generation (todo 12) consume the registered checks later; this file
|
||||||
|
* stops at "run a spec against a toolchain and report pass/fail".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _POSIX_C_SOURCE
|
||||||
|
#define _POSIX_C_SOURCE 200809L /* mkstemp, write, unlink, strdup */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "api.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* The language v1 custom checks compile against: custom checks are C
|
||||||
|
* snippets, so they belong to the builtin "c" module's toolchain. This
|
||||||
|
* is the one language string this bridge knows; it is NOT a core string
|
||||||
|
* (core never sees it -- api.c is extension territory, like lang_c.c). */
|
||||||
|
#define ST_LUA_CHECK_LANGUAGE "c"
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* language detect stub */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/* A Lua-registered language has no generic detection in v1: there is no
|
||||||
|
* way for an arbitrary extension to express "here is my compiler" that
|
||||||
|
* survives the sandbox (which has no process/io surface). Real detection
|
||||||
|
* for third-party languages lands in todo 22 (example fortran module).
|
||||||
|
* Until then detecting a Lua language is a clean usage error -- a Lua
|
||||||
|
* language still REGISTERS (lands in the ctx, enumerable, name visible)
|
||||||
|
* but its toolchain cannot be detected. */
|
||||||
|
static struct st_error *
|
||||||
|
lua_lang_detect(struct st_ext_ctx *ctx, void *module_ctx,
|
||||||
|
struct st_toolchain *out)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
(void)module_ctx;
|
||||||
|
(void)out;
|
||||||
|
return st_error_usage("Lua-registered language has no detection yet "
|
||||||
|
"(todo 22)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* bridge */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
struct st_error *
|
||||||
|
st_ext_bridge_lua(struct st_ext_ctx *ctx, struct st_lua_rt *rt)
|
||||||
|
{
|
||||||
|
struct st_error *err;
|
||||||
|
size_t i, n;
|
||||||
|
|
||||||
|
if (ctx == NULL || rt == NULL) {
|
||||||
|
return st_error_usage("st_ext_bridge_lua: NULL argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Languages first, then checks (checks reference a language; the
|
||||||
|
* builtin "c" module is already registered by discovery/init). */
|
||||||
|
n = st_lua_language_count(rt);
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
const char *name = st_lua_language_name(rt, i);
|
||||||
|
|
||||||
|
err = st_ext_register_language(ctx, name, lua_lang_detect, NULL,
|
||||||
|
NULL);
|
||||||
|
if (err != NULL) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n = st_lua_check_count(rt);
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
const char *name = st_lua_check_name(rt, i);
|
||||||
|
void *spec = st_lua_check_probe_spec(rt, i);
|
||||||
|
|
||||||
|
/* The Lua check NAME becomes the ABI check KIND; the spec's own
|
||||||
|
* `kind` field (compile/link/run) is the PROBE MODE and stays
|
||||||
|
* inside the spec. probe_spec is BORROWED (owned by the rt --
|
||||||
|
* see api.h ownership note). */
|
||||||
|
err = st_ext_register_check(ctx, ST_LUA_CHECK_LANGUAGE, name, spec);
|
||||||
|
if (err != NULL) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* probe run */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/* Grow a heap argv by one element (kept NULL-terminated). Returns 0 or
|
||||||
|
* -1 on OOM. */
|
||||||
|
static int
|
||||||
|
argv_push(char ***argv, size_t *argc, const char *s)
|
||||||
|
{
|
||||||
|
char **na = realloc(*argv, (*argc + 2) * sizeof *na);
|
||||||
|
|
||||||
|
if (na == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*argv = na;
|
||||||
|
(*argv)[(*argc)++] = (char *)s;
|
||||||
|
(*argv)[*argc] = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy the captured compiler output into *errbuf (or free it). Sets
|
||||||
|
* *errbuf = NULL when the probe passed or there was nothing captured. */
|
||||||
|
static void
|
||||||
|
set_errbuf(char **errbuf, char *captured, int passed)
|
||||||
|
{
|
||||||
|
if (errbuf == NULL) {
|
||||||
|
free(captured);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (passed) {
|
||||||
|
free(captured);
|
||||||
|
*errbuf = NULL;
|
||||||
|
} else if (captured != NULL) {
|
||||||
|
*errbuf = captured; /* hand ownership to the caller */
|
||||||
|
} else {
|
||||||
|
*errbuf = strdup("probe failed (no compiler output)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
st_lua_probe_run(const struct st_toolchain *tc,
|
||||||
|
const struct st_lua_probe_spec *spec, char **errbuf)
|
||||||
|
{
|
||||||
|
char src_tmpl[] = "/tmp/stprobe_src_XXXXXX";
|
||||||
|
char out_tmpl[] = "/tmp/stprobe_out_XXXXXX";
|
||||||
|
int sfd;
|
||||||
|
int ofd;
|
||||||
|
size_t off;
|
||||||
|
size_t slen;
|
||||||
|
size_t nlink = 0;
|
||||||
|
size_t i;
|
||||||
|
size_t argc = 0;
|
||||||
|
char **argv = NULL;
|
||||||
|
char *captured = NULL;
|
||||||
|
size_t captured_len = 0;
|
||||||
|
int compile_only;
|
||||||
|
int run_it;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
if (errbuf != NULL) {
|
||||||
|
*errbuf = NULL;
|
||||||
|
}
|
||||||
|
if (tc == NULL || tc->path == NULL || tc->path[0] == '\0' ||
|
||||||
|
spec == NULL || spec->kind == NULL || spec->source == NULL) {
|
||||||
|
if (errbuf != NULL) {
|
||||||
|
*errbuf = strdup("probe: missing toolchain or spec");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_only = strcmp(spec->kind, "compile") == 0;
|
||||||
|
run_it = strcmp(spec->kind, "run") == 0;
|
||||||
|
|
||||||
|
slen = strlen(spec->source);
|
||||||
|
if (spec->link != NULL) {
|
||||||
|
while (spec->link[nlink] != NULL) {
|
||||||
|
nlink++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write the source to a unique temp file. The "-x c" flag below (not
|
||||||
|
* a ".c" suffix -- mkstemp needs the template to END in XXXXXX, so no
|
||||||
|
* suffix is possible) forces the compiler to classify it as C: an
|
||||||
|
* extensionless file is otherwise treated as a linker input and would
|
||||||
|
* "pass" every probe. mkstemp -> no shell, no path collision; the
|
||||||
|
* source's shell metacharacters are just bytes in a file. */
|
||||||
|
sfd = mkstemp(src_tmpl);
|
||||||
|
if (sfd < 0) {
|
||||||
|
if (errbuf != NULL) {
|
||||||
|
*errbuf = strdup("probe: cannot create temp source file");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* A unique output path (the file is created empty; cc overwrites it).
|
||||||
|
* Non-fatal if it fails: the literal template still names a usable
|
||||||
|
* path and every probe unlinks it afterwards. */
|
||||||
|
ofd = mkstemp(out_tmpl);
|
||||||
|
if (ofd >= 0) {
|
||||||
|
(void)close(ofd);
|
||||||
|
}
|
||||||
|
off = 0;
|
||||||
|
while (off < slen) {
|
||||||
|
ssize_t w = write(sfd, spec->source + off, slen - off);
|
||||||
|
|
||||||
|
if (w < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
off += (size_t)w;
|
||||||
|
}
|
||||||
|
(void)close(sfd);
|
||||||
|
if (off != slen) {
|
||||||
|
(void)unlink(src_tmpl);
|
||||||
|
if (errbuf != NULL) {
|
||||||
|
*errbuf = strdup("probe: cannot write temp source file");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <cc> -x c [-c] <src> -o <out> [link...] */
|
||||||
|
if (argv_push(&argv, &argc, tc->path) != 0 ||
|
||||||
|
argv_push(&argv, &argc, "-x") != 0 ||
|
||||||
|
argv_push(&argv, &argc, "c") != 0 ||
|
||||||
|
(compile_only && argv_push(&argv, &argc, "-c") != 0) ||
|
||||||
|
argv_push(&argv, &argc, src_tmpl) != 0 ||
|
||||||
|
argv_push(&argv, &argc, "-o") != 0 ||
|
||||||
|
argv_push(&argv, &argc, out_tmpl) != 0) {
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
for (i = 0; i < nlink; i++) {
|
||||||
|
if (argv_push(&argv, &argc, spec->link[i]) != 0) {
|
||||||
|
goto oom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compile (and link) -- `out_tmpl` already exists from the mkstemp
|
||||||
|
* placeholder above; cc overwrites it. */
|
||||||
|
rc = st_ext_run_capture(argv, &captured, &captured_len);
|
||||||
|
|
||||||
|
if (rc == 0 && run_it) {
|
||||||
|
char *runargv[2] = { out_tmpl, NULL };
|
||||||
|
char *rcap = NULL;
|
||||||
|
size_t rclen = 0;
|
||||||
|
|
||||||
|
free(captured);
|
||||||
|
captured = NULL;
|
||||||
|
captured_len = 0;
|
||||||
|
rc = st_ext_run_capture(runargv, &rcap, &rclen);
|
||||||
|
free(rcap);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(argv);
|
||||||
|
(void)unlink(out_tmpl);
|
||||||
|
(void)unlink(src_tmpl);
|
||||||
|
set_errbuf(errbuf, captured, rc == 0);
|
||||||
|
return rc == 0 ? 1 : 0;
|
||||||
|
|
||||||
|
oom:
|
||||||
|
free(argv);
|
||||||
|
(void)unlink(out_tmpl);
|
||||||
|
(void)unlink(src_tmpl);
|
||||||
|
free(captured);
|
||||||
|
if (errbuf != NULL) {
|
||||||
|
*errbuf = strdup("probe: out of memory");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
#ifndef ST_EXT_API_H
|
||||||
|
#define ST_EXT_API_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Lua API over the extension ABI (plan todo 21).
|
||||||
|
*
|
||||||
|
* Todo 19 shipped the sandboxed Lua runtime and let extensions RECORD
|
||||||
|
* registrations into the runtime's own lists (st_lua_check_* /
|
||||||
|
* st_lua_language_*). Todo 13 shipped the extension ABI (abi.h) with the
|
||||||
|
* real check/language registries a builtin module (lang_c.c / lang_cpp.c)
|
||||||
|
* registers into. THIS header bridges the two: it flushes a Lua runtime's
|
||||||
|
* registrations into a struct st_ext_ctx so a Lua-registered check or
|
||||||
|
* language behaves exactly like a builtin.
|
||||||
|
*
|
||||||
|
* The bridge (st_ext_bridge_lua) is a thin shim -- it does NOT reimplement
|
||||||
|
* the registry in Lua and does NOT let extensions mutate the DSL grammar:
|
||||||
|
*
|
||||||
|
* - each Lua check -> st_ext_register_check(ctx, "c", name, spec)
|
||||||
|
* - each Lua language -> st_ext_register_language(ctx, name, detect, ...)
|
||||||
|
*
|
||||||
|
* PROBE SPEC (the one piece of module data this todo adds):
|
||||||
|
*
|
||||||
|
* A Lua extension describes a custom check with a Lua table:
|
||||||
|
*
|
||||||
|
* st.register_check("magic", {
|
||||||
|
* kind = "compile", -- "compile" | "link" | "run"
|
||||||
|
* source = "int main(void){return 0;}",
|
||||||
|
* link = { "-lfoo", "-lbar" }, -- optional
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* which the runtime materializes into the C struct below and stores in
|
||||||
|
* the runtime registry's opaque `probe_spec` slot. `kind` is the PROBE
|
||||||
|
* MODE (compile / link / run), not the ABI's check "kind" (that slot
|
||||||
|
* gets the check's NAME -- see the bridge note). `source` is a C
|
||||||
|
* snippet; `link` are extra argv elements for the link step. v1 keeps
|
||||||
|
* everything STRINGS: no Lua functions as probe callbacks (documented
|
||||||
|
* limitation; a later todo may allow functions).
|
||||||
|
*
|
||||||
|
* RESOLVING a spec = compiling `source` with the detected C toolchain
|
||||||
|
* and asserting the compile/link/run succeeded. st_lua_probe_run() does
|
||||||
|
* exactly that (driving the compiler through st_ext_run_capture, argv
|
||||||
|
* only -- never a shell string) and is what the acceptance test uses
|
||||||
|
* directly. The registry-driven resolution that todo 14/16 perform is
|
||||||
|
* OUT of scope here; those todos consume the registered checks and call
|
||||||
|
* the probe runner (or its generated-configure equivalent, todo 12).
|
||||||
|
*
|
||||||
|
* OWNERSHIP (the one subtle contract):
|
||||||
|
*
|
||||||
|
* A probe spec is heap-allocated by the Lua runtime when it parses the
|
||||||
|
* table, OWNED BY THE RUNTIME's registry entry, and freed when the
|
||||||
|
* runtime is freed. The ABI ctx stores only a BORROWED pointer to it
|
||||||
|
* (abi.h's probe_spec slot is opaque module data the ctx never frees).
|
||||||
|
* Consequence: the ctx must not be used to read probe specs after the
|
||||||
|
* runtime that produced them is freed. A bridge call is therefore
|
||||||
|
* expected to be followed by (ctx, rt) being used together and freed in
|
||||||
|
* that order (rt last). This mirrors how lang_c.c's probe_spec would be
|
||||||
|
* owned by the module that registered it.
|
||||||
|
*
|
||||||
|
* Why st_lua_probe_spec_free is a STATIC INLINE here rather than a
|
||||||
|
* function in api.c: the Lua runtime (lua.c, todo 19) must free specs it
|
||||||
|
* owns, but tests/unit/test_lua.c and test_discovery.c link lua.c WITHOUT
|
||||||
|
* api.c. A static inline in this header keeps lua.c free of any api.c
|
||||||
|
* symbol reference while still sharing one spec layout. Do NOT move the
|
||||||
|
* free into api.c without also fixing those two frozen test link lines.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "abi.h"
|
||||||
|
#include "lua.h"
|
||||||
|
|
||||||
|
/* A custom check's probe spec, materialized from the Lua table
|
||||||
|
* { kind = "compile"|"link"|"run", source = "<C snippet>",
|
||||||
|
* link = { "-lfoo", ... } } (link optional). All fields are owned
|
||||||
|
* (strdup'd) and freed together by st_lua_probe_spec_free(). */
|
||||||
|
struct st_lua_probe_spec {
|
||||||
|
char *kind; /* "compile" | "link" | "run" */
|
||||||
|
char *source; /* the C snippet (NUL-terminated, owned) */
|
||||||
|
char **link; /* NULL-terminated extra link args, or NULL when absent */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Frees a spec (all fields). NULL is a safe no-op. Static inline so the
|
||||||
|
* runtime (lua.c) can call it without linking api.c. */
|
||||||
|
static inline void
|
||||||
|
st_lua_probe_spec_free(struct st_lua_probe_spec *spec)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (spec == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(spec->kind);
|
||||||
|
free(spec->source);
|
||||||
|
if (spec->link != NULL) {
|
||||||
|
for (i = 0; spec->link[i] != NULL; i++) {
|
||||||
|
free(spec->link[i]);
|
||||||
|
}
|
||||||
|
free(spec->link);
|
||||||
|
}
|
||||||
|
free(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flushes the Lua runtime's registrations into the ABI ctx:
|
||||||
|
*
|
||||||
|
* languages: st_ext_register_language(ctx, name, lua_lang_detect, ...)
|
||||||
|
* checks: st_ext_register_check(ctx, "c", name, probe_spec)
|
||||||
|
*
|
||||||
|
* where "c" is the language a v1 custom check compiles against (v1 custom
|
||||||
|
* checks are C snippets; the C toolchain -- builtin "c" module -- is what
|
||||||
|
* st_lua_probe_run drives). The language detect fn is a documented v1
|
||||||
|
* stub (Lua languages have no generic detection yet; todo 22 wires it).
|
||||||
|
*
|
||||||
|
* Returns NULL on success. Any registration error (duplicate name that
|
||||||
|
* collides with a builtin, e.g. a Lua check named "header", or a Lua
|
||||||
|
* language named "c") is returned unchanged. Idempotency is NOT promised:
|
||||||
|
* a second bridge re-registers the same names and fails on duplicates. */
|
||||||
|
struct st_error *st_ext_bridge_lua(struct st_ext_ctx *ctx,
|
||||||
|
struct st_lua_rt *rt);
|
||||||
|
|
||||||
|
/* Runs a probe spec against a detected C toolchain (`tc->path` is the
|
||||||
|
* compiler command, e.g. the CC the builtin "c" module detected):
|
||||||
|
*
|
||||||
|
* compile -> <cc> -c <tmp.c> -o <tmp.o>
|
||||||
|
* link -> <cc> <tmp.c> -o <tmp> [link...]
|
||||||
|
* run -> link, then execute <tmp>
|
||||||
|
*
|
||||||
|
* The source is written to a mkstemp'd file and every argument is passed
|
||||||
|
* as an argv element through st_ext_run_capture (execvp) -- NEVER through
|
||||||
|
* a shell, so shell metacharacters in `source` or `link` are inert.
|
||||||
|
*
|
||||||
|
* Returns 1 when the required step(s) all exit 0 (probe passed), 0 when
|
||||||
|
* any step fails (compiler non-zero exit or spawn failure), or -1 on a
|
||||||
|
* setup error (NULL toolchain/spec, temp-file creation/write failure, or
|
||||||
|
* OOM). When `errbuf` is non-NULL and the probe does NOT pass, *errbuf is
|
||||||
|
* set to a malloc'd copy of the captured compiler stderr/stdout (caller
|
||||||
|
* frees); on success *errbuf is set to NULL. */
|
||||||
|
int st_lua_probe_run(const struct st_toolchain *tc,
|
||||||
|
const struct st_lua_probe_spec *spec, char **errbuf);
|
||||||
|
|
||||||
|
#endif /* ST_EXT_API_H */
|
||||||
+153
-6
@@ -54,6 +54,14 @@
|
|||||||
|
|
||||||
#include "lua.h"
|
#include "lua.h"
|
||||||
|
|
||||||
|
/* The probe-spec struct (struct st_lua_probe_spec) and its static-inline
|
||||||
|
* free live in api.h (todo 21). Including it here is deliberate and safe
|
||||||
|
* under the frozen unit-test link lines: api.h contributes only a struct
|
||||||
|
* definition + a static inline, so lua.c gains NO reference to any
|
||||||
|
* api.c/abi.c symbol -- tests/unit/test_lua.c and test_discovery.c, which
|
||||||
|
* link lua.c WITHOUT api.c/abi.c, keep linking. */
|
||||||
|
#include "api.h"
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* libm-free math shims */
|
/* libm-free math shims */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
@@ -296,7 +304,8 @@ pow(double x, double y)
|
|||||||
struct st_lua_reg {
|
struct st_lua_reg {
|
||||||
struct st_lua_reg *next;
|
struct st_lua_reg *next;
|
||||||
char *name; /* owned copy */
|
char *name; /* owned copy */
|
||||||
void *probe_spec; /* checks only; always NULL for languages */
|
void *probe_spec; /* checks: a struct st_lua_probe_spec * (owned);
|
||||||
|
languages: always NULL (name only in v1) */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct st_lua_rt {
|
struct st_lua_rt {
|
||||||
@@ -346,6 +355,7 @@ regs_free(struct st_lua_reg *head)
|
|||||||
while (p != NULL) {
|
while (p != NULL) {
|
||||||
struct st_lua_reg *next = p->next;
|
struct st_lua_reg *next = p->next;
|
||||||
free(p->name);
|
free(p->name);
|
||||||
|
st_lua_probe_spec_free((struct st_lua_probe_spec *)p->probe_spec);
|
||||||
free(p);
|
free(p);
|
||||||
p = next;
|
p = next;
|
||||||
}
|
}
|
||||||
@@ -515,15 +525,143 @@ l_math_open(lua_State *L)
|
|||||||
/* registration API (Lua side) */
|
/* registration API (Lua side) */
|
||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
/* The `st` table: st.register_check(name) / st.register_language(name).
|
/* The `st` table: st.register_check(name[, spec]) and
|
||||||
* Both write into the runtime's own registries through the same C API
|
* st.register_language(name[, spec]). Both write into the runtime's own
|
||||||
* todo 21 will enumerate when bridging into a struct st_ext_ctx. The
|
* registries through the same C API todo 21 enumerates when bridging into
|
||||||
* rt travels as a light-userdata upvalue -- there is no global state. */
|
* a struct st_ext_ctx. The rt travels as a light-userdata upvalue -- there
|
||||||
|
* is no global state. A present second argument (a probe-spec TABLE) is
|
||||||
|
* parsed into a heap struct st_lua_probe_spec (api.h) and passed through
|
||||||
|
* as the registry entry's opaque spec pointer. */
|
||||||
|
|
||||||
|
static void
|
||||||
|
l_links_free(char **link)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (link == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (i = 0; link[i] != NULL; i++) {
|
||||||
|
free(link[i]);
|
||||||
|
}
|
||||||
|
free(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parses the optional probe-spec table (argument 2) into a heap
|
||||||
|
* struct st_lua_probe_spec. Returns NULL when argument 2 is absent or
|
||||||
|
* nil (bare registration); raises a Lua error on a malformed table.
|
||||||
|
* All fields are strdup'd (so the borrowed lua_tostring pointers never
|
||||||
|
* outlive their stack slot); the caller owns the result. */
|
||||||
|
static struct st_lua_probe_spec *
|
||||||
|
l_parse_probe_spec(lua_State *L)
|
||||||
|
{
|
||||||
|
struct st_lua_probe_spec *spec;
|
||||||
|
char *kind_dup = NULL;
|
||||||
|
char *source_dup = NULL;
|
||||||
|
char **link = NULL;
|
||||||
|
size_t nlink = 0;
|
||||||
|
size_t i;
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
if (lua_gettop(L) < 2 || lua_isnoneornil(L, 2)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
luaL_checktype(L, 2, LUA_TTABLE);
|
||||||
|
|
||||||
|
lua_getfield(L, 2, "kind");
|
||||||
|
if (lua_type(L, -1) != LUA_TSTRING) {
|
||||||
|
luaL_error(L, "probe spec 'kind' must be a string");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
s = lua_tostring(L, -1);
|
||||||
|
if (strcmp(s, "compile") != 0 && strcmp(s, "link") != 0 &&
|
||||||
|
strcmp(s, "run") != 0) {
|
||||||
|
luaL_error(L, "probe spec 'kind' must be \"compile\", \"link\" or "
|
||||||
|
"\"run\"");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
kind_dup = strdup(s);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
if (kind_dup == NULL) {
|
||||||
|
luaL_error(L, "out of memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getfield(L, 2, "source");
|
||||||
|
if (lua_type(L, -1) != LUA_TSTRING) {
|
||||||
|
free(kind_dup);
|
||||||
|
luaL_error(L, "probe spec 'source' must be a string");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
s = lua_tostring(L, -1);
|
||||||
|
source_dup = strdup(s);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
if (source_dup == NULL) {
|
||||||
|
free(kind_dup);
|
||||||
|
luaL_error(L, "out of memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getfield(L, 2, "link");
|
||||||
|
if (!lua_isnoneornil(L, -1)) {
|
||||||
|
luaL_checktype(L, -1, LUA_TTABLE);
|
||||||
|
nlink = lua_rawlen(L, -1);
|
||||||
|
link = calloc(nlink + 1, sizeof *link);
|
||||||
|
if (link == NULL) {
|
||||||
|
free(kind_dup);
|
||||||
|
free(source_dup);
|
||||||
|
luaL_error(L, "out of memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
for (i = 0; i < nlink; i++) {
|
||||||
|
size_t slen;
|
||||||
|
|
||||||
|
lua_geti(L, -1, (lua_Integer)i + 1);
|
||||||
|
s = lua_tostring(L, -1);
|
||||||
|
slen = lua_rawlen(L, -1);
|
||||||
|
if (lua_type(L, -1) != LUA_TSTRING || s == NULL ||
|
||||||
|
strlen(s) != slen) {
|
||||||
|
lua_pop(L, 1);
|
||||||
|
l_links_free(link);
|
||||||
|
free(kind_dup);
|
||||||
|
free(source_dup);
|
||||||
|
luaL_error(L, "probe spec 'link' entries must be strings "
|
||||||
|
"without NUL bytes");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
link[i] = strdup(s);
|
||||||
|
lua_pop(L, 1);
|
||||||
|
if (link[i] == NULL) {
|
||||||
|
l_links_free(link);
|
||||||
|
free(kind_dup);
|
||||||
|
free(source_dup);
|
||||||
|
luaL_error(L, "out of memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
link[nlink] = NULL;
|
||||||
|
}
|
||||||
|
lua_pop(L, 1);
|
||||||
|
|
||||||
|
spec = malloc(sizeof *spec);
|
||||||
|
if (spec == NULL) {
|
||||||
|
l_links_free(link);
|
||||||
|
free(kind_dup);
|
||||||
|
free(source_dup);
|
||||||
|
luaL_error(L, "out of memory");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
spec->kind = kind_dup;
|
||||||
|
spec->source = source_dup;
|
||||||
|
spec->link = link;
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
l_st_register_check(lua_State *L)
|
l_st_register_check(lua_State *L)
|
||||||
{
|
{
|
||||||
struct st_lua_rt *rt = lua_touserdata(L, lua_upvalueindex(1));
|
struct st_lua_rt *rt = lua_touserdata(L, lua_upvalueindex(1));
|
||||||
|
struct st_lua_probe_spec *spec;
|
||||||
size_t len;
|
size_t len;
|
||||||
const char *name;
|
const char *name;
|
||||||
|
|
||||||
@@ -535,7 +673,9 @@ l_st_register_check(lua_State *L)
|
|||||||
if (name == NULL || strlen(name) != len) {
|
if (name == NULL || strlen(name) != len) {
|
||||||
return luaL_error(L, "check name must not contain a NUL byte");
|
return luaL_error(L, "check name must not contain a NUL byte");
|
||||||
}
|
}
|
||||||
if (st_lua_register_check(rt, name, NULL) != 0) {
|
spec = l_parse_probe_spec(L);
|
||||||
|
if (st_lua_register_check(rt, name, spec) != 0) {
|
||||||
|
st_lua_probe_spec_free(spec); /* not stored: free it */
|
||||||
return luaL_error(L, "check '%s' is already registered", name);
|
return luaL_error(L, "check '%s' is already registered", name);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -556,6 +696,13 @@ l_st_register_language(lua_State *L)
|
|||||||
if (name == NULL || strlen(name) != len) {
|
if (name == NULL || strlen(name) != len) {
|
||||||
return luaL_error(L, "language name must not contain a NUL byte");
|
return luaL_error(L, "language name must not contain a NUL byte");
|
||||||
}
|
}
|
||||||
|
/* Optional second arg: accept a probe-spec TABLE for forward
|
||||||
|
* compatibility, but for v1 register the name only -- the table's
|
||||||
|
* contents (detection info) are consumed by todo 22. A non-table
|
||||||
|
* second arg is a clean error. */
|
||||||
|
if (lua_gettop(L) >= 2 && !lua_isnoneornil(L, 2)) {
|
||||||
|
luaL_checktype(L, 2, LUA_TTABLE);
|
||||||
|
}
|
||||||
if (st_lua_register_language(rt, name) != 0) {
|
if (st_lua_register_language(rt, name) != 0) {
|
||||||
return luaL_error(L, "language '%s' is already registered", name);
|
return luaL_error(L, "language '%s' is already registered", name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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