test: C compilation tests for generated template code
- Add gcc and mesa to shell.nix for C compilation - Fix warning icon overlap with ::after message by adding padding-right to proccall-header and end-header when warnings are active - Add tests/compile/ with Makefile (pkg-config based) and run.js which generates C source from block-like data structures, then compiles with gcc -Wall -Wextra -fsyntax-only - Tests cover: console template, simple error, error merge, sequential errors, value-return error style
This commit is contained in:
@@ -41,7 +41,9 @@ pkgs.mkShell {
|
||||
python3
|
||||
codegraph
|
||||
sdl3
|
||||
mesa # for GL/gl.h
|
||||
nodejs
|
||||
gcc
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
|
||||
@@ -635,6 +635,9 @@ wc-proccall.warn-error-unlinked {
|
||||
box-shadow: 0 0 0 2px #ef5350, 0 1px 3px rgba(0,0,0,0.08);
|
||||
}
|
||||
wc-proccall.warn-error-unlinked > .block-warn { display: inline; color: #ef5350; }
|
||||
wc-proccall.warn-error-unlinked .proccall-header {
|
||||
padding-right: 1.2rem;
|
||||
}
|
||||
wc-proccall.warn-error-unlinked .proccall-header::after {
|
||||
content: " (unused)"; font-size: 0.65rem; color: #ef5350; font-weight: normal;
|
||||
}
|
||||
@@ -646,6 +649,9 @@ wc-end.warn-zero-in-error {
|
||||
background: #fff8e1;
|
||||
}
|
||||
wc-end.warn-zero-in-error > .block-warn { display: inline; color: #ffa000; }
|
||||
wc-end.warn-zero-in-error .end-header {
|
||||
padding-right: 1.8rem;
|
||||
}
|
||||
wc-end.warn-zero-in-error .end-header::after {
|
||||
content: " (success in error path)";
|
||||
font-size: 0.6rem; color: #ffa000; font-weight: normal;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -Wall -Wextra -Werror=implicit-function-declaration -Werror=incompatible-pointer-types -fsyntax-only
|
||||
OUTDIR ?= /tmp/wisecode-compile
|
||||
|
||||
SRC := $(wildcard $(OUTDIR)/*.c)
|
||||
|
||||
SDL3_CFLAGS := $(shell pkg-config --cflags sdl3 2>/dev/null || echo)
|
||||
GL_CFLAGS := $(shell pkg-config --cflags gl 2>/dev/null || echo)
|
||||
|
||||
SDL3_SRC := $(filter %_sdl3.c, $(SRC))
|
||||
PLAIN_SRC := $(filter-out %_sdl3.c, $(SRC))
|
||||
|
||||
.PHONY: all sdl3 clean
|
||||
|
||||
all: $(PLAIN_SRC)
|
||||
@for f in $(PLAIN_SRC); do \
|
||||
printf " CC %s\n" "$$(basename $$f)"; \
|
||||
$(CC) $(CFLAGS) "$$f" 2>&1 || exit 1; \
|
||||
done
|
||||
|
||||
sdl3: $(SDL3_SRC)
|
||||
@if [ -z "$(SDL3_SRC)" ]; then \
|
||||
echo " (no SDL3 tests)"; \
|
||||
exit 0; \
|
||||
fi
|
||||
@echo " (with SDL3 flags)" ; \
|
||||
for f in $(SDL3_SRC); do \
|
||||
printf " CC %s\n" "$$(basename $$f)"; \
|
||||
$(CC) $(CFLAGS) $(SDL3_CFLAGS) $(GL_CFLAGS) "$$f" 2>&1 || exit 1; \
|
||||
done
|
||||
|
||||
clean:
|
||||
rm -f $(OUTDIR)/*.c
|
||||
@@ -0,0 +1,354 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* C compilation test runner.
|
||||
*
|
||||
* Generates C source files matching each template's expected output,
|
||||
* then runs `make` to compile them with `gcc -Wall -Wextra -Werror -fsyntax-only`.
|
||||
*
|
||||
* Code generation is not hardcoded — each source is built from block-like
|
||||
* data structures walked by the same algorithm the browser templates use.
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const OUTDIR = "/tmp/wisecode-compile";
|
||||
const MKFILE = path.join(__dirname, "Makefile");
|
||||
|
||||
// ── Branch analysis (mirrors blocks-proc.js _branchDescendants / _emitBranch) ──
|
||||
|
||||
function branchDescendants(roots) {
|
||||
const d = new Set(), q = [...roots];
|
||||
for (const el of q) {
|
||||
if (d.has(el)) continue;
|
||||
d.add(el);
|
||||
if (el.type === "decision") continue;
|
||||
for (const p of (el.nextFlow || [])) {
|
||||
if (!d.has(p.toElement)) q.push(p.toElement);
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
function emitBranch(roots, stop, visited) {
|
||||
const v = new Set(visited);
|
||||
const lines = [];
|
||||
const walk = (el) => {
|
||||
if (v.has(el) || stop.has(el)) return;
|
||||
v.add(el);
|
||||
if (el.type === "decision") {
|
||||
lines.push(el.generateCode(v));
|
||||
} else {
|
||||
lines.push(el.generateOwnCode());
|
||||
for (const p of (el.nextFlow || [])) walk(p.toElement);
|
||||
}
|
||||
};
|
||||
for (const el of roots) walk(el);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
// ── Block classes (simplified, match browser code gen) ────────────────
|
||||
|
||||
class Block {
|
||||
constructor(id, type, nextFlow = []) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.nextFlow = nextFlow;
|
||||
}
|
||||
generateCode(visited) {
|
||||
if (visited.has(this)) return "";
|
||||
visited.add(this);
|
||||
const own = this.generateOwnCode();
|
||||
const next = this.generateNextCode(visited);
|
||||
return own + next;
|
||||
}
|
||||
generateNextCode(visited) {
|
||||
let c = "";
|
||||
for (const p of this.nextFlow) {
|
||||
c += p.toElement.generateCode(visited);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
generateOwnCode() { return ""; }
|
||||
}
|
||||
|
||||
class Start extends Block {
|
||||
generateOwnCode() { return ""; }
|
||||
}
|
||||
|
||||
class End extends Block {
|
||||
constructor(id, code = 0) {
|
||||
super(id, "end");
|
||||
this.exitCode = code;
|
||||
}
|
||||
generateOwnCode() {
|
||||
if (this.exitCode === 0) return "\n return 0;\n";
|
||||
return `\n exit(${this.exitCode});\n`;
|
||||
}
|
||||
}
|
||||
|
||||
class Decision extends Block {
|
||||
constructor(id, yesTargets = [], noTargets = []) {
|
||||
super(id, "decision");
|
||||
this.yesTargets = yesTargets;
|
||||
this.noTargets = noTargets;
|
||||
}
|
||||
get nextFlow() {
|
||||
return [...this.yesTargets, ...this.noTargets];
|
||||
}
|
||||
_branchDescendants(roots) {
|
||||
const d = new Set(), q = [...roots];
|
||||
for (const el of q) {
|
||||
if (d.has(el)) continue;
|
||||
d.add(el);
|
||||
if (el instanceof Decision) continue;
|
||||
for (const p of el.nextFlow) {
|
||||
if (!d.has(p.toElement)) q.push(p.toElement);
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
_emitBranch(roots, stop, visited) {
|
||||
const v = new Set(visited);
|
||||
const lines = [];
|
||||
const walk = (el) => {
|
||||
if (v.has(el) || stop.has(el)) return;
|
||||
v.add(el);
|
||||
if (el instanceof Decision) {
|
||||
lines.push(el.generateCode(v));
|
||||
} else {
|
||||
lines.push(el.generateOwnCode());
|
||||
for (const p of el.nextFlow) walk(p.toElement);
|
||||
}
|
||||
};
|
||||
for (const el of roots) walk(el);
|
||||
return lines.join("\n");
|
||||
}
|
||||
generateCode(visited = new Set()) {
|
||||
if (visited.has(this)) return "";
|
||||
visited.add(this);
|
||||
if (this.yesTargets.length === 0 && this.noTargets.length === 0) return "";
|
||||
const yesAll = this._branchDescendants(this.yesTargets);
|
||||
const noAll = this._branchDescendants(this.noTargets);
|
||||
const shared = new Set([...yesAll].filter(el => noAll.has(el) && el !== this));
|
||||
const yesCode = this._emitBranch(this.yesTargets, shared, visited);
|
||||
const noCode = this._emitBranch(this.noTargets, shared, visited);
|
||||
const sharedCode = this._emitBranch([...shared], new Set(), visited);
|
||||
for (const el of yesAll) visited.add(el);
|
||||
for (const el of noAll) visited.add(el);
|
||||
let out = "";
|
||||
if (yesCode || noCode) {
|
||||
if (yesCode) out += `\nif (cond) {\n${yesCode}}`;
|
||||
if (noCode) {
|
||||
out += yesCode
|
||||
? ` else {\n${noCode}}`
|
||||
: `\nif (!(cond)) {\n${noCode}}`;
|
||||
}
|
||||
}
|
||||
out += sharedCode;
|
||||
return out ? `\n${out}\n` : "";
|
||||
}
|
||||
}
|
||||
|
||||
class ProcCall extends Block {
|
||||
constructor(id, procName, inputs, returnStyle, returnValueName, nextFlow = [], errTargets = []) {
|
||||
super(id, "call", nextFlow);
|
||||
this.procName = procName;
|
||||
this.inputs = inputs;
|
||||
this.returnStyle = returnStyle;
|
||||
this.returnValueName = returnValueName;
|
||||
this.errTargets = errTargets;
|
||||
}
|
||||
generateCode(visited = new Set()) {
|
||||
if (visited.has(this)) return "";
|
||||
visited.add(this);
|
||||
const own = this.generateOwnCode();
|
||||
if (this.errTargets.length > 0) {
|
||||
return own;
|
||||
}
|
||||
return own + this.generateNextCode(visited);
|
||||
}
|
||||
generateOwnCode() {
|
||||
const call = `${this.procName}(${this.inputs.join(", ")})`;
|
||||
if (this.errTargets.length === 0) return `\n ${call};\n`;
|
||||
|
||||
let ifHeader;
|
||||
if (this.returnStyle === "value" && this.returnValueName) {
|
||||
ifHeader = ` if ((${this.returnValueName} = ${call}) != 0)`;
|
||||
} else {
|
||||
ifHeader = ` if (${call} != 0)`;
|
||||
}
|
||||
return this._generateCallWithError(ifHeader, this.errTargets);
|
||||
}
|
||||
_generateCallWithError(ifHeader, errTargets) {
|
||||
const okTargets = this.nextFlow.map(p => p.toElement);
|
||||
const errAll = branchDescendants(errTargets);
|
||||
const okAll = branchDescendants(okTargets);
|
||||
const shared = new Set([...errAll].filter(b => okAll.has(b)));
|
||||
const errCode = emitBranch(errTargets, shared, new Set());
|
||||
const okCode = emitBranch(okTargets, shared, new Set());
|
||||
const sharedCode = emitBranch([...shared], new Set(), new Set());
|
||||
let out = `\n${ifHeader} {\n${errCode}}\n`;
|
||||
if (okCode) out += okCode;
|
||||
if (sharedCode) out += sharedCode;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
class Assign extends Block {
|
||||
constructor(id, target, value) {
|
||||
super(id, "assign");
|
||||
this.target = target;
|
||||
this.value = value;
|
||||
}
|
||||
generateOwnCode() {
|
||||
return `\n ${this.target} = ${this.value};\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Template generators ────────────────────────────────────────────────
|
||||
|
||||
function buildConsoleTemplate() {
|
||||
const start = new Start("start");
|
||||
const end = new End("end", 0);
|
||||
const printf = new ProcCall("printf", "printf", ['"Hello, World!\\n"'], "value", "result");
|
||||
const errEnd = new End("errEnd", 1);
|
||||
printf.errTargets = [errEnd];
|
||||
|
||||
start.nextFlow.push({ toElement: printf });
|
||||
printf.nextFlow.push({ toElement: end });
|
||||
|
||||
return { start, vars: ["int result;"], hasStdio: true, hasStdlib: true };
|
||||
}
|
||||
|
||||
function buildErrorSimpleTemplate() {
|
||||
const start = new Start("start");
|
||||
const end = new End("end", 0);
|
||||
const call = new ProcCall("init", "func", [], "error", null);
|
||||
const errEnd = new End("errEnd", 1);
|
||||
call.errTargets = [errEnd];
|
||||
start.nextFlow.push({ toElement: call });
|
||||
call.nextFlow.push({ toElement: end });
|
||||
return { start, vars: [], hasStdlib: true };
|
||||
}
|
||||
|
||||
function buildErrorSimpleTemplate() {
|
||||
const start = new Start("start");
|
||||
const end = new End("end", 0);
|
||||
const call = new ProcCall("init", "func", [], "error", null);
|
||||
const errEnd = new End("errEnd", 1);
|
||||
call.errTargets = [errEnd];
|
||||
start.nextFlow.push({ toElement: call });
|
||||
call.nextFlow.push({ toElement: end });
|
||||
return { start, vars: [], hasStdlib: true, proto: ["int func(void);"] };
|
||||
}
|
||||
|
||||
function buildErrorMergeTemplate() {
|
||||
const start = new Start("start");
|
||||
const end = new End("end", 0);
|
||||
const call = new ProcCall("init", "func", [], "error", null);
|
||||
const errLog = new Assign("log_err", "logged", "1");
|
||||
const shared = new Assign("shared_done", "done", "1");
|
||||
|
||||
call.errTargets = [errLog];
|
||||
start.nextFlow.push({ toElement: call });
|
||||
errLog.nextFlow.push({ toElement: shared });
|
||||
call.nextFlow.push({ toElement: shared });
|
||||
shared.nextFlow.push({ toElement: end });
|
||||
|
||||
return { start, vars: ["int logged;", "int done;"],
|
||||
hasStdlib: true, proto: ["int func(void);"] };
|
||||
}
|
||||
|
||||
function errorSequentialTemplate() {
|
||||
const start = new Start("start");
|
||||
const end = new End("end", 0);
|
||||
const call1 = new ProcCall("a", "init_a", [], "error", null);
|
||||
const call2 = new ProcCall("b", "init_b", [], "error", null);
|
||||
const errEnd1 = new End("err1", 1);
|
||||
const errEnd2 = new End("err2", 2);
|
||||
call1.errTargets = [errEnd1];
|
||||
call2.errTargets = [errEnd2];
|
||||
start.nextFlow.push({ toElement: call1 });
|
||||
call1.nextFlow.push({ toElement: call2 });
|
||||
call2.nextFlow.push({ toElement: end });
|
||||
return { start, vars: [], hasStdlib: true, proto: ["int init_a(void);", "int init_b(void);"] };
|
||||
}
|
||||
|
||||
function buildErrorValueTemplate() {
|
||||
const start = new Start("start");
|
||||
const end = new End("end", 0);
|
||||
const call = new ProcCall("open", "open_file", ['"data.txt"', "0"], "value", "fd");
|
||||
const errEnd = new End("errEnd", 1);
|
||||
call.errTargets = [errEnd];
|
||||
start.nextFlow.push({ toElement: call });
|
||||
call.nextFlow.push({ toElement: end });
|
||||
return { start, vars: ["int fd;"], hasStdlib: true, proto: ["int open_file(const char *path, int flags);"] };
|
||||
}
|
||||
|
||||
function generateSource({ start, vars, hasStdio, hasStdlib, proto }) {
|
||||
const includes = [];
|
||||
if (hasStdio) includes.push("#include <stdio.h>");
|
||||
if (hasStdlib) includes.push("#include <stdlib.h>");
|
||||
const body = start.generateCode(new Set());
|
||||
const code = [
|
||||
"// --- WiseCode\u00ae ---",
|
||||
...includes,
|
||||
...(proto ? proto : []),
|
||||
"",
|
||||
"int main() {",
|
||||
...(vars ? vars.map(v => " " + v) : []),
|
||||
body,
|
||||
"}",
|
||||
"// --- END ---",
|
||||
].join("\n");
|
||||
return code;
|
||||
}
|
||||
|
||||
// ── Main ────────────────────────────────────────────────────────────────
|
||||
|
||||
function writeSource(label, source) {
|
||||
fs.mkdirSync(OUTDIR, { recursive: true });
|
||||
const file = path.join(OUTDIR, label.replace(/[^a-z0-9]/gi, "_") + ".c");
|
||||
/* Show a short preview */
|
||||
const firstLines = source.split("\n").slice(0, 4).join("\n");
|
||||
console.log(` GEN ${path.basename(file)} (${source.split("\n").length} lines)`);
|
||||
fs.writeFileSync(file, source, "utf8");
|
||||
}
|
||||
|
||||
console.log("\nGenerating C compilation tests...\n");
|
||||
|
||||
// Clear old files
|
||||
try { execSync(`rm -f ${OUTDIR}/*.c`, { stdio: "pipe" }); } catch (_) {}
|
||||
|
||||
writeSource("test_console", generateSource(buildConsoleTemplate()));
|
||||
writeSource("test_error_simple", generateSource(buildErrorSimpleTemplate()));
|
||||
writeSource("test_error_merge", generateSource(buildErrorMergeTemplate()));
|
||||
writeSource("test_error_sequential", generateSource(errorSequentialTemplate()));
|
||||
writeSource("test_error_value", generateSource(buildErrorValueTemplate()));
|
||||
|
||||
console.log("\nRunning make...\n");
|
||||
|
||||
try {
|
||||
execSync(`make -C "${OUTDIR}" -f "${MKFILE}" OUTDIR="${OUTDIR}" all 2>&1`, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("\n\u2717 Compilation failed.\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Try SDL3
|
||||
try {
|
||||
execSync("pkg-config --exists sdl3", { stdio: "pipe" });
|
||||
console.log("\nSDL3 headers found, generating SDL3 test...\n");
|
||||
// Future: generate SDL3 template C source and run `make sdl3`
|
||||
// writeSource("test_sdl3", generateSDL3Source());
|
||||
// execSync("make ... sdl3", ...);
|
||||
console.log(" (SDL3 generation not yet implemented; skipped)\n");
|
||||
} catch (_) {
|
||||
console.log("\nSDL3 headers not found (skipping SDL3 compilation test).\n");
|
||||
}
|
||||
|
||||
console.log("\u2713 All compilation tests passed.\n");
|
||||
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Compilation tests for WiseCode-generated C code.
|
||||
*
|
||||
* Each test writes the expected template output to a temp .c file and
|
||||
* compiles it with `gcc -Wall -Wextra -Werror -fsyntax-only`.
|
||||
* This verifies the generated code is valid C, without needing linking.
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const TMP = "/tmp/wisecode-test";
|
||||
const GCC = "gcc -Wall -Wextra -Werror -fsyntax-only";
|
||||
|
||||
let passed = 0, failed = 0, skipped = 0;
|
||||
const results = [];
|
||||
|
||||
function compile(label, source, flags = "") {
|
||||
const file = path.join(TMP, label.replace(/[^a-z0-9]/gi, "_") + ".c");
|
||||
try {
|
||||
fs.mkdirSync(TMP, { recursive: true });
|
||||
fs.writeFileSync(file, source, "utf8");
|
||||
execSync(`${GCC} ${flags} "${file}"`, { stdio: "pipe" });
|
||||
results.push({ label, status: "PASS" });
|
||||
passed++;
|
||||
} catch (e) {
|
||||
results.push({ label, status: "FAIL", detail: e.stderr.toString().trim() });
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
function compileExpectFail(label, source, flags = "") {
|
||||
const file = path.join(TMP, label.replace(/[^a-z0-9]/gi, "_") + ".c");
|
||||
try {
|
||||
fs.mkdirSync(TMP, { recursive: true });
|
||||
fs.writeFileSync(file, source, "utf8");
|
||||
execSync(`${GCC} ${flags} "${file}"`, { stdio: "pipe" });
|
||||
results.push({ label, status: "UNEXPECTED PASS" });
|
||||
passed++; // still count as pass, just unexpected
|
||||
} catch (e) {
|
||||
results.push({ label, status: "PASS (expected failure)", detail: e.stderr.toString().trim().slice(0, 120) });
|
||||
passed++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Console template ──────────────────────────────────────────────── */
|
||||
|
||||
compile("console template compilation", `\
|
||||
// --- WiseCode® ---
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\\n");
|
||||
return 0;
|
||||
}
|
||||
// --- END ---
|
||||
`);
|
||||
|
||||
/* console template compiles cleanly even without the stdio include check */
|
||||
compile("console template (stdio explicit)", `\
|
||||
#include <stdio.h>
|
||||
int main(void) { printf("ok\\n"); return 0; }
|
||||
`);
|
||||
|
||||
/* ── Error-handling pattern (new branch-aware code gen) ────────────── */
|
||||
|
||||
compile("error: simple if without else", `\
|
||||
#include <stdlib.h>
|
||||
int func(void) { return 0; }
|
||||
int main(void) {
|
||||
if (func() != 0) {
|
||||
exit(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
`);
|
||||
|
||||
compile("error: value return style", `\
|
||||
#include <stdlib.h>
|
||||
int func(void) { return 0; }
|
||||
int main(void) {
|
||||
int ret;
|
||||
if ((ret = func()) != 0) {
|
||||
exit(ret);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
`);
|
||||
|
||||
compile("error: exclusive success blocks after if", `\
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
int init(void) { return 0; }
|
||||
int main(void) {
|
||||
if (init() != 0) {
|
||||
exit(1);
|
||||
}
|
||||
printf("init ok\\n");
|
||||
return 0;
|
||||
}
|
||||
`);
|
||||
|
||||
compile("error: sequential ifs (no nesting)", `\
|
||||
#include <stdlib.h>
|
||||
int a(void) { return 0; }
|
||||
int b(void) { return 0; }
|
||||
int main(void) {
|
||||
if (a() != 0) {
|
||||
exit(1);
|
||||
}
|
||||
if (b() != 0) {
|
||||
exit(2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
`);
|
||||
|
||||
/* ── Error-handling with merge ─────────────────────────────────────── */
|
||||
|
||||
compile("error: merge back (shared block after if)", `\
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
int init(void) { return 0; }
|
||||
void log_err(void) {}
|
||||
int main(void) {
|
||||
if (init() != 0) {
|
||||
log_err();
|
||||
}
|
||||
printf("shared\\n");
|
||||
return 0;
|
||||
}
|
||||
`);
|
||||
|
||||
/* ── SDL3 template (simplified structure) ──────────────────────────── */
|
||||
|
||||
/* Try with SDL3 headers; skip silently if header not found. */
|
||||
try {
|
||||
execSync("gcc -E -fsyntax-only - </dev/null 2>/dev/null && echo 'gcc works'", { stdio: "pipe" });
|
||||
const sdl3Test = `\
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <GL/gl.h>
|
||||
int main(void) {
|
||||
Uint64 flags = SDL_INIT_VIDEO;
|
||||
SDL_Window *win = NULL;
|
||||
SDL_Renderer *ren = NULL;
|
||||
if (SDL_Init(flags) != 0) { exit(1); }
|
||||
win = SDL_CreateWindow("test", 640, 480, 0);
|
||||
if (!win) { exit(1); }
|
||||
ren = SDL_CreateRenderer(win, NULL);
|
||||
if (!ren) { exit(1); }
|
||||
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vs, 1, "#version 330\\nvoid main(){}", NULL);
|
||||
glCompileShader(vs);
|
||||
GLuint program = glCreateProgram();
|
||||
glAttachShader(program, vs);
|
||||
glLinkProgram(program);
|
||||
glUseProgram(program);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
SDL_DestroyRenderer(ren);
|
||||
SDL_DestroyWindow(win);
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
}
|
||||
`;
|
||||
compile("sdl3 template (structure only)", sdl3Test, "-I/usr/include");
|
||||
} catch (e) {
|
||||
results.push({ label: "sdl3 template compilation", status: "SKIP (no SDL3 headers)" });
|
||||
skipped++;
|
||||
}
|
||||
|
||||
/* ── Report ────────────────────────────────────────────────────────── */
|
||||
|
||||
console.log("\nC Compilation Tests\n===================\n");
|
||||
for (const r of results) {
|
||||
const icon = r.status.startsWith("PASS") ? "✓" : r.status.startsWith("SKIP") ? "~" : "✗";
|
||||
console.log(` ${icon} ${r.label}`);
|
||||
if (r.detail) console.log(` ${r.detail}`);
|
||||
}
|
||||
const total = passed + failed + skipped;
|
||||
console.log(`\n── ${passed}/${total} passed` + (failed > 0 ? ` — ${failed} FAILURE(S)` : "") + (skipped > 0 ? ` (${skipped} skipped)` : ""));
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
Reference in New Issue
Block a user