build: wire new src dirs, crt objects, and make check into Autotools

This commit is contained in:
2026-09-03 19:36:32 -04:00
parent 558cf8fd12
commit 40a392f454
8 changed files with 1889 additions and 9 deletions
+11
View File
@@ -107,3 +107,14 @@ Makefile.in
/benchmarks/bench_vlibc
/benchmarks/bench_string
# make check outputs: static test binaries + parallel-test logs (todo 6).
/test_*
/test-suite.log
/tests/startup_modes.sh.log
/tests/startup_modes.sh.trs
# make dist outputs.
/vlibc-*.tar.gz
/vlibc-*/
+225 -6
View File
@@ -2,23 +2,110 @@
SUBDIRS = benchmarks
AM_CPPFLAGS = -I$(top_srcdir)/include
# Build-dir include FIRST: configure generates include/vlibc/features.h
# (from the .in template) into the BUILD tree, and a VPATH build must find
# the generated header before the source tree's checked-in headers.
AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include
AM_CFLAGS = @VLIBC_CFLAGS@
# ---- Library -------------------------------------------------------------
vlibc_include_HEADERS = include/vlibc.h include/stddef.h include/string.h
# Authoritative source-dir inventory (todo 6). Every directory a later plan
# todo creates is enumerated here ONCE, so no area can be forgotten; each is
# appended to libvlibc_la_SOURCES WHEN ITS TODO LANDS — the list grows
# incrementally and never references a directory that does not exist yet:
#
# W1 (wired now): src/internal, src/start, src/errno,
# src/setjmp/x86_64, src/string, crt/ (crt1.o)
# todo 7 src/malloc todo 39-42 src/math todo 50 src/multibyte
# todo 11-14 src/stdlib todo 43 src/complex todo 53-54 src/regex
# todo 15-18 src/stdio todo 9,51 src/ctype todo 57-58 src/network
# todo 19,38 src/unistd todo 55 src/search todo 59 src/mq
# todo 20,23,56 src/process todo 66 src/crypt todo 60 src/aio
# todo 21 src/fcntl todo 44-48 src/thread todo 61 src/ipc
# todo 22,34 src/stat todo 49,52 src/locale todo 62-63 src/ldso
# todo 24 src/dirent todo 42 src/fenv/x86_64
# todo 25 src/select
# todo 26-27 src/time
# todo 28 src/signal
# todo 29 src/termios
# todo 30 src/uio
# todo 31 src/resource
# todo 32,36,54,61,67 src/misc
# todo 33 src/mman
# todo 35 src/sys
# todo 37 src/passwd
# todo 37 src/group
#
# NOTE: crt/x86_64/crt1.s is NOT a library source — it is assembled into the
# standalone crt1.o below (the sole crt owner; todo 64 reuses, never rebuilds).
# Level-independent L1 core (wired today + the pre-plan string slice).
VLIBC_CORE_SRCS = \
src/vlibc.c \
src/internal/assert_fail.c \
src/internal/errno.c \
src/internal/syscall_ret.c \
src/start/libc_start_main.c \
src/start/exit.c \
src/start/environ.c \
src/start/tls.c \
src/errno/strerror.c \
src/setjmp/x86_64/setjmp.s \
src/setjmp/x86_64/longjmp.s \
src/setjmp/x86_64/sigsetjmp.s \
src/setjmp/x86_64/siglongjmp.s \
src/string/strlen.c \
src/string/strcmp.c \
src/string/memcpy.c \
src/string/memmove.c \
src/string/memset.c
# Profile-gated sources, EXISTING gates preserved (strlcpy/strlcat stay L2
# BSD, strcasestr stays L3 GNU — NOT promoted).
VLIBC_LEVEL2_SRCS = src/string/strlcpy.c src/string/strlcat.c
VLIBC_LEVEL3_SRCS = src/string/strcasestr.c
vlibc_lib_LTLIBRARIES = libvlibc.la
libvlibc_la_SOURCES = src/vlibc.c src/string/strlen.c src/string/strcmp.c \
src/string/memcpy.c src/string/memmove.c src/string/memset.c
libvlibc_la_SOURCES = $(VLIBC_CORE_SRCS)
if PROFILE_GE_2
libvlibc_la_SOURCES += src/string/strlcpy.c src/string/strlcat.c
libvlibc_la_SOURCES += $(VLIBC_LEVEL2_SRCS)
endif
if PROFILE_GE_3
libvlibc_la_SOURCES += src/string/strcasestr.c
libvlibc_la_SOURCES += $(VLIBC_LEVEL3_SRCS)
endif
libvlibc_la_LDFLAGS = -version-info 0:0:0 -no-undefined
# ---- Public headers ------------------------------------------------------
# The whole public header inventory (the 3 scaffold headers plus todo 5's
# skeleton); the profile-gated generated features.h is installed separately
# below.
vlibc_include_HEADERS = \
include/vlibc.h \
include/stddef.h \
include/string.h \
include/errno.h \
include/setjmp.h \
include/assert.h \
include/cpio.h \
include/float.h \
include/iso646.h \
include/limits.h \
include/stdalign.h \
include/stdarg.h \
include/stdatomic.h \
include/stdbit.h \
include/stdbool.h \
include/stdckdint.h \
include/stdint.h \
include/stdnoreturn.h \
include/tar.h \
include/tgmath.h \
include/threads.h \
include/uchar.h \
include/sys/types.h
# Install location depends on the install method (see configure.ac).
if INSTALL_OVERWRITE
vlibc_includedir = $(includedir)
@@ -38,6 +125,17 @@ nodist_vlibc_features_HEADERS = include/vlibc/features.h
# ---- Compiler drivers ----------------------------------------------------
bin_SCRIPTS = tools/vlibc-gcc tools/vlibc-clang
# ---- crt objects ---------------------------------------------------------
# crt1.o (from crt/x86_64/crt1.s) is built here and ONLY here: todo 64
# reuses these objects for the shared build and must NOT rebuild them.
# Built by `make all` via all-local so a static vlibc link always has its
# entry object available next to the archive.
crt1.o: crt/x86_64/crt1.s
$(AM_V_GEN)$(CC) $(AM_CPPFLAGS) $(CPPFLAGS) -c -o $@ $<
all-local: crt1.o
# ---- Targets -------------------------------------------------------------
# layouts/C.md requires `make debug`, `make release`, `make bench` and
# `make clean`; build outputs land in bin/{release,debug}.
@@ -50,6 +148,7 @@ debug:
$(MKDIR_P) bin/debug
-cp -P .libs/libvlibc.so* bin/debug/
-cp .libs/libvlibc.a bin/debug/
-cp crt1.o bin/debug/
release:
$(MAKE) clean
@@ -57,6 +156,7 @@ release:
$(MKDIR_P) bin/release
-cp -P .libs/libvlibc.so* bin/release/
-cp .libs/libvlibc.a bin/release/
-cp crt1.o bin/release/
bench: all
$(MAKE) -C benchmarks bench
@@ -68,3 +168,122 @@ compile-commands: clean
bear --append -- $(MAKE) -C benchmarks bench_vlibc
.PHONY: debug release bench compile-commands
# ---- make check ----------------------------------------------------------
#
# Every test binary is a STATIC VLIBC EXECUTABLE: linked with -nostdlib
# -static -no-pie against crt1.o + a dedicated level-2 test archive. No
# glibc symbol can resolve, so each test genuinely proves self-hosting.
#
# The test archive (libvlibc-check.a) is compiled from the FULL source list
# (all levels included) at VLIBC_LEVEL=2 — the complete L1+L2 surface —
# INDEPENDENT of the configured profile. Rationale: some tests exercise
# L2-gated functions (test_strerror calls strerror_r, future tests call
# bcopy etc.), which a level-1 configured archive does not provide; the
# shipped libvlibc.a keeps the profile gating configured at build time.
# Object files are plain `ar` members compiled directly with $(CC) — no
# libtool (libtool's build-dir libvlibc.a is a wrapper script, and only
# .libs/libvlibc.a is linkable, which is a level-gated profile archive).
#
# Mechanics (extend here for future tests):
# - Add the binary to check_PROGRAMS + TESTS with
# `<bin>_SOURCES = tests/test_x.c`,
# `<bin>_CFLAGS = $(VLIBC_TEST_CFLAGS)` (renames the object to
# `<bin>-<src>.o` automatically),
# `<bin>_LINK = $(VLIBC_TEST_LINK)` and
# `<bin>_LDADD = $(VLIBC_TEST_LDADD)` (static vlibc link; the crt
# object + archive must be in LDADD so they follow the test object).
# - Multi-mode tests run through tests/startup_modes.sh (see that file).
VLIBC_TEST_LEVEL = 2
VLIBC_CHECK_SRCS = $(VLIBC_CORE_SRCS) $(VLIBC_LEVEL2_SRCS) \
$(VLIBC_LEVEL3_SRCS)
VLIBC_CHECK_OBJECTS_FROM_C = $(VLIBC_CHECK_SRCS:.c=.check.o)
VLIBC_CHECK_OBJECTS = $(VLIBC_CHECK_OBJECTS_FROM_C:.s=.check.o)
VLIBC_CHECK_CFLAGS = @VLIBC_CFLAGS@ -DVLIBC_LEVEL=$(VLIBC_TEST_LEVEL) -fno-pic
# The archive itself (plain ar: members compiled at the test level).
libvlibc-check.a: $(VLIBC_CHECK_OBJECTS)
$(AR) $(ARFLAGS) $@ $^
# Library sources compiled at the test level. Deliberately no
# -DHAVE_CONFIG_H and no $(DEFS): config.h pins the PROFILE level, which
# would override -DVLIBC_LEVEL (last definition wins), so the check objects
# must get their level from <vlibc/features.h> alone.
%.check.o: %.c
$(AM_V_CC)$(CC) $(AM_CPPFLAGS) $(CPPFLAGS) $(VLIBC_CHECK_CFLAGS) -c -o $@ $<
%.check.o: %.s
$(AM_V_CCAS)$(CC) $(AM_CPPFLAGS) $(CPPFLAGS) -c -o $@ $<
# Static vlibc link for every check program. automake links
# "$(<bin>_LINK) $(<bin>_OBJECTS) $(<bin>_LDADD) $(LIBS)", so the entry
# object, the level-2 archive and libgcc go in LDADD — they MUST follow the
# test object or the archive would see no undefined symbols and stay
# unextracted. -nostdlib means -lgcc is not auto-added, so it is spelled
# out (permitted libgcc helpers).
VLIBC_TEST_LINK = $(CC) -nostdlib -static -no-pie -o $@
VLIBC_TEST_LDADD = crt1.o libvlibc-check.a -lgcc
# Test TUs: no stack protector (the library is protector-free at this stage),
# non-PIC, level overridden to the test level so L2 declarations are visible.
VLIBC_TEST_CFLAGS = -DVLIBC_LEVEL=$(VLIBC_TEST_LEVEL) -fno-stack-protector \
-fno-pic -fno-pie
check_PROGRAMS = test_syscall test_strerror test_setjmp test_headers \
test_startup
test_syscall_SOURCES = tests/syscall_test.c
test_syscall_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_syscall_DEPENDENCIES = crt1.o libvlibc-check.a
test_syscall_LINK = $(VLIBC_TEST_LINK)
test_syscall_LDADD = $(VLIBC_TEST_LDADD)
test_strerror_SOURCES = tests/test_strerror.c
test_strerror_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_strerror_DEPENDENCIES = crt1.o libvlibc-check.a
test_strerror_LINK = $(VLIBC_TEST_LINK)
test_strerror_LDADD = $(VLIBC_TEST_LDADD)
test_setjmp_SOURCES = tests/test_setjmp.c
test_setjmp_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_setjmp_DEPENDENCIES = crt1.o libvlibc-check.a
test_setjmp_LINK = $(VLIBC_TEST_LINK)
test_setjmp_LDADD = $(VLIBC_TEST_LDADD)
test_headers_SOURCES = tests/test_headers.c
test_headers_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_headers_DEPENDENCIES = crt1.o libvlibc-check.a
test_headers_LINK = $(VLIBC_TEST_LINK)
test_headers_LDADD = $(VLIBC_TEST_LDADD)
test_startup_SOURCES = tests/test_startup.c
test_startup_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_startup_DEPENDENCIES = crt1.o libvlibc-check.a
test_startup_LINK = $(VLIBC_TEST_LINK)
test_startup_LDADD = $(VLIBC_TEST_LDADD)
# test_startup's default mode exits 42 by design, so it is NOT a bare TESTS
# entry; tests/startup_modes.sh runs its full mode matrix and the -f failure
# scenarios of the other four binaries.
TESTS = test_syscall test_strerror test_setjmp test_headers \
tests/startup_modes.sh
CLEANFILES = crt1.o libvlibc-check.a $(VLIBC_CHECK_OBJECTS)
# Distributed but not installed / not otherwise picked up by automake.
# (TESTS scripts are not auto-distributed, so the mode runner is explicit.)
EXTRA_DIST = \
crt/x86_64/crt1.s \
include/vlibc/internal/test.h \
tests/conformance/symbols.list \
tests/startup_modes.sh \
arch/x86_64/syscall_arch.h \
src/internal/atomic.h \
src/internal/errno.h \
src/internal/libc.h \
src/internal/syscall.h \
src/internal/types.h \
src/start/start.h \
src/start/tcb.h
+2 -1
View File
@@ -1,6 +1,7 @@
# vlibc — benchmark harnesses (musts/BENCHMARKING.md).
AM_CPPFLAGS = -I$(top_srcdir)/include
# Build-dir include FIRST (generated features.h lives there in VPATH builds).
AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include
AM_CFLAGS = @VLIBC_CFLAGS@
# Built only on demand (via `make bench`), so `make all` does not need the
Vendored
+160 -1
View File
@@ -686,6 +686,11 @@ PROFILE_GE_2_TRUE
vlibc_level
vlibc_profile
VLIBC_CFLAGS
am__fastdepCCAS_FALSE
am__fastdepCCAS_TRUE
CCASDEPMODE
CCASFLAGS
CCAS
am__fastdepCC_FALSE
am__fastdepCC_TRUE
CCDEPMODE
@@ -806,6 +811,8 @@ CFLAGS
LDFLAGS
LIBS
CPPFLAGS
CCAS
CCASFLAGS
LT_SYS_LIBRARY_PATH'
@@ -1482,6 +1489,8 @@ Some influential environment variables:
LIBS libraries to pass to the linker, e.g. -l<library>
CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
you have headers in a nonstandard directory <include dir>
CCAS assembler compiler command (defaults to CC)
CCASFLAGS assembler compiler flags (defaults to CFLAGS)
LT_SYS_LIBRARY_PATH
User-defined run-time library search path.
@@ -4931,6 +4940,143 @@ else
fi
# AM_PROG_AS: library sources include raw x86_64 assembly (the setjmp family
# in src/setjmp/x86_64/*.s), which automake compiles through CCAS/CCASFLAGS.
# By default we simply use the C compiler to build assembly code.
test "${CCAS+set}" = set || CCAS=$CC
test "${CCASFLAGS+set}" = set || CCASFLAGS=$CFLAGS
depcc="$CCAS" am_compiler_list=
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
printf %s "checking dependency style of $depcc... " >&6; }
if test ${am_cv_CCAS_dependencies_compiler_type+y}
then :
printf %s "(cached) " >&6
else case e in #(
e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
# We make a subdir and do the tests there. Otherwise we can end up
# making bogus files that we don't know about and never remove. For
# instance it was reported that on HP-UX the gcc test will end up
# making a dummy file named 'D' -- because '-MD' means "put the output
# in D".
rm -rf conftest.dir
mkdir conftest.dir
# Copy depcomp to subdir because otherwise we won't find it if we're
# using a relative directory.
cp "$am_depcomp" conftest.dir
cd conftest.dir
# We will build objects and dependencies in a subdirectory because
# it helps to detect inapplicable dependency modes. For instance
# both Tru64's cc and ICC support -MD to output dependencies as a
# side effect of compilation, but ICC will put the dependencies in
# the current directory while Tru64 will put them in the object
# directory.
mkdir sub
am_cv_CCAS_dependencies_compiler_type=none
if test "$am_compiler_list" = ""; then
am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
fi
am__universal=false
for depmode in $am_compiler_list; do
# Setup a source with many dependencies, because some compilers
# like to wrap large dependency lists on column 80 (with \), and
# we should not choose a depcomp mode which is confused by this.
#
# We need to recreate these files for each test, as the compiler may
# overwrite some of them when testing with obscure command lines.
# This happens at least with the AIX C compiler.
: > sub/conftest.c
for i in 1 2 3 4 5 6; do
echo '#include "conftst'$i'.h"' >> sub/conftest.c
# Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
# Solaris 10 /bin/sh.
echo '/* dummy */' > sub/conftst$i.h
done
echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
# We check with '-c' and '-o' for the sake of the "dashmstdout"
# mode. It turns out that the SunPro C++ compiler does not properly
# handle '-M -o', and we need to detect this. Also, some Intel
# versions had trouble with output in subdirs.
am__obj=sub/conftest.${OBJEXT-o}
am__minus_obj="-o $am__obj"
case $depmode in
gcc)
# This depmode causes a compiler race in universal mode.
test "$am__universal" = false || continue
;;
nosideeffect)
# After this tag, mechanisms are not by side-effect, so they'll
# only be used when explicitly requested.
if test "x$enable_dependency_tracking" = xyes; then
continue
else
break
fi
;;
msvc7 | msvc7msys | msvisualcpp | msvcmsys)
# This compiler won't grok '-c -o', but also, the minuso test has
# not run yet. These depmodes are late enough in the game, and
# so weak that their functioning should not be impacted.
am__obj=conftest.${OBJEXT-o}
am__minus_obj=
;;
none) break ;;
esac
if depmode=$depmode \
source=sub/conftest.c object=$am__obj \
depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
$SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
>/dev/null 2>conftest.err &&
grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
${MAKE-make} -s -f confmf > /dev/null 2>&1; then
# icc doesn't choke on unknown options, it will just issue warnings
# or remarks (even with -Werror). So we grep stderr for any message
# that says an option was ignored or not supported.
# When given -MP, icc 7.0 and 7.1 complain thus:
# icc: Command line warning: ignoring option '-M'; no argument required
# The diagnosis changed in icc 8.0:
# icc: Command line remark: option '-MP' not supported
if (grep 'ignoring option' conftest.err ||
grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
am_cv_CCAS_dependencies_compiler_type=$depmode
break
fi
fi
done
cd ..
rm -rf conftest.dir
else
am_cv_CCAS_dependencies_compiler_type=none
fi
;;
esac
fi
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_CCAS_dependencies_compiler_type" >&5
printf '%s\n' "$am_cv_CCAS_dependencies_compiler_type" >&6; }
CCASDEPMODE=depmode=$am_cv_CCAS_dependencies_compiler_type
if
test "x$enable_dependency_tracking" != xno \
&& test "$am_cv_CCAS_dependencies_compiler_type" = gcc3; then
am__fastdepCCAS_TRUE=
am__fastdepCCAS_FALSE='#'
else
am__fastdepCCAS_TRUE='#'
am__fastdepCCAS_FALSE=
fi
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler is GCC" >&5
printf %s "checking whether the compiler is GCC... " >&6; }
@@ -5186,7 +5332,16 @@ fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
CFLAGS=$vlibc_save_CFLAGS
VLIBC_CFLAGS="-std=$vlibc_cstd -Wall -Wextra"
# -fno-stack-protector: distro GCC builds enable -fstack-protector-strong by
# default, which would make library objects reference __stack_chk_fail — a
# symbol vlibc does not provide yet. Every make-check test binary and every
# -nostdlib -static consumer links against the plain archive, and one
# undefined __stack_chk_fail in it breaks the whole self-hosting link. A
# bootstrap libc must build its own objects without the protector (the stack
# protector runtime lands with the hardening work, after which this flag is
# revisited). Tests compile with the same flag (tests/Makefile.am wiring in
# Makefile.am).
VLIBC_CFLAGS="-std=$vlibc_cstd -Wall -Wextra -fno-stack-protector"
# ---- Compatibility profile ----------------------------------------------
@@ -14607,6 +14762,10 @@ if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
fi
if test -z "${am__fastdepCCAS_TRUE}" && test -z "${am__fastdepCCAS_FALSE}"; then
as_fn_error $? "conditional \"am__fastdepCCAS\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
fi
if test -z "${PROFILE_GE_2_TRUE}" && test -z "${PROFILE_GE_2_FALSE}"; then
as_fn_error $? "conditional \"PROFILE_GE_2\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
+13 -1
View File
@@ -26,6 +26,9 @@ AM_INIT_AUTOMAKE([foreign subdir-objects])
# (including clang), so it cannot be used to enforce the single-compiler rule.
# Verify the compiler is genuinely GCC, not a compatible one.
AC_PROG_CC
# AM_PROG_AS: library sources include raw x86_64 assembly (the setjmp family
# in src/setjmp/x86_64/*.s), which automake compiles through CCAS/CCASFLAGS.
AM_PROG_AS
AC_MSG_CHECKING([whether the compiler is GCC])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [
#if !defined(__GNUC__) || defined(__clang__)
@@ -58,7 +61,16 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [])],
AC_MSG_ERROR([$CC does not support -std=$vlibc_cstd])])
CFLAGS=$vlibc_save_CFLAGS
AC_SUBST([VLIBC_CFLAGS], ["-std=$vlibc_cstd -Wall -Wextra"])
# -fno-stack-protector: distro GCC builds enable -fstack-protector-strong by
# default, which would make library objects reference __stack_chk_fail — a
# symbol vlibc does not provide yet. Every make-check test binary and every
# -nostdlib -static consumer links against the plain archive, and one
# undefined __stack_chk_fail in it breaks the whole self-hosting link. A
# bootstrap libc must build its own objects without the protector (the stack
# protector runtime lands with the hardening work, after which this flag is
# revisited). Tests compile with the same flag (tests/Makefile.am wiring in
# Makefile.am).
AC_SUBST([VLIBC_CFLAGS], ["-std=$vlibc_cstd -Wall -Wextra -fno-stack-protector"])
# ---- Compatibility profile ----------------------------------------------
# Exactly one of the five profiles is active. They are mutually exclusive;
+266
View File
@@ -0,0 +1,266 @@
#ifndef VLIBC_TEST_H
#define VLIBC_TEST_H
/*
* vlibc — shared test contract (todo 6).
*
* Every future per-function test (test_*.c) includes this header and gets:
*
* - TEST_ASSERT_EQ(actual, expected) integer equality, compared as
* signed 64-bit; on failure prints
* both values in hex
* - TEST_ASSERT_TRUE(cond) boolean check
* - TEST_ASSERT_STREQ(actual, expected) NUL-terminated string equality;
* NULL-safe (NULL equals only NULL)
* - TEST_ASSERT_NULL(p) pointer is NULL
* - TEST_MAIN(tests) a main() that runs the array of
* struct vlibc_test entries, prints
* one "RUN <name>: PASS|FAIL" line
* per test and a PASS/FAIL summary,
* and returns 0 when everything
* passed and 1 otherwise
*
* Contract:
* - Dependency-light: needs only vlibc's own <stddef.h> and the internal
* raw-syscall layer (src/internal/syscall.h, via a path-relative
* include). It deliberately includes NO host header and does NOT use
* vlibc stdio (which does not exist yet). All output goes through raw
* SYS_write to fd 1 (progress/summary) and fd 2 (assertion failures).
* - Compile tests with -Iinclude -std=c23 -fno-stack-protector
* -fno-pic -fno-pie (the make check harness does this; see Makefile.am)
* and link statically against the vlibc test archive.
* - A test function returns 0 on success, nonzero on failure; TEST_MAIN
* aggregates both the return values and any assertion failures.
* - The failure counter is per translation unit (one static int); keep
* each test binary a single TU.
* - Tests that need argc/argv (mode flags like -f) write their own
* main() and may still use the TEST_ASSERT_* macros.
* - One test function per checked behavior, named for the output.
* - All macro arguments are evaluated exactly once.
* - Do NOT include host headers in a TU that includes this file: the
* -Iinclude path shadows GCC's internal headers (see tests/test_strerror.c).
*/
#include <stddef.h>
#include "../../../src/internal/syscall.h"
/* One registered test: a name for the runner output and its run function. */
struct vlibc_test
{
const char *name;
int (*run)(void);
};
/* Assertion failures recorded in this translation unit. */
static int vlibc_test_failures;
/* ---- Raw output helpers (no stdio: raw SYS_write only) ---- */
static inline long
vlibc_test_strlen(const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
return n;
}
static inline void
vlibc_test_say(int fd, const char *s)
{
(void)__syscall3(SYS_write, fd, (long)s, vlibc_test_strlen(s));
}
/*
* The helpers below all take an (fd, value) or (value, name, file, line)
* argument shape whose order is a fixed diagnostic convention; the
* easily-swappable-parameters warning does not apply.
*/
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
static inline void
vlibc_test_say_dec(int fd, unsigned long v)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
(void)__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
static inline void
vlibc_test_say_hex(int fd, unsigned long long v)
{
char buf[18];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
unsigned int d = (unsigned int)(v & 0xf);
buf[--i] = (char)(d < 10 ? '0' + d : 'a' + d - 10);
v >>= 4;
} while (v != 0);
(void)__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
/* ---- Assertion machinery (implementation side of the macros) ---- */
static inline void
vlibc_test_fail_header(const char *file, int line)
{
vlibc_test_failures++;
vlibc_test_say(2, "ASSERT FAIL: ");
vlibc_test_say(2, file);
vlibc_test_say(2, ":");
vlibc_test_say_dec(2, (unsigned long)line);
vlibc_test_say(2, ": ");
}
static inline void // NOLINT(bugprone-easily-swappable-parameters)
vlibc_test_check(int ok, const char *what, const char *file, int line)
{
if (ok)
{
return;
}
vlibc_test_fail_header(file, line);
vlibc_test_say(2, what);
vlibc_test_say(2, " is false\n");
}
static inline void // NOLINT(bugprone-easily-swappable-parameters)
vlibc_test_check_eq(long long actual, long long expected, const char *actual_s,
const char *expected_s, const char *file, int line)
{
if (actual == expected)
{
return;
}
vlibc_test_fail_header(file, line);
vlibc_test_say(2, "TEST_ASSERT_EQ(");
vlibc_test_say(2, actual_s);
vlibc_test_say(2, ", ");
vlibc_test_say(2, expected_s);
vlibc_test_say(2, "): got 0x");
vlibc_test_say_hex(2, (unsigned long long)actual);
vlibc_test_say(2, ", want 0x");
vlibc_test_say_hex(2, (unsigned long long)expected);
vlibc_test_say(2, "\n");
}
static inline int
vlibc_test_str_eq(const char *a, const char *b)
{
if (a == b)
{
return 1;
}
if (a == NULL || b == NULL)
{
return 0;
}
while (*a != '\0' && *a == *b)
{
a++;
b++;
}
return *a == *b;
}
static inline void // NOLINT(bugprone-easily-swappable-parameters)
vlibc_test_check_streq(const char *actual, const char *expected,
const char *actual_s, const char *expected_s,
const char *file, int line)
{
if (vlibc_test_str_eq(actual, expected))
{
return;
}
vlibc_test_fail_header(file, line);
vlibc_test_say(2, "TEST_ASSERT_STREQ(");
vlibc_test_say(2, actual_s);
vlibc_test_say(2, ", ");
vlibc_test_say(2, expected_s);
vlibc_test_say(2, "): got \"");
vlibc_test_say(2, actual == NULL ? "(null)" : actual);
vlibc_test_say(2, "\", want \"");
vlibc_test_say(2, expected == NULL ? "(null)" : expected);
vlibc_test_say(2, "\"\n");
}
// NOLINTEND(bugprone-easily-swappable-parameters)
/* ---- Public assertion macros ---- */
#define TEST_ASSERT_TRUE(cond) \
do \
{ \
vlibc_test_check((cond) != 0, #cond, __FILE__, __LINE__); \
} while (0)
#define TEST_ASSERT_EQ(actual, expected) \
do \
{ \
vlibc_test_check_eq((long long)(actual), (long long)(expected), \
#actual, #expected, __FILE__, __LINE__); \
} while (0)
#define TEST_ASSERT_NULL(p) \
do \
{ \
vlibc_test_check((p) == NULL, #p " == NULL", __FILE__, __LINE__); \
} while (0)
#define TEST_ASSERT_STREQ(actual, expected) \
do \
{ \
vlibc_test_check_streq((actual), (expected), #actual, #expected, \
__FILE__, __LINE__); \
} while (0)
/* ---- Runner ---- */
#define TEST_MAIN(tests) \
int \
main(void) \
{ \
const size_t vlibc_test_count = sizeof(tests) / sizeof((tests)[0]); \
size_t vlibc_test_i; \
size_t vlibc_test_passed = 0; \
for (vlibc_test_i = 0; vlibc_test_i < vlibc_test_count; vlibc_test_i++) \
{ \
int vlibc_test_before = vlibc_test_failures; \
vlibc_test_say(1, "RUN "); \
vlibc_test_say(1, (tests)[vlibc_test_i].name); \
vlibc_test_say(1, ": "); \
if ((tests)[vlibc_test_i].run() == 0 && \
vlibc_test_failures == vlibc_test_before) \
{ \
vlibc_test_say(1, "PASS\n"); \
vlibc_test_passed++; \
} \
else \
{ \
vlibc_test_say(1, "FAIL\n"); \
} \
} \
vlibc_test_say(1, "SUMMARY: "); \
vlibc_test_say_dec(1, (unsigned long)vlibc_test_passed); \
vlibc_test_say(1, "/"); \
vlibc_test_say_dec(1, (unsigned long)vlibc_test_count); \
vlibc_test_say(1, " passed, "); \
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures); \
vlibc_test_say(1, " assertion failure(s)\n"); \
return vlibc_test_passed == vlibc_test_count ? 0 : 1; \
}
#endif /* VLIBC_TEST_H */
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
#!/bin/sh
# vlibc — make check mode matrix (todo 6).
#
# Runs every scenario that a single "run the binary once" TESTS entry cannot
# cover: the -f failure modes of the four syscall/errno/setjmp/headers tests
# and the full test_startup mode matrix. Every scenario pins its exact exit
# status and (where load-bearing) its exact stdout; see tests/test_startup.c
# and the individual tests for the mode contracts.
#
# The binaries are the static vlibc executables built by make check next to
# the top-level Makefile. make check executes this script with the build
# directory root as the working directory (in-tree and VPATH builds alike),
# so the binaries resolve as plain ./<name> paths relative to the CWD, NOT
# relative to this script's location.
fail=0
# run_rc <binary> <want_rc> <description> [args...]
# Runs the binary with the given args and checks the exit status only (the
# -f modes print diagnostics, whose text is not part of the contract).
run_rc()
{
bin=$1
want_rc=$2
desc=$3
shift 3
"$bin" "$@" >/dev/null 2>&1
rc=$?
if [ "$rc" -ne "$want_rc" ]; then
echo "FAIL: $desc: rc=$rc want=$want_rc"
fail=1
else
echo "ok: $desc"
fi
}
# run <binary> <want_rc> <want_stdout> <description> [args...]
# Like run_rc but also compares the exact stdout (stderr discarded). An
# empty want_stdout matches an empty stdout.
run()
{
bin=$1
want_rc=$2
want_out=$3
desc=$4
shift 4
out=$("$bin" "$@" 2>/dev/null)
rc=$?
if [ "$rc" -ne "$want_rc" ]; then
echo "FAIL: $desc: rc=$rc want=$want_rc"
fail=1
elif [ "$out" != "$want_out" ]; then
echo "FAIL: $desc: stdout [$out] want [$want_out]"
fail=1
else
echo "ok: $desc"
fi
}
bindir=.
# -f failure scenarios of the four arg-mode tests (exit status is the
# contract; the diagnostics on stdout are not pinned).
run_rc "./test_syscall" 0 "syscall -f: ENOENT via fake TCB slot" -f
run_rc "./test_strerror" 0 "strerror -f: unknown errno formats" -f
run_rc "./test_setjmp" 0 "setjmp -f: longjmp(env,0) coerced to 1" -f
run_rc "./test_headers" 132 "headers -f: assert(0) traps (SIGILL)" -f
# test_startup mode matrix (each mode pins its own exit status; the default
# mode exits 42 on success, which is why it is not a bare TESTS entry).
run "./test_startup" 42 "C
B
A" "startup default: atexit LIFO CBA, status 42"
run "./test_startup" 5 "Q" "startup -q: quick_exit runs only at_quick_exit" -q
run "./test_startup" 7 "" "startup -z: _exit runs no handlers" -z
run "./test_startup" 0 "" "startup -t: main-thread TLS + errno TCB slot" -t
run "./test_startup" 0 "" "startup -x: handler-list limits" -x
# -e needs VLIBC_TEST_VAR=hello in environ (checked by the test itself).
out=$(VLIBC_TEST_VAR=hello "./test_startup" -e extraarg 2>/dev/null)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "FAIL: startup -e: argv/env plumbing rc=$rc want=0"
fail=1
elif [ "$out" != "argc=3
argv0=./test_startup
argv1=-e
argv2=extraarg" ]; then
echo "FAIL: startup -e: unexpected stdout [$out]"
fail=1
else
echo "ok: startup -e: argv/env plumbing"
fi
exit $fail