Template
Replace the v1 minimal preamble in the generated ./configure with the
full argument-parsing section (src/gen/args.{h,c}): -h/--help usage,
--version, --prefix/--exec-prefix/--host/--build in = and space forms,
per-DSL-option --enable-<name>/--disable-<name> (default from the
option's default property), generic positional VAR=VALUE overrides
(identifier-validated, single-quote-escaped eval assignment), unknown
--* -> usage on stderr + exit 1, cross_compiling=yes when --host differs
from --build. The substitution set gains exec_prefix/host/build/
cross_compiling plus one enable_<name> per option. Option names are
validated as shell identifiers at generation time.
Tests: 12 new /gen/args/* cases (help lists --prefix/--enable-debug/
--disable-debug; CC=clang positional + env; enable/debug default;
cross_compiling observable; --bogus/--bogus=1/--enable- errors;
--prefix missing/empty; space form; exec_prefix defaulting; VAR=VALUE
injection inert; hostile option name errors at generation) - 28/28
green, three-shell -n + banned-construct sweep clean.
1478 lines
44 KiB
C
1478 lines
44 KiB
C
/* LINK: ../../src/gen/args.c ../../src/gen/config.c ../../src/gen/configure.c ../../src/gen/sh_emit.c ../../src/detect/probe.c ../../src/detect/checks.c ../../src/detect/check_registry.c ../../src/detect/resolve.c ../../src/kdl/schema.c ../../src/kdl/parser.c ../../src/kdl/lexer.c ../../src/kdl/value.c ../../src/ext/abi.c ../../src/ext/lang_c.c ../../src/ext/lang_cpp.c ../../src/error.c ../../src/span.c */
|
|
#ifndef _POSIX_C_SOURCE
|
|
#define _POSIX_C_SOURCE 200809L /* mkdtemp/unsetenv/setenv (POSIX.1-2008) */
|
|
#endif
|
|
/* tests/unit/test_gen_configure.c
|
|
*
|
|
* Unit + integration tests for the ./configure generator (todo 16):
|
|
* src/gen/configure.c. THE MODEL: the generator EMITS a POSIX-sh script
|
|
* from a schema-validated DSL document + an extension ctx; the emitted
|
|
* script RUNS at configure time (probes + @VAR@ substitution). These tests
|
|
* prove the emitted script is syntactically valid (sh/bash/zsh -n, no
|
|
* banned constructs), REALLY runs (exit 0, Makefile written, CC/LIBS/
|
|
* prefix correct), honors --prefix, errors cleanly on an unknown @VAR@, a
|
|
* missing Makefile.in and an unrecognized option, carries values with
|
|
* '/', spaces and '&' byte-exact, keeps $(...) / backticks inert, and
|
|
* cross-compile-skips RUN probes.
|
|
*
|
|
* The magic LINK comment on line 1 is REQUIRED by tests/run.sh (extra .c
|
|
* sources, relative to tests/unit/). configure.c pulls in the whole detect
|
|
* chain (probe/checks/check_registry/resolve) + sh_emit + abi (the C/C++
|
|
* modules register CC/CFLAGS) + kdl (parse/lexer/value/schema) + error/span.
|
|
*/
|
|
#include "munit.h"
|
|
|
|
#include "detect/check_registry.h"
|
|
#include "error.h"
|
|
#include "ext/abi.h"
|
|
#include "gen/configure.h"
|
|
#include "kdl/ast.h"
|
|
#include "kdl/schema.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
/* ---- per-test temp dir ------------------------------------------------ */
|
|
|
|
static char temp_dir[128];
|
|
|
|
static void *
|
|
setup(const MunitParameter params[], void *user_data)
|
|
{
|
|
(void)params;
|
|
(void)user_data;
|
|
if (snprintf(temp_dir, sizeof temp_dir, "/tmp/st_gen_XXXXXX") < 0) {
|
|
return NULL;
|
|
}
|
|
if (mkdtemp(temp_dir) == NULL) {
|
|
return NULL;
|
|
}
|
|
return (void *)1;
|
|
}
|
|
|
|
static void
|
|
teardown(void *fixture)
|
|
{
|
|
char cmd[160];
|
|
|
|
if (fixture == NULL || temp_dir[0] == '\0') {
|
|
return;
|
|
}
|
|
if (snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", temp_dir) < 0) {
|
|
return;
|
|
}
|
|
(void)system(cmd); /* best-effort; mkdtemp names are [A-Za-z0-9_]* */
|
|
temp_dir[0] = '\0';
|
|
}
|
|
|
|
/* ---- small file/process helpers --------------------------------------- */
|
|
|
|
/* Join temp_dir/`name` into `buf`; returns the snprintf result. */
|
|
static int
|
|
mkpath(char *buf, size_t sz, const char *name)
|
|
{
|
|
return snprintf(buf, sz, "%s/%s", temp_dir, name);
|
|
}
|
|
|
|
/* Slurp a file into a NUL-terminated heap buffer, or NULL. Caller frees. */
|
|
static char *
|
|
slurp(const char *path)
|
|
{
|
|
FILE *f;
|
|
long n;
|
|
char *buf;
|
|
|
|
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 = malloc((size_t)n + 1);
|
|
if (buf == NULL) {
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
if (fread(buf, 1, (size_t)n, f) != (size_t)n) {
|
|
free(buf);
|
|
fclose(f);
|
|
return NULL;
|
|
}
|
|
fclose(f);
|
|
buf[n] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/* Slurp temp_dir/Makefile, or NULL. Caller frees. */
|
|
static char *
|
|
makefile_text(void)
|
|
{
|
|
char path[600];
|
|
|
|
if (mkpath(path, sizeof path, "Makefile") < 0) {
|
|
return NULL;
|
|
}
|
|
return slurp(path);
|
|
}
|
|
|
|
/* Slurp temp_dir/run.err, or NULL. Caller frees. */
|
|
static char *
|
|
stderr_text(void)
|
|
{
|
|
char path[600];
|
|
|
|
if (mkpath(path, sizeof path, "run.err") < 0) {
|
|
return NULL;
|
|
}
|
|
return slurp(path);
|
|
}
|
|
|
|
/* Write `content` to temp_dir/Makefile.in. Returns 0 on success. */
|
|
static int
|
|
write_makefile_in(const char *content)
|
|
{
|
|
char path[600];
|
|
FILE *f;
|
|
|
|
if (mkpath(path, sizeof path, "Makefile.in") < 0) {
|
|
return -1;
|
|
}
|
|
f = fopen(path, "w");
|
|
if (f == NULL) {
|
|
return -1;
|
|
}
|
|
if (fputs(content, f) == EOF) {
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
/* Run "sh ./configure <args>" with cwd = temp_dir, stderr to run.err.
|
|
* Returns the configure exit status, or -1 if it could not be spawned. */
|
|
static int
|
|
run_configure(const char *args)
|
|
{
|
|
char cmd[2400];
|
|
int n;
|
|
int spawned;
|
|
|
|
n = snprintf(cmd, sizeof cmd, "cd '%s' && sh ./configure %s 2>'%s/run.err'",
|
|
temp_dir, args != NULL ? args : "", temp_dir);
|
|
if (n < 0 || (size_t)n >= sizeof cmd) {
|
|
return -1;
|
|
}
|
|
spawned = system(cmd);
|
|
if (spawned == -1) {
|
|
return -1;
|
|
}
|
|
return WEXITSTATUS(spawned);
|
|
}
|
|
|
|
/* Run "cd temp_dir && <env>sh ./configure <args>" with stdout captured to
|
|
* run.out and stderr to run.err. `env` is a prefix like "CC=clang " (the
|
|
* exported VAR=VALUE form) or NULL/"" for none. Returns the exit status,
|
|
* or -1 if configure could not be spawned. */
|
|
static int
|
|
run_configure_full(const char *env, const char *args)
|
|
{
|
|
char cmd[2400];
|
|
int n;
|
|
int spawned;
|
|
|
|
n = snprintf(cmd, sizeof cmd,
|
|
"cd '%s' && %ssh ./configure %s >'%s/run.out' 2>'%s/run.err'",
|
|
temp_dir, env != NULL ? env : "", args != NULL ? args : "",
|
|
temp_dir, temp_dir);
|
|
if (n < 0 || (size_t)n >= sizeof cmd) {
|
|
return -1;
|
|
}
|
|
spawned = system(cmd);
|
|
if (spawned == -1) {
|
|
return -1;
|
|
}
|
|
return WEXITSTATUS(spawned);
|
|
}
|
|
|
|
/* "<shell> -n <path>": the shell's exit status or -1. */
|
|
static int
|
|
syntax_check(const char *shell, const char *path)
|
|
{
|
|
char cmd[700];
|
|
int n;
|
|
int spawned;
|
|
|
|
n = snprintf(cmd, sizeof cmd, "%s -n '%s'", shell, path);
|
|
if (n < 0 || (size_t)n >= sizeof cmd) {
|
|
return -1;
|
|
}
|
|
spawned = system(cmd);
|
|
if (spawned == -1) {
|
|
return -1;
|
|
}
|
|
return WEXITSTATUS(spawned);
|
|
}
|
|
|
|
/* Run "./config.status <args>" with cwd = temp_dir; stdout to status.out,
|
|
* stderr to status.err. Returns the exit status, or -1 if not spawned. */
|
|
static int
|
|
run_status(const char *args)
|
|
{
|
|
char cmd[2400];
|
|
int n;
|
|
int spawned;
|
|
|
|
n = snprintf(cmd, sizeof cmd,
|
|
"cd '%s' && ./config.status %s >'%s/status.out' "
|
|
"2>'%s/status.err'",
|
|
temp_dir, args != NULL ? args : "", temp_dir, temp_dir);
|
|
if (n < 0 || (size_t)n >= sizeof cmd) {
|
|
return -1;
|
|
}
|
|
spawned = system(cmd);
|
|
if (spawned == -1) {
|
|
return -1;
|
|
}
|
|
return WEXITSTATUS(spawned);
|
|
}
|
|
|
|
/* Slurp temp_dir/<name>, or NULL. Caller frees. */
|
|
static char *
|
|
slurp_named(const char *name)
|
|
{
|
|
char path[600];
|
|
|
|
if (mkpath(path, sizeof path, name) < 0) {
|
|
return NULL;
|
|
}
|
|
return slurp(path);
|
|
}
|
|
|
|
/* Slurp temp_dir/run.out (stdout of the last run_configure_full), or
|
|
* NULL. Caller frees. */
|
|
static char *
|
|
stdout_text(void)
|
|
{
|
|
return slurp_named("run.out");
|
|
}
|
|
|
|
/* Remove temp_dir/<name> (a missing file is not an error: the stale_state
|
|
* checks remove outputs that may or may not exist). Returns 0 on success. */
|
|
static int
|
|
remove_named(const char *name)
|
|
{
|
|
char path[600];
|
|
|
|
if (mkpath(path, sizeof path, name) < 0) {
|
|
return -1;
|
|
}
|
|
(void)remove(path);
|
|
return 0;
|
|
}
|
|
|
|
/* Parse + validate + emit an INLINE document into temp_dir/configure. */
|
|
static int
|
|
emit_inline_configure(struct st_ext_ctx *ctx, const char *docsrc)
|
|
{
|
|
struct st_kdl_document *doc = NULL;
|
|
struct st_error *err = NULL;
|
|
char conf[600];
|
|
|
|
doc = st_kdl_parse(docsrc, "inline.kdl", &err);
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
return -1;
|
|
}
|
|
if (doc == NULL) {
|
|
return -1;
|
|
}
|
|
err = st_kdl_validate(doc);
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
st_kdl_document_free(doc);
|
|
return -1;
|
|
}
|
|
if (mkpath(conf, sizeof conf, "configure") < 0) {
|
|
st_kdl_document_free(doc);
|
|
return -1;
|
|
}
|
|
err = st_gen_configure_emit_path(conf, doc, ctx);
|
|
st_kdl_document_free(doc);
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ---- fixture loading (same as test_schema.c) -------------------------- */
|
|
|
|
static const char *
|
|
fixture_path(void)
|
|
{
|
|
static const char *const candidates[] = {
|
|
"tests/fixtures/stupid.kdl",
|
|
"../fixtures/stupid.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;
|
|
}
|
|
|
|
/* Build a ctx with the builtins + C detected (env CC/CFLAGS/etc. reset).
|
|
* Returns NULL on any failure. */
|
|
static struct st_ext_ctx *
|
|
build_ctx(void)
|
|
{
|
|
struct st_ext_ctx *ctx = st_ext_ctx_new();
|
|
struct st_error *err;
|
|
|
|
if (ctx == NULL) {
|
|
return NULL;
|
|
}
|
|
unsetenv("CC");
|
|
unsetenv("CFLAGS");
|
|
unsetenv("CXX");
|
|
unsetenv("CXXFLAGS");
|
|
err = st_ext_init_builtins(ctx);
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
st_ext_ctx_free(ctx);
|
|
return NULL;
|
|
}
|
|
err = st_ext_detect_language(ctx, "c");
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
st_ext_ctx_free(ctx);
|
|
return NULL;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
/* Parse + validate + emit the fixture into temp_dir/configure, using
|
|
* `ctx`. Returns 0 on success, -1 on failure. */
|
|
static int
|
|
emit_fixture_configure(struct st_ext_ctx *ctx)
|
|
{
|
|
const char *path = fixture_path();
|
|
char conf[600];
|
|
char *src;
|
|
struct st_kdl_document *doc = NULL;
|
|
struct st_error *err = NULL;
|
|
|
|
if (path == NULL || ctx == NULL) {
|
|
return -1;
|
|
}
|
|
src = slurp(path);
|
|
if (src == NULL) {
|
|
return -1;
|
|
}
|
|
doc = st_kdl_parse(src, path, &err);
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
free(src);
|
|
return -1;
|
|
}
|
|
if (doc == NULL) {
|
|
free(src);
|
|
return -1;
|
|
}
|
|
err = st_kdl_validate(doc);
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
st_kdl_document_free(doc);
|
|
free(src);
|
|
return -1;
|
|
}
|
|
if (mkpath(conf, sizeof conf, "configure") < 0) {
|
|
st_kdl_document_free(doc);
|
|
free(src);
|
|
return -1;
|
|
}
|
|
err = st_gen_configure_emit_path(conf, doc, ctx);
|
|
/* the document borrows `src`'s token slices, so free src only after
|
|
* the document is released */
|
|
st_kdl_document_free(doc);
|
|
free(src);
|
|
if (err != NULL) {
|
|
st_error_free(err);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* The standard Makefile.in every end-to-end case uses. */
|
|
#define STD_MAKEFILE_IN \
|
|
"CC = @CC@\n" \
|
|
"LIBS = @LIBS@\n" \
|
|
"prefix = @prefix@\n" \
|
|
"FLAGS = @CFLAGS@\n"
|
|
|
|
/* ---- (a) the emitted configure passes the three-shell syntax oracle --- */
|
|
|
|
static MunitResult
|
|
test_syntax(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
static const char *const banned[] = {
|
|
"[[ ", "]]", "local ", "==", "<<<", "&>", "set -e",
|
|
};
|
|
struct st_ext_ctx *ctx;
|
|
char conf[600];
|
|
char *bytes;
|
|
size_t i;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(mkpath(conf, sizeof conf, "configure"), >, 0);
|
|
munit_assert_int(syntax_check("sh", conf), ==, 0);
|
|
munit_assert_int(syntax_check("bash", conf), ==, 0);
|
|
munit_assert_int(syntax_check("zsh", conf), ==, 0);
|
|
|
|
bytes = slurp(conf);
|
|
munit_assert_not_null(bytes);
|
|
for (i = 0; i < sizeof(banned) / sizeof(banned[0]); i++) {
|
|
munit_assert_null(strstr(bytes, banned[i]));
|
|
}
|
|
/* the preamble is structurally marked for todo 18 */
|
|
munit_assert_not_null(strstr(bytes, "# --- PREAMBLE ---"));
|
|
free(bytes);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- (b) real end-to-end run ------------------------------------------ */
|
|
|
|
static MunitResult
|
|
test_end_to_end(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
const char *cc;
|
|
char cc_buf[64];
|
|
char want[128];
|
|
char *mk;
|
|
int cc_len;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
cc = st_registry_get_var(st_ext_var_registry(ctx), "CC");
|
|
munit_assert_not_null(cc);
|
|
/* copy before st_ext_ctx_free() — the registry value is borrowed */
|
|
cc_len = snprintf(cc_buf, sizeof cc_buf, "%s", cc);
|
|
munit_assert_int(cc_len, >, 0);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
|
munit_assert_int(run_configure(NULL), ==, 0);
|
|
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
/* CC substituted from the registry value */
|
|
{
|
|
int n = snprintf(want, sizeof want, "CC = %s", cc_buf);
|
|
|
|
munit_assert_int(n, >, 0);
|
|
munit_assert_not_null(strstr(mk, want));
|
|
}
|
|
/* LIBS accumulated from library checks (pthread + m) */
|
|
munit_assert_not_null(strstr(mk, "-lpthread"));
|
|
munit_assert_not_null(strstr(mk, "-lm"));
|
|
/* prefix defaults to /usr/local */
|
|
munit_assert_not_null(strstr(mk, "prefix = /usr/local"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- (c) --prefix is honored ------------------------------------------ */
|
|
|
|
static MunitResult
|
|
test_prefix(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
|
munit_assert_int(run_configure("--prefix=/opt/x"), ==, 0);
|
|
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "prefix = /opt/x"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- (d) an unknown @VAR@ errors naming the variable ------------------ */
|
|
|
|
static MunitResult
|
|
test_unknown_var(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *err;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in("CC = @NOPE@\n"), ==, 0);
|
|
munit_assert_int(run_configure(NULL), !=, 0);
|
|
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "NOPE"));
|
|
free(err);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- (e) a missing Makefile.in is a clean error ----------------------- */
|
|
|
|
static MunitResult
|
|
test_missing_makefile(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *err;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
/* no Makefile.in written at all */
|
|
munit_assert_int(run_configure(NULL), !=, 0);
|
|
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "Makefile.in"));
|
|
free(err);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- (f) '/', spaces and '&' survive byte-exact ----------------------- */
|
|
|
|
static MunitResult
|
|
test_bytes_exact(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
/* spaces via CFLAGS; '&' and '/' via --prefix */
|
|
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
|
munit_assert_int(setenv("CFLAGS", "a b c", 1), ==, 0);
|
|
munit_assert_int(run_configure("--prefix='/opt/x & y/z'"), ==, 0);
|
|
unsetenv("CFLAGS");
|
|
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "FLAGS = a b c"));
|
|
munit_assert_not_null(strstr(mk, "prefix = /opt/x & y/z"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- unrecognized --flag is a clean error ----------------------------- */
|
|
|
|
static MunitResult
|
|
test_unrecognized_option(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *err;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(run_configure("--bogus"), !=, 0);
|
|
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "unrecognized option"));
|
|
free(err);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- injection: a $(...) VALUE stays inert (not executed) ------------- */
|
|
|
|
static MunitResult
|
|
test_injection_value(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char marker[600];
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(mkpath(marker, sizeof marker, "pwned1"), >, 0);
|
|
|
|
/* prefix gets a command-substitution payload; it must be a literal. */
|
|
munit_assert_int(write_makefile_in("prefix = @prefix@\n"), ==, 0);
|
|
munit_assert_int(run_configure("--prefix='$(touch pwned1)'"), ==, 0);
|
|
|
|
munit_assert_int(access(marker, F_OK), ==, -1);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "prefix = $(touch pwned1)"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- injection: an unknown @$(...)@ name stays inert (not executed) --- */
|
|
|
|
static MunitResult
|
|
test_injection_unknown_name(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char marker[600];
|
|
char *err;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(mkpath(marker, sizeof marker, "pwned2"), >, 0);
|
|
|
|
munit_assert_int(write_makefile_in("X = @$(touch pwned2)@\n"), ==, 0);
|
|
munit_assert_int(run_configure(NULL), !=, 0);
|
|
|
|
munit_assert_int(access(marker, F_OK), ==, -1);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "unknown variable"));
|
|
free(err);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- cross-compile: RUN probes skip with a warning -------------------- */
|
|
|
|
static MunitResult
|
|
test_cross_compile(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
static const char *const docsrc =
|
|
"project \"x\" version \"1.0\"\n"
|
|
"feature \"sz\" { sizeof \"long\" }\n";
|
|
struct st_ext_ctx *ctx;
|
|
struct st_kdl_document *doc = NULL;
|
|
struct st_error *err = NULL;
|
|
char conf[600];
|
|
char *e;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
|
|
doc = st_kdl_parse(docsrc, "inline.kdl", &err);
|
|
munit_assert_null(err);
|
|
munit_assert_not_null(doc);
|
|
munit_assert_null(st_kdl_validate(doc));
|
|
munit_assert_int(mkpath(conf, sizeof conf, "configure"), >, 0);
|
|
munit_assert_null(st_gen_configure_emit_path(conf, doc, ctx));
|
|
st_kdl_document_free(doc);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in("CC = @CC@\n"), ==, 0);
|
|
munit_assert_int(run_configure("--host=aarch64-linux --build=x86_64-linux"),
|
|
==, 0);
|
|
|
|
e = stderr_text();
|
|
munit_assert_not_null(e);
|
|
munit_assert_not_null(strstr(e, "cross-compiling"));
|
|
free(e);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 17 (a): the three new sections stay shell-safe ------------- */
|
|
|
|
static MunitResult
|
|
test_config_sections_syntax(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char conf[600];
|
|
char *bytes;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(mkpath(conf, sizeof conf, "configure"), >, 0);
|
|
munit_assert_int(syntax_check("sh", conf), ==, 0);
|
|
munit_assert_int(syntax_check("bash", conf), ==, 0);
|
|
munit_assert_int(syntax_check("zsh", conf), ==, 0);
|
|
|
|
bytes = slurp(conf);
|
|
munit_assert_not_null(bytes);
|
|
/* the three sections are structurally marked (--- not ===, per the
|
|
* F3 banned-construct grep) */
|
|
munit_assert_not_null(strstr(bytes, "# --- config.log:"));
|
|
munit_assert_not_null(strstr(bytes, "# --- config.h ---"));
|
|
munit_assert_not_null(strstr(bytes, "# --- config.status ---"));
|
|
munit_assert_not_null(strstr(bytes, "'## results'"));
|
|
free(bytes);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 17 (b): a real run writes config.h/config.log/config.status -- */
|
|
|
|
static MunitResult
|
|
test_config_artifacts(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char status[600];
|
|
char *ch;
|
|
char *cl;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
|
munit_assert_int(run_configure(NULL), ==, 0);
|
|
|
|
/* config.h: the ST_CONFIG_H guard + one define per resolved feature
|
|
* (content asserted, not mere existence) */
|
|
ch = slurp_named("config.h");
|
|
munit_assert_not_null(ch);
|
|
munit_assert_not_null(strstr(ch, "#ifndef ST_CONFIG_H"));
|
|
munit_assert_not_null(strstr(ch, "#define ST_CONFIG_H"));
|
|
munit_assert_not_null(strstr(ch, "#define HAVE_PTHREAD 1"));
|
|
munit_assert_not_null(strstr(ch, "#define HAVE_MATH 1"));
|
|
free(ch);
|
|
|
|
/* config.log: the transcript header + the ## results summary */
|
|
cl = slurp_named("config.log");
|
|
munit_assert_not_null(cl);
|
|
munit_assert_not_null(
|
|
strstr(cl, "It was created by stupidtools configure"));
|
|
munit_assert_not_null(strstr(cl, "## results"));
|
|
munit_assert_not_null(strstr(cl, "have_pthread=yes"));
|
|
munit_assert_not_null(strstr(cl, "have_math=yes"));
|
|
free(cl);
|
|
|
|
/* config.status: exists, is executable, and is valid POSIX-sh */
|
|
munit_assert_int(mkpath(status, sizeof status, "config.status"), >, 0);
|
|
munit_assert_int(access(status, F_OK), ==, 0);
|
|
munit_assert_int(access(status, X_OK), ==, 0);
|
|
munit_assert_int(syntax_check("sh", status), ==, 0);
|
|
munit_assert_int(syntax_check("bash", status), ==, 0);
|
|
munit_assert_int(syntax_check("zsh", status), ==, 0);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 17 (c): config.status --config prints the original args ----- */
|
|
|
|
static MunitResult
|
|
test_config_status_config(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *out;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
|
munit_assert_int(run_configure("--prefix=/opt/x"), ==, 0);
|
|
munit_assert_int(run_status("--config"), ==, 0);
|
|
|
|
out = slurp_named("status.out");
|
|
munit_assert_not_null(out);
|
|
munit_assert_string_equal(out, "--prefix=/opt/x\n");
|
|
free(out);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 17 (d): config.status --recheck re-runs configure ----------- */
|
|
|
|
static MunitResult
|
|
test_config_status_recheck(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *mk;
|
|
char *ch;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
|
munit_assert_int(run_configure("--prefix=/opt/x"), ==, 0);
|
|
|
|
/* stale_state: destroy the outputs, then --recheck regenerates them
|
|
* (observable regeneration, not a cached no-op) */
|
|
munit_assert_int(remove_named("Makefile"), ==, 0);
|
|
munit_assert_int(remove_named("config.h"), ==, 0);
|
|
munit_assert_int(run_status("--recheck"), ==, 0);
|
|
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "prefix = /opt/x"));
|
|
free(mk);
|
|
|
|
ch = slurp_named("config.h");
|
|
munit_assert_not_null(ch);
|
|
munit_assert_not_null(strstr(ch, "#define HAVE_PTHREAD 1"));
|
|
free(ch);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 17 (e): a failed feature gets no HAVE_ define --------------- */
|
|
|
|
static MunitResult
|
|
test_config_failed_feature(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
static const char *const docsrc =
|
|
"project \"x\" version \"1.0\"\n"
|
|
"feature \"nope\" { header \"nope_missing_xyz.h\" }\n"
|
|
"feature \"ok\" { header \"unistd.h\" }\n";
|
|
struct st_ext_ctx *ctx;
|
|
char *ch;
|
|
char *cl;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_inline_configure(ctx, docsrc), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in("CC = @CC@\n"), ==, 0);
|
|
munit_assert_int(run_configure(NULL), ==, 0);
|
|
|
|
ch = slurp_named("config.h");
|
|
munit_assert_not_null(ch);
|
|
munit_assert_null(strstr(ch, "HAVE_NOPE"));
|
|
munit_assert_not_null(strstr(ch, "#define HAVE_OK 1"));
|
|
free(ch);
|
|
|
|
cl = slurp_named("config.log");
|
|
munit_assert_not_null(cl);
|
|
munit_assert_not_null(strstr(cl, "have_nope=no"));
|
|
munit_assert_not_null(strstr(cl, "have_ok=yes"));
|
|
free(cl);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 17 (f): injected values never execute ----------------------- */
|
|
|
|
static MunitResult
|
|
test_config_status_injection(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char pwned1[600];
|
|
char pwned2[600];
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(mkpath(pwned1, sizeof pwned1, "pwned1"), >, 0);
|
|
munit_assert_int(mkpath(pwned2, sizeof pwned2, "pwned2"), >, 0);
|
|
munit_assert_int(write_makefile_in("prefix = @prefix@\n"), ==, 0);
|
|
|
|
/* The payloads use ${IFS} instead of a literal space: they contain no
|
|
* whitespace, so they survive the (documented, v1) unquoted recheck
|
|
* expansion as one word - yet they would create the marker files if
|
|
* any shell ever executed them. */
|
|
munit_assert_int(run_configure("--prefix='$(touch${IFS}pwned1)'"), ==, 0);
|
|
munit_assert_int(access(pwned1, F_OK), ==, -1);
|
|
munit_assert_int(run_status("--config"), ==, 0);
|
|
munit_assert_int(access(pwned1, F_OK), ==, -1);
|
|
munit_assert_int(run_status("--recheck"), ==, 0);
|
|
munit_assert_int(access(pwned1, F_OK), ==, -1);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "prefix = $(touch${IFS}pwned1)"));
|
|
free(mk);
|
|
|
|
/* payload 2: backticks - same guarantee, via a fresh configure run */
|
|
munit_assert_int(run_configure("--prefix='`touch${IFS}pwned2`'"), ==, 0);
|
|
munit_assert_int(access(pwned2, F_OK), ==, -1);
|
|
munit_assert_int(run_status("--config"), ==, 0);
|
|
munit_assert_int(access(pwned2, F_OK), ==, -1);
|
|
munit_assert_int(run_status("--recheck"), ==, 0);
|
|
munit_assert_int(access(pwned2, F_OK), ==, -1);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "prefix = `touch${IFS}pwned2`"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 17 (g): config.status --help and unknown-option handling ---- */
|
|
|
|
static MunitResult
|
|
test_config_status_help(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *out;
|
|
char *e;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in(STD_MAKEFILE_IN), ==, 0);
|
|
munit_assert_int(run_configure(NULL), ==, 0);
|
|
|
|
munit_assert_int(run_status("--help"), ==, 0);
|
|
out = slurp_named("status.out");
|
|
munit_assert_not_null(out);
|
|
munit_assert_not_null(strstr(out, "Usage"));
|
|
free(out);
|
|
|
|
munit_assert_int(run_status("--bogus"), !=, 0);
|
|
e = slurp_named("status.err");
|
|
munit_assert_not_null(e);
|
|
munit_assert_not_null(strstr(e, "unrecognized option"));
|
|
free(e);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 18 (a): the full preamble stays shell-safe + carries the
|
|
* parser machinery ------------------------------------------------------ */
|
|
|
|
static MunitResult
|
|
test_args_syntax(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char conf[600];
|
|
char *bytes;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(mkpath(conf, sizeof conf, "configure"), >, 0);
|
|
munit_assert_int(syntax_check("sh", conf), ==, 0);
|
|
munit_assert_int(syntax_check("bash", conf), ==, 0);
|
|
munit_assert_int(syntax_check("zsh", conf), ==, 0);
|
|
|
|
bytes = slurp(conf);
|
|
munit_assert_not_null(bytes);
|
|
munit_assert_not_null(strstr(bytes, "# --- PREAMBLE ---"));
|
|
munit_assert_not_null(strstr(bytes, "st_usage()"));
|
|
/* the fixture's option "debug" -> enable/disable arms + default */
|
|
munit_assert_not_null(strstr(bytes, "--enable-debug) enable_debug=yes"));
|
|
munit_assert_not_null(strstr(bytes, "--disable-debug) enable_debug=no"));
|
|
munit_assert_not_null(strstr(bytes, "enable_debug=no"));
|
|
/* VAR=VALUE parser machinery (identifier check + quoted eval) */
|
|
munit_assert_not_null(strstr(bytes, "st_val_q="));
|
|
munit_assert_not_null(strstr(bytes, "st_var=${st_arg%%=*}"));
|
|
free(bytes);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 18 (b): --help lists the standard + per-option flags -------- */
|
|
|
|
static MunitResult
|
|
test_args_help(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *out;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
/* help exits 0 BEFORE probing/substitution (no Makefile.in needed) */
|
|
munit_assert_int(run_configure_full(NULL, "--help"), ==, 0);
|
|
out = stdout_text();
|
|
munit_assert_not_null(out);
|
|
munit_assert_not_null(strstr(out, "Usage:"));
|
|
munit_assert_not_null(strstr(out, "--prefix"));
|
|
munit_assert_not_null(strstr(out, "--exec-prefix"));
|
|
munit_assert_not_null(strstr(out, "--host"));
|
|
munit_assert_not_null(strstr(out, "--build"));
|
|
munit_assert_not_null(strstr(out, "VAR=VALUE"));
|
|
munit_assert_not_null(strstr(out, "--enable-debug"));
|
|
munit_assert_not_null(strstr(out, "--disable-debug"));
|
|
munit_assert_not_null(strstr(out, "--version"));
|
|
free(out);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 18 (g): --version prints name + version, exits 0 ------------ */
|
|
|
|
static MunitResult
|
|
test_args_version(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *out;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--version"), ==, 0);
|
|
out = stdout_text();
|
|
munit_assert_not_null(out);
|
|
munit_assert_not_null(strstr(out, "stupidtools"));
|
|
munit_assert_not_null(strstr(out, "1.0.0"));
|
|
free(out);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 18 (c): CC=clang as positional AND as env override ---------- */
|
|
|
|
static MunitResult
|
|
test_args_cc_override(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
/* positional form: ./configure CC=clang */
|
|
munit_assert_int(write_makefile_in("CC = @CC@\n"), ==, 0);
|
|
munit_assert_int(run_configure_full(NULL, "CC=clang"), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "CC = clang"));
|
|
free(mk);
|
|
|
|
/* exported form: CC=clang ./configure */
|
|
munit_assert_int(run_configure_full("CC=clang ", NULL), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "CC = clang"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 18 (d): --enable-debug / --disable-debug / default ---------- */
|
|
|
|
static MunitResult
|
|
test_args_enable_debug(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in("DEBUG = @enable_debug@\n"), ==, 0);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--enable-debug"), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "DEBUG = yes"));
|
|
free(mk);
|
|
|
|
/* the fixture option has default=#false, so no flag -> no */
|
|
munit_assert_int(run_configure_full(NULL, NULL), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "DEBUG = no"));
|
|
free(mk);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--disable-debug"), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "DEBUG = no"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 18 (e): --host != --build -> cross_compiling=yes ------------ */
|
|
|
|
static MunitResult
|
|
test_args_cross_compiling(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in("CROSS = @cross_compiling@\n"
|
|
"HOST = @host@\n"
|
|
"BUILD = @build@\n"), ==, 0);
|
|
munit_assert_int(run_configure_full(NULL,
|
|
"--host=aarch64-linux-gnu "
|
|
"--build=x86_64-linux-gnu"), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "CROSS = yes"));
|
|
munit_assert_not_null(strstr(mk, "HOST = aarch64-linux-gnu"));
|
|
munit_assert_not_null(strstr(mk, "BUILD = x86_64-linux-gnu"));
|
|
free(mk);
|
|
|
|
munit_assert_int(run_configure_full(NULL, NULL), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "CROSS = no"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- todo 18 (f): unknown --* -> exit 1 + "unrecognized option" ------- */
|
|
|
|
static MunitResult
|
|
test_args_bogus(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *err;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--bogus"), !=, 0);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "unrecognized option"));
|
|
munit_assert_not_null(strstr(err, "Usage:")); /* usage goes to stderr */
|
|
free(err);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--bogus=1"), !=, 0);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "unrecognized option"));
|
|
free(err);
|
|
|
|
/* --enable- with no name falls through to the unknown-option error */
|
|
munit_assert_int(run_configure_full(NULL, "--enable-"), !=, 0);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "unrecognized option"));
|
|
free(err);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- malformed values: missing/empty/option-looking --prefix ---------- */
|
|
|
|
static MunitResult
|
|
test_args_malformed(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *err;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
/* missing value (space form, end of argv) */
|
|
munit_assert_int(run_configure_full(NULL, "--prefix"), !=, 0);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "requires an argument"));
|
|
free(err);
|
|
|
|
/* empty value */
|
|
munit_assert_int(run_configure_full(NULL, "--prefix="), !=, 0);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "non-empty argument"));
|
|
free(err);
|
|
|
|
/* an option-looking value is rejected */
|
|
munit_assert_int(run_configure_full(NULL, "--prefix --bogus"), !=, 0);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "requires an argument"));
|
|
free(err);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- --prefix <dir> (space form) and --exec-prefix -------------------- */
|
|
|
|
static MunitResult
|
|
test_args_prefix_space(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char *mk;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(write_makefile_in("prefix = @prefix@\n"
|
|
"EP = @exec_prefix@\n"), ==, 0);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--prefix /opt/space"), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "prefix = /opt/space"));
|
|
/* exec_prefix defaults to prefix when not given */
|
|
munit_assert_not_null(strstr(mk, "EP = /opt/space"));
|
|
free(mk);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--prefix '/opt/x y'"), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "prefix = /opt/x y"));
|
|
free(mk);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "--exec-prefix=/opt/ep"), ==, 0);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "EP = /opt/ep"));
|
|
munit_assert_not_null(strstr(mk, "prefix = /usr/local"));
|
|
free(mk);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- positional VAR=VALUE injection stays inert ----------------------- */
|
|
|
|
static MunitResult
|
|
test_args_injection(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
struct st_ext_ctx *ctx;
|
|
char pwned3[600];
|
|
char pwned4[600];
|
|
char *mk;
|
|
char *err;
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
munit_assert_int(emit_fixture_configure(ctx), ==, 0);
|
|
st_ext_ctx_free(ctx);
|
|
|
|
munit_assert_int(mkpath(pwned3, sizeof pwned3, "pwned3"), >, 0);
|
|
munit_assert_int(mkpath(pwned4, sizeof pwned4, "pwned4"), >, 0);
|
|
munit_assert_int(write_makefile_in("FLAGS = @CFLAGS@\n"), ==, 0);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "'CFLAGS=$(touch pwned3)'"),
|
|
==, 0);
|
|
munit_assert_int(access(pwned3, F_OK), ==, -1);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "FLAGS = $(touch pwned3)"));
|
|
free(mk);
|
|
|
|
munit_assert_int(run_configure_full(NULL, "'CFLAGS=`touch pwned4`'"),
|
|
==, 0);
|
|
munit_assert_int(access(pwned4, F_OK), ==, -1);
|
|
mk = makefile_text();
|
|
munit_assert_not_null(mk);
|
|
munit_assert_not_null(strstr(mk, "FLAGS = `touch pwned4`"));
|
|
free(mk);
|
|
|
|
/* a non-identifier VAR name is a clean error */
|
|
munit_assert_int(run_configure_full(NULL, "B-AD=1"), !=, 0);
|
|
err = stderr_text();
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(err, "invalid variable assignment"));
|
|
free(err);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
/* ---- a hostile option NAME errors at GENERATION time ------------------ */
|
|
|
|
static MunitResult
|
|
test_args_option_name(const MunitParameter params[], void *data)
|
|
{
|
|
(void)params;
|
|
(void)data;
|
|
static const char *const docsrc =
|
|
"project \"x\" version \"1.0\"\n"
|
|
"option \"x; rm -rf /\" default=#false\n"
|
|
"feature \"f\" { header \"unistd.h\" }\n";
|
|
struct st_ext_ctx *ctx;
|
|
struct st_kdl_document *doc = NULL;
|
|
struct st_error *err = NULL;
|
|
char conf[600];
|
|
|
|
ctx = build_ctx();
|
|
munit_assert_not_null(ctx);
|
|
|
|
doc = st_kdl_parse(docsrc, "inline.kdl", &err);
|
|
munit_assert_null(err);
|
|
munit_assert_not_null(doc);
|
|
munit_assert_null(st_kdl_validate(doc)); /* schema-valid, hostile name */
|
|
munit_assert_int(mkpath(conf, sizeof conf, "configure"), >, 0);
|
|
err = st_gen_configure_emit_path(conf, doc, ctx);
|
|
munit_assert_not_null(err);
|
|
munit_assert_not_null(strstr(st_error_message(err),
|
|
"not a valid shell identifier"));
|
|
st_error_free(err);
|
|
err = NULL;
|
|
/* nothing was emitted: the truncated output is an empty file */
|
|
{
|
|
char *bytes = slurp(conf);
|
|
|
|
munit_assert_not_null(bytes);
|
|
munit_assert_string_equal(bytes, "");
|
|
free(bytes);
|
|
}
|
|
st_kdl_document_free(doc);
|
|
st_ext_ctx_free(ctx);
|
|
return MUNIT_OK;
|
|
}
|
|
|
|
static MunitTest tests[] = {
|
|
{ "/gen/syntax", test_syntax, setup, teardown, MUNIT_TEST_OPTION_NONE,
|
|
NULL },
|
|
{ "/gen/end-to-end", test_end_to_end, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/prefix", test_prefix, setup, teardown, MUNIT_TEST_OPTION_NONE,
|
|
NULL },
|
|
{ "/gen/unknown-var", test_unknown_var, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/missing-makefile", test_missing_makefile, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/bytes-exact", test_bytes_exact, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/unrecognized-option", test_unrecognized_option, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/injection-value", test_injection_value, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/injection-unknown-name", test_injection_unknown_name, setup,
|
|
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/cross-compile", test_cross_compile, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/config-sections-syntax", test_config_sections_syntax, setup,
|
|
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/config-artifacts", test_config_artifacts, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/config-status-config", test_config_status_config, setup,
|
|
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/config-status-recheck", test_config_status_recheck, setup,
|
|
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/config-failed-feature", test_config_failed_feature, setup,
|
|
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/config-status-injection", test_config_status_injection, setup,
|
|
teardown, MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/config-status-help", test_config_status_help, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/syntax", test_args_syntax, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/help", test_args_help, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/version", test_args_version, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/cc-override", test_args_cc_override, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/enable-debug", test_args_enable_debug, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/cross-compiling", test_args_cross_compiling, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/bogus", test_args_bogus, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/malformed", test_args_malformed, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/prefix-space", test_args_prefix_space, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/injection", test_args_injection, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ "/gen/args/option-name", test_args_option_name, setup, teardown,
|
|
MUNIT_TEST_OPTION_NONE, NULL },
|
|
{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
|
|
};
|
|
|
|
static const MunitSuite suite = {
|
|
"/gen", 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);
|
|
}
|