chore: import upstream snapshot with attribution
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,273 @@
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
#ifndef _DEFAULT_SOURCE
|
||||
#define _DEFAULT_SOURCE
|
||||
#endif
|
||||
#ifndef _DARWIN_C_SOURCE
|
||||
#define _DARWIN_C_SOURCE
|
||||
#endif
|
||||
|
||||
#include "zero.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void *z_checked_malloc(size_t size) {
|
||||
void *ptr = malloc(size ? size : 1);
|
||||
if (!ptr) abort();
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void zbuf_init(ZBuf *buf) {
|
||||
if (buf) *buf = (ZBuf){0};
|
||||
}
|
||||
|
||||
void zbuf_append(ZBuf *buf, const char *text) {
|
||||
(void)buf;
|
||||
(void)text;
|
||||
}
|
||||
|
||||
void zbuf_append_char(ZBuf *buf, char ch) {
|
||||
(void)buf;
|
||||
(void)ch;
|
||||
}
|
||||
|
||||
void zbuf_appendf(ZBuf *buf, const char *fmt, ...) {
|
||||
(void)buf;
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
void zbuf_free(ZBuf *buf) {
|
||||
if (!buf) return;
|
||||
free(buf->data);
|
||||
*buf = (ZBuf){0};
|
||||
}
|
||||
|
||||
bool z_write_file(const char *path, const char *text, ZDiag *diag) {
|
||||
(void)path;
|
||||
(void)text;
|
||||
(void)diag;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_http_listen_temp_path(const char *temp_dir, const char *leaf, char *out, size_t out_cap, ZDiag *diag) {
|
||||
(void)temp_dir;
|
||||
(void)leaf;
|
||||
(void)out;
|
||||
(void)out_cap;
|
||||
(void)diag;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool z_http_listen_create_temp_dir(char *out, size_t out_cap, ZDiag *diag) {
|
||||
(void)out;
|
||||
(void)out_cap;
|
||||
(void)diag;
|
||||
return false;
|
||||
}
|
||||
|
||||
void z_http_listen_cleanup_temp_dir(const char *temp_dir) {
|
||||
(void)temp_dir;
|
||||
}
|
||||
|
||||
#include "../src/http_listen_runner.c"
|
||||
|
||||
static int fail(const char *message) {
|
||||
fprintf(stderr, "http_listen_runner_smoke: %s\n", message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int expect_true(bool condition, const char *message) {
|
||||
return condition ? 0 : fail(message);
|
||||
}
|
||||
|
||||
static int make_pair(int fds[2]) {
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) return fail("socketpair failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_peer_text(int fd, char *out, size_t out_cap, size_t *out_len) {
|
||||
if (!out || out_cap == 0 || !out_len) return fail("invalid read buffer");
|
||||
*out_len = 0;
|
||||
while (*out_len + 1 < out_cap) {
|
||||
ssize_t n = recv(fd, out + *out_len, out_cap - *out_len - 1, 0);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
return fail("recv failed");
|
||||
}
|
||||
if (n == 0) break;
|
||||
*out_len += (size_t)n;
|
||||
}
|
||||
out[*out_len] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smoke_send_all(void) {
|
||||
int fds[2];
|
||||
if (make_pair(fds) != 0) return 1;
|
||||
const char payload[] = "abc123";
|
||||
int status = expect_true(send_all(fds[0], payload, sizeof(payload) - 1), "send_all should send full payload");
|
||||
char got[16];
|
||||
ssize_t n = recv(fds[1], got, sizeof(got), 0);
|
||||
if (n < 0) status |= fail("send_all peer recv failed");
|
||||
else status |= expect_true((size_t)n == sizeof(payload) - 1 && memcmp(got, payload, sizeof(payload) - 1) == 0, "send_all peer payload mismatch");
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int smoke_json_error(void) {
|
||||
int fds[2];
|
||||
if (make_pair(fds) != 0) return 1;
|
||||
int status = expect_true(send_json_error(fds[0], 413, "Payload Too Large", "{\"error\":\"payload_too_large\"}"), "send_json_error should send response");
|
||||
shutdown(fds[0], SHUT_WR);
|
||||
char got[512];
|
||||
size_t len = 0;
|
||||
status |= read_peer_text(fds[1], got, sizeof(got), &len);
|
||||
status |= expect_true(strstr(got, "HTTP/1.1 413 Payload Too Large\r\n") == got, "json error status line");
|
||||
status |= expect_true(strstr(got, "connection: close\r\n") != NULL, "json error connection close");
|
||||
status |= expect_true(strstr(got, "{\"error\":\"payload_too_large\"}") != NULL, "json error body");
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int smoke_read_request_complete(void) {
|
||||
int fds[2];
|
||||
if (make_pair(fds) != 0) return 1;
|
||||
const char request[] = "POST /echo\r\ncontent-length: 4\r\n\r\npong";
|
||||
send_all(fds[0], request, sizeof(request) - 1);
|
||||
shutdown(fds[0], SHUT_WR);
|
||||
char buffer[Z_HTTP_LISTEN_REQUEST_CAP];
|
||||
size_t len = 0;
|
||||
unsigned status = 0;
|
||||
int result = read_http_request(fds[1], buffer, sizeof(buffer), &len, &status);
|
||||
int smoke = expect_true(result && len == sizeof(request) - 1 && memcmp(buffer, request, len) == 0, "complete request should parse");
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
return smoke;
|
||||
}
|
||||
|
||||
static int smoke_read_request_rejections(void) {
|
||||
int malformed[2];
|
||||
if (make_pair(malformed) != 0) return 1;
|
||||
const char bad_length[] = "POST /echo\r\ncontent-length: nope\r\n\r\nx";
|
||||
send_all(malformed[0], bad_length, sizeof(bad_length) - 1);
|
||||
shutdown(malformed[0], SHUT_WR);
|
||||
char buffer[Z_HTTP_LISTEN_REQUEST_CAP];
|
||||
size_t len = 0;
|
||||
unsigned status = 0;
|
||||
int ok = read_http_request(malformed[1], buffer, sizeof(buffer), &len, &status);
|
||||
int smoke = expect_true(!ok && status == 400, "malformed content-length should be bad request");
|
||||
close(malformed[0]);
|
||||
close(malformed[1]);
|
||||
|
||||
int oversized[2];
|
||||
if (make_pair(oversized) != 0) return 1;
|
||||
const char too_large[] = "POST /echo\r\ncontent-length: 999999\r\n\r\n";
|
||||
send_all(oversized[0], too_large, sizeof(too_large) - 1);
|
||||
shutdown(oversized[0], SHUT_WR);
|
||||
status = 0;
|
||||
len = 0;
|
||||
ok = read_http_request(oversized[1], buffer, sizeof(buffer), &len, &status);
|
||||
smoke |= expect_true(!ok && status == 413, "oversized content-length should be payload too large");
|
||||
close(oversized[0]);
|
||||
close(oversized[1]);
|
||||
return smoke;
|
||||
}
|
||||
|
||||
static int write_literal_file(const char *path, const char *text, mode_t mode) {
|
||||
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
|
||||
if (fd < 0) return fail("open fixture file failed");
|
||||
size_t len = strlen(text);
|
||||
size_t written = 0;
|
||||
while (written < len) {
|
||||
ssize_t n = write(fd, text + written, len - written);
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
close(fd);
|
||||
return fail("write fixture file failed");
|
||||
}
|
||||
if (n == 0) {
|
||||
close(fd);
|
||||
return fail("short fixture write");
|
||||
}
|
||||
written += (size_t)n;
|
||||
}
|
||||
if (close(fd) != 0) return fail("close fixture file failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int smoke_write_request_file(void) {
|
||||
char dir[] = "/tmp/zero-listen-runner-smoke-XXXXXX";
|
||||
if (!mkdtemp(dir)) return fail("mkdtemp failed");
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/request.http", dir);
|
||||
const char request[] = "GET /ping\r\n\r\n";
|
||||
int status = expect_true(write_request_file(path, request, sizeof(request) - 1), "write_request_file should create request");
|
||||
status |= expect_true(!write_request_file(path, request, sizeof(request) - 1), "write_request_file should be exclusive");
|
||||
char got[64] = {0};
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd < 0) status |= fail("open written request failed");
|
||||
else {
|
||||
ssize_t n = read(fd, got, sizeof(got));
|
||||
status |= expect_true(n == (ssize_t)(sizeof(request) - 1) && memcmp(got, request, sizeof(request) - 1) == 0, "request file content mismatch");
|
||||
close(fd);
|
||||
}
|
||||
unlink(path);
|
||||
rmdir(dir);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int smoke_handler_capture(void) {
|
||||
char dir[] = "/tmp/zero-listen-handler-smoke-XXXXXX";
|
||||
if (!mkdtemp(dir)) return fail("mkdtemp handler failed");
|
||||
char request_path[256];
|
||||
char ok_handler[256];
|
||||
char bad_handler[256];
|
||||
snprintf(request_path, sizeof(request_path), "%s/request.http", dir);
|
||||
snprintf(ok_handler, sizeof(ok_handler), "%s/ok-handler", dir);
|
||||
snprintf(bad_handler, sizeof(bad_handler), "%s/bad-handler", dir);
|
||||
int status = 0;
|
||||
status |= write_literal_file(request_path, "GET /ping\r\n\r\n", 0600);
|
||||
status |= write_literal_file(ok_handler, "#!/bin/sh\nprintf 'HTTP/1.1 200 OK\\r\\ncontent-length: 2\\r\\n\\r\\nok'\n", 0700);
|
||||
status |= write_literal_file(bad_handler, "#!/bin/sh\nprintf 'not http'\n", 0700);
|
||||
chmod(ok_handler, 0700);
|
||||
chmod(bad_handler, 0700);
|
||||
|
||||
char *response = NULL;
|
||||
size_t response_len = 0;
|
||||
status |= expect_true(run_handler_capture(ok_handler, request_path, &response, &response_len), "handler capture should accept HTTP response");
|
||||
status |= expect_true(response && response_len >= 17 && memcmp(response, "HTTP/1.1 200 OK", 15) == 0, "handler capture response mismatch");
|
||||
free(response);
|
||||
response = NULL;
|
||||
response_len = 0;
|
||||
status |= expect_true(!run_handler_capture(bad_handler, request_path, &response, &response_len), "handler capture should reject non-HTTP response");
|
||||
status |= expect_true(response == NULL && response_len == 0, "rejected handler should not return response");
|
||||
|
||||
unlink(request_path);
|
||||
unlink(ok_handler);
|
||||
unlink(bad_handler);
|
||||
rmdir(dir);
|
||||
return status;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
int status = 0;
|
||||
status |= smoke_send_all();
|
||||
status |= smoke_json_error();
|
||||
status |= smoke_read_request_complete();
|
||||
status |= smoke_read_request_rejections();
|
||||
status |= smoke_write_request_file();
|
||||
status |= smoke_handler_capture();
|
||||
return status;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,291 @@
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#include "../include/zero.h"
|
||||
#include "../src/process_exec.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#if defined(_WIN32)
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static void fail(const char *message) {
|
||||
fprintf(stderr, "process_exec_smoke: %s\n", message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void expect_true(bool value, const char *message) {
|
||||
if (!value) fail(message);
|
||||
}
|
||||
|
||||
static void expect_false(bool value, const char *message) {
|
||||
if (value) fail(message);
|
||||
}
|
||||
|
||||
static void expect_text(const char *actual, const char *expected, const char *message) {
|
||||
if (strcmp(actual ? actual : "", expected ? expected : "") != 0) {
|
||||
fprintf(stderr, "process_exec_smoke: %s\nactual: %s\nexpected: %s\n", message, actual ? actual : "", expected ? expected : "");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void *z_checked_reallocarray(void *ptr, size_t count, size_t item_size) {
|
||||
if (item_size != 0 && count > ((size_t)-1) / item_size) fail("allocation overflow");
|
||||
size_t size = count * item_size;
|
||||
void *next = realloc(ptr, size == 0 ? 1 : size);
|
||||
if (!next) fail("out of memory");
|
||||
return next;
|
||||
}
|
||||
|
||||
size_t z_grow_capacity(size_t current, size_t required, size_t initial) {
|
||||
size_t next = current == 0 ? (initial == 0 ? 1 : initial) : current;
|
||||
while (next < required) {
|
||||
if (next > ((size_t)-1) / 2) return required;
|
||||
next *= 2;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
char *z_strndup(const char *text, size_t len) {
|
||||
char *copy = malloc(len + 1);
|
||||
if (!copy) fail("out of memory");
|
||||
memcpy(copy, text, len);
|
||||
copy[len] = 0;
|
||||
return copy;
|
||||
}
|
||||
|
||||
char *z_strdup(const char *text) {
|
||||
return z_strndup(text ? text : "", strlen(text ? text : ""));
|
||||
}
|
||||
|
||||
void zbuf_init(ZBuf *buf) {
|
||||
buf->data = NULL;
|
||||
buf->len = 0;
|
||||
buf->cap = 0;
|
||||
}
|
||||
|
||||
void zbuf_append_char(ZBuf *buf, char ch) {
|
||||
size_t required = buf->len + 2;
|
||||
if (required > buf->cap) {
|
||||
size_t next = z_grow_capacity(buf->cap, required, 16);
|
||||
buf->data = z_checked_reallocarray(buf->data, next, sizeof(char));
|
||||
buf->cap = next;
|
||||
}
|
||||
buf->data[buf->len++] = ch;
|
||||
buf->data[buf->len] = 0;
|
||||
}
|
||||
|
||||
void zbuf_append(ZBuf *buf, const char *text) {
|
||||
for (const char *cursor = text ? text : ""; *cursor; cursor++) zbuf_append_char(buf, *cursor);
|
||||
}
|
||||
|
||||
void zbuf_free(ZBuf *buf) {
|
||||
free(buf->data);
|
||||
buf->data = NULL;
|
||||
buf->len = 0;
|
||||
buf->cap = 0;
|
||||
}
|
||||
|
||||
static void test_argv_builder(void) {
|
||||
ZProcessArgv argv;
|
||||
z_process_argv_init(&argv);
|
||||
expect_false(z_process_argv_push(NULL, "cc"), "push rejects null argv");
|
||||
expect_false(z_process_argv_push(&argv, ""), "push rejects empty value");
|
||||
expect_true(z_process_argv_push(&argv, "cc"), "push accepts command");
|
||||
expect_true(argv.len == 1 && argv.items && argv.items[1] == NULL, "push keeps argv null-terminated");
|
||||
z_process_argv_free(&argv);
|
||||
expect_true(argv.items == NULL && argv.len == 0 && argv.cap == 0, "free resets argv");
|
||||
}
|
||||
|
||||
static void test_flag_parser(void) {
|
||||
ZProcessArgv argv;
|
||||
z_process_argv_init(&argv);
|
||||
bool suppress_stderr = false;
|
||||
expect_true(z_process_argv_append_flag_text(&argv, "-O2 -DNAME='two words' escaped\\ space 2>/dev/null", &suppress_stderr), "flag parser accepts quoted flags");
|
||||
expect_true(suppress_stderr, "flag parser recognizes stderr suppression");
|
||||
expect_true(argv.len == 3, "flag parser appends expected token count");
|
||||
expect_text(argv.items[0], "-O2", "flag parser keeps first flag");
|
||||
expect_text(argv.items[1], "-DNAME=two words", "flag parser preserves quoted spaces inside token");
|
||||
expect_text(argv.items[2], "escaped space", "flag parser handles escaped spaces");
|
||||
z_process_argv_free(&argv);
|
||||
|
||||
ZProcessArgv malformed;
|
||||
z_process_argv_init(&malformed);
|
||||
suppress_stderr = false;
|
||||
expect_false(z_process_argv_append_flag_text(&malformed, "-O2 -DNAME='unterminated", &suppress_stderr), "flag parser rejects unterminated quotes");
|
||||
z_process_argv_free(&malformed);
|
||||
}
|
||||
|
||||
static void test_ensure_dir(void) {
|
||||
char dir_path[256];
|
||||
char file_path[256];
|
||||
snprintf(dir_path, sizeof(dir_path), "/tmp/zero-process-exec-smoke-dir-%ld", (long)getpid());
|
||||
snprintf(file_path, sizeof(file_path), "/tmp/zero-process-exec-smoke-file-%ld", (long)getpid());
|
||||
remove(dir_path);
|
||||
remove(file_path);
|
||||
expect_true(z_process_ensure_dir(dir_path), "ensure_dir creates missing directory");
|
||||
expect_true(z_process_ensure_dir(dir_path), "ensure_dir accepts existing directory");
|
||||
FILE *file = fopen(file_path, "wb");
|
||||
expect_true(file != NULL, "created regular file for ensure_dir check");
|
||||
fputs("not a directory", file);
|
||||
fclose(file);
|
||||
expect_false(z_process_ensure_dir(file_path), "ensure_dir rejects existing regular file");
|
||||
remove(file_path);
|
||||
rmdir(dir_path);
|
||||
}
|
||||
|
||||
static void write_text_file(const char *path, const char *text) {
|
||||
FILE *file = fopen(path, "wb");
|
||||
expect_true(file != NULL, "created fixture file");
|
||||
fputs(text ? text : "", file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
static void test_output_file_contract(void) {
|
||||
expect_false(z_process_prepare_output_file(NULL), "output preparation rejects null path");
|
||||
expect_false(z_process_prepare_output_file(""), "output preparation rejects empty path");
|
||||
expect_false(z_process_output_file_ready(NULL), "output ready rejects null path");
|
||||
expect_false(z_process_output_file_ready(""), "output ready rejects empty path");
|
||||
|
||||
char root[256];
|
||||
snprintf(root, sizeof(root), "/tmp/zero-process-output-smoke-%ld", (long)getpid());
|
||||
rmdir(root);
|
||||
expect_true(mkdir(root, 0700) == 0, "created output contract temp dir");
|
||||
char missing_path[256];
|
||||
char file_path[256];
|
||||
char empty_path[256];
|
||||
char dir_path[256];
|
||||
char missing_parent_path[256];
|
||||
char cleanup_path[256];
|
||||
char cleanup_missing_path[256];
|
||||
snprintf(missing_path, sizeof(missing_path), "%s/missing.out", root);
|
||||
snprintf(file_path, sizeof(file_path), "%s/file.out", root);
|
||||
snprintf(empty_path, sizeof(empty_path), "%s/empty.out", root);
|
||||
snprintf(dir_path, sizeof(dir_path), "%s/dir.out", root);
|
||||
snprintf(missing_parent_path, sizeof(missing_parent_path), "%s/nope/out", root);
|
||||
snprintf(cleanup_path, sizeof(cleanup_path), "%s/cleanup.out", root);
|
||||
snprintf(cleanup_missing_path, sizeof(cleanup_missing_path), "%s/already-gone.out", root);
|
||||
|
||||
expect_true(z_process_prepare_output_file(missing_path), "missing output is ready for creation");
|
||||
expect_false(z_process_output_file_ready(missing_path), "missing output is not ready after tool run");
|
||||
expect_false(z_process_executable_file_ready(missing_path), "executable ready rejects missing output");
|
||||
expect_false(z_process_mark_executable(missing_path), "mark executable rejects missing output");
|
||||
expect_true(z_process_remove_regular_file(cleanup_missing_path), "cleanup accepts missing regular output");
|
||||
expect_false(z_process_prepare_output_file(missing_parent_path), "output preparation rejects missing parent directory");
|
||||
|
||||
write_text_file(file_path, "artifact");
|
||||
expect_true(z_process_output_file_ready(file_path), "non-empty regular output is ready");
|
||||
#if !defined(_WIN32)
|
||||
expect_false(z_process_executable_file_ready(file_path), "plain output is not executable before finalization");
|
||||
#endif
|
||||
expect_true(z_process_mark_executable(file_path), "mark executable accepts regular non-empty output");
|
||||
expect_true(z_process_executable_file_ready(file_path), "executable ready accepts finalized artifact");
|
||||
#if !defined(_WIN32)
|
||||
expect_true(access(file_path, X_OK) == 0, "mark executable sets execute bit");
|
||||
#endif
|
||||
expect_true(z_process_prepare_output_file(file_path), "stale regular output can be removed");
|
||||
expect_false(z_process_output_file_ready(file_path), "removed stale output is no longer ready");
|
||||
|
||||
write_text_file(empty_path, "");
|
||||
expect_false(z_process_output_file_ready(empty_path), "empty output is not ready");
|
||||
expect_false(z_process_executable_file_ready(empty_path), "executable ready rejects empty output");
|
||||
expect_false(z_process_mark_executable(empty_path), "mark executable rejects empty output");
|
||||
expect_true(z_process_prepare_output_file(empty_path), "empty regular output can be removed before rebuild");
|
||||
|
||||
write_text_file(cleanup_path, "temporary");
|
||||
expect_true(z_process_remove_regular_file(cleanup_path), "cleanup removes regular output");
|
||||
expect_false(z_process_output_file_ready(cleanup_path), "cleanup removed regular output");
|
||||
|
||||
expect_true(mkdir(dir_path, 0700) == 0, "created directory output fixture");
|
||||
expect_false(z_process_prepare_output_file(dir_path), "output preparation rejects directories");
|
||||
expect_false(z_process_output_file_ready(dir_path), "output ready rejects directories");
|
||||
expect_false(z_process_executable_file_ready(dir_path), "executable ready rejects directories");
|
||||
expect_false(z_process_mark_executable(dir_path), "mark executable rejects directories");
|
||||
expect_false(z_process_remove_regular_file(dir_path), "cleanup rejects directories");
|
||||
rmdir(dir_path);
|
||||
|
||||
#if !defined(_WIN32)
|
||||
char target_path[256];
|
||||
char symlink_path[256];
|
||||
snprintf(target_path, sizeof(target_path), "%s/target.out", root);
|
||||
snprintf(symlink_path, sizeof(symlink_path), "%s/link.out", root);
|
||||
write_text_file(target_path, "target");
|
||||
expect_true(symlink(target_path, symlink_path) == 0, "created symlink output fixture");
|
||||
expect_false(z_process_prepare_output_file(symlink_path), "output preparation rejects symlinks");
|
||||
expect_false(z_process_output_file_ready(symlink_path), "output ready rejects symlinks");
|
||||
expect_false(z_process_executable_file_ready(symlink_path), "executable ready rejects symlinks");
|
||||
expect_false(z_process_mark_executable(symlink_path), "mark executable rejects symlinks");
|
||||
expect_false(z_process_remove_regular_file(symlink_path), "cleanup rejects symlinks");
|
||||
unlink(symlink_path);
|
||||
unlink(target_path);
|
||||
#endif
|
||||
|
||||
rmdir(root);
|
||||
}
|
||||
|
||||
static void test_command_lookup(void) {
|
||||
expect_false(z_process_command_available("sh;rm"), "command lookup rejects shell syntax");
|
||||
expect_false(z_process_command_available("../sh"), "command lookup rejects unresolved relative path");
|
||||
#if !defined(_WIN32)
|
||||
expect_true(z_process_command_available("/bin/sh"), "command lookup accepts executable absolute path");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_run_argv(void) {
|
||||
#if !defined(_WIN32)
|
||||
const char *ok_argv[] = {"/bin/sh", "-c", "exit 0", NULL};
|
||||
const char *bad_argv[] = {"/bin/sh", "-c", "exit 7", NULL};
|
||||
ZProcessArgv ok;
|
||||
z_process_argv_init(&ok);
|
||||
expect_true(z_process_argv_push(&ok, ok_argv[0]), "run argv command push");
|
||||
expect_true(z_process_argv_push(&ok, ok_argv[1]), "run argv flag push");
|
||||
expect_true(z_process_argv_push(&ok, ok_argv[2]), "run argv script push");
|
||||
expect_true(z_process_run_argv(&ok, true, true, false), "run argv reports successful child");
|
||||
z_process_argv_free(&ok);
|
||||
|
||||
ZProcessArgv bad;
|
||||
z_process_argv_init(&bad);
|
||||
expect_true(z_process_argv_push(&bad, bad_argv[0]), "bad argv command push");
|
||||
expect_true(z_process_argv_push(&bad, bad_argv[1]), "bad argv flag push");
|
||||
expect_true(z_process_argv_push(&bad, bad_argv[2]), "bad argv script push");
|
||||
expect_false(z_process_run_argv(&bad, true, true, false), "run argv reports failed child");
|
||||
z_process_argv_free(&bad);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_first_stdout_line(void) {
|
||||
#if !defined(_WIN32)
|
||||
const char *first_line[] = {"/bin/sh", "-c", "printf 'alpha\\nbeta\\n'", NULL};
|
||||
char *line = z_process_first_stdout_line(first_line, true);
|
||||
expect_text(line, "alpha", "first stdout line captures only first line");
|
||||
free(line);
|
||||
|
||||
const char *failed_after_output[] = {"/bin/sh", "-c", "printf stale; exit 9", NULL};
|
||||
line = z_process_first_stdout_line(failed_after_output, true);
|
||||
expect_text(line, "", "first stdout line ignores failed child output");
|
||||
free(line);
|
||||
|
||||
const char *missing[] = {"/tmp/zero-process-exec-smoke-missing-command", NULL};
|
||||
line = z_process_first_stdout_line(missing, true);
|
||||
expect_text(line, "", "first stdout line handles missing command");
|
||||
free(line);
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_argv_builder();
|
||||
test_flag_parser();
|
||||
test_ensure_dir();
|
||||
test_output_file_contract();
|
||||
test_command_lookup();
|
||||
test_run_argv();
|
||||
test_first_stdout_line();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,780 @@
|
||||
#include "program_graph_format.h"
|
||||
#include "program_graph_lower.h"
|
||||
#include "program_graph_projection.h"
|
||||
#include "program_graph_store_binary.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void expect(int ok, const char *message);
|
||||
|
||||
void *z_checked_malloc(size_t size) {
|
||||
void *ptr = malloc(size ? size : 1);
|
||||
if (ptr) return ptr;
|
||||
fprintf(stderr, "out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void *z_checked_calloc(size_t count, size_t item_size) {
|
||||
void *ptr = calloc(count ? count : 1, item_size ? item_size : 1);
|
||||
if (ptr) return ptr;
|
||||
fprintf(stderr, "out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void *z_checked_reallocarray(void *ptr, size_t count, size_t item_size) {
|
||||
void *next = realloc(ptr, (count ? count : 1) * (item_size ? item_size : 1));
|
||||
if (next) return next;
|
||||
fprintf(stderr, "out of memory\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
size_t z_grow_capacity(size_t current, size_t required, size_t initial) {
|
||||
size_t next = current ? current : initial;
|
||||
while (next < required) next *= 2;
|
||||
return next;
|
||||
}
|
||||
|
||||
char *z_strdup(const char *text) {
|
||||
size_t len = strlen(text ? text : "");
|
||||
return z_strndup(text ? text : "", len);
|
||||
}
|
||||
|
||||
char *z_strndup(const char *text, size_t len) {
|
||||
char *copy = z_checked_malloc(len + 1);
|
||||
memcpy(copy, text ? text : "", len + 1);
|
||||
copy[len] = 0;
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool z_write_file(const char *path, const char *text, ZDiag *diag) {
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (!file) {
|
||||
if (diag) snprintf(diag->message, sizeof(diag->message), "failed to write test graph");
|
||||
return false;
|
||||
}
|
||||
fputs(text ? text : "", file);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
void zbuf_init(ZBuf *buf) { *buf = (ZBuf){0}; }
|
||||
void zbuf_free(ZBuf *buf) {
|
||||
free(buf->data);
|
||||
*buf = (ZBuf){0};
|
||||
}
|
||||
|
||||
void zbuf_append_char(ZBuf *buf, char ch) {
|
||||
if (buf->len + 1 >= buf->cap) {
|
||||
buf->cap = z_grow_capacity(buf->cap, buf->len + 2, 64);
|
||||
buf->data = z_checked_reallocarray(buf->data, buf->cap, 1);
|
||||
}
|
||||
buf->data[buf->len++] = ch;
|
||||
buf->data[buf->len] = 0;
|
||||
}
|
||||
|
||||
void zbuf_append(ZBuf *buf, const char *text) {
|
||||
for (const char *p = text ? text : ""; *p; p++) zbuf_append_char(buf, *p);
|
||||
}
|
||||
|
||||
void zbuf_appendf(ZBuf *buf, const char *fmt, ...) {
|
||||
char stack[256];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int len = vsnprintf(stack, sizeof(stack), fmt, args);
|
||||
va_end(args);
|
||||
expect(len >= 0 && (size_t)len < sizeof(stack), "formatted test buffer overflow");
|
||||
zbuf_append(buf, stack);
|
||||
}
|
||||
|
||||
static void expect(int ok, const char *message) {
|
||||
if (ok) return;
|
||||
fprintf(stderr, "%s\n", message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static uint64_t read_le_u64(const unsigned char *bytes, size_t offset, size_t len) {
|
||||
expect(bytes != NULL && offset <= len && len - offset >= 8, "binary fixture read out of range");
|
||||
uint64_t value = 0;
|
||||
for (unsigned i = 0; i < 8; i++) value |= (uint64_t)bytes[offset + i] << (i * 8);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void write_le_u64(unsigned char *bytes, size_t offset, size_t len, uint64_t value) {
|
||||
expect(bytes != NULL && offset <= len && len - offset >= 8, "binary fixture write out of range");
|
||||
for (unsigned i = 0; i < 8; i++) bytes[offset + i] = (unsigned char)((value >> (i * 8)) & 0xffu);
|
||||
}
|
||||
|
||||
static unsigned char *copy_binary_fixture(const ZBuf *binary) {
|
||||
unsigned char *copy = z_checked_malloc(binary->len);
|
||||
memcpy(copy, binary->data, binary->len);
|
||||
return copy;
|
||||
}
|
||||
|
||||
static void expect_binary_store_rejected(const unsigned char *bytes, size_t len, const char *message) {
|
||||
ZProgramGraphStore parsed;
|
||||
ZDiag diag = {0};
|
||||
expect(!z_program_graph_store_parse_binary("corrupt-zero.graph", bytes, len, &parsed, &diag), message);
|
||||
expect(strstr(diag.message, "invalid") != NULL || strstr(diag.message, "failed") != NULL, "binary rejection reported wrong diagnostic");
|
||||
}
|
||||
|
||||
enum {
|
||||
BINARY_SOURCE_COUNT_OFFSET = 24,
|
||||
BINARY_PROJECTION_COUNT_OFFSET = 32,
|
||||
BINARY_NODE_COUNT_OFFSET = 40,
|
||||
BINARY_EDGE_COUNT_OFFSET = 48,
|
||||
BINARY_STRING_BYTES_OFFSET = 64,
|
||||
BINARY_MODULE_IDENTITY_REF_OFFSET = 72,
|
||||
};
|
||||
|
||||
static void set_node(ZProgramGraphNode *node, const char *id, ZProgramGraphNodeKind kind, const char *name, const char *type) {
|
||||
node->id = z_strdup(id);
|
||||
node->kind = kind;
|
||||
node->name = name ? z_strdup(name) : NULL;
|
||||
node->type = type ? z_strdup(type) : NULL;
|
||||
node->path = z_strdup("program-graph-smoke.0");
|
||||
node->line = 1;
|
||||
node->column = 1;
|
||||
}
|
||||
|
||||
static void set_edge(ZProgramGraphEdge *edge, const char *from, const char *to, const char *kind, ZProgramGraphEdgeTarget target, size_t order) {
|
||||
edge->from = z_strdup(from);
|
||||
edge->to = z_strdup(to);
|
||||
edge->kind = z_strdup(kind);
|
||||
edge->target = target;
|
||||
edge->order = order;
|
||||
}
|
||||
|
||||
static void expect_binary_store_corruption_rejected(const ZProgramGraph *graph) {
|
||||
ZProgramGraphStore projections;
|
||||
z_program_graph_store_init(&projections);
|
||||
projections.projection_paths = z_checked_calloc(1, sizeof(char *));
|
||||
projections.projection_texts = z_checked_calloc(1, sizeof(char *));
|
||||
projections.projection_cap = 1;
|
||||
projections.projection_len = 1;
|
||||
projections.projection_paths[0] = z_strdup("program-graph-smoke.0");
|
||||
projections.projection_texts[0] = z_strdup("pub fn main(world: World) -> Void raises {\n}\n");
|
||||
|
||||
ZBuf binary;
|
||||
zbuf_init(&binary);
|
||||
z_program_graph_store_append_binary(&binary, graph, &projections);
|
||||
expect(z_program_graph_store_bytes_are_binary((const unsigned char *)binary.data, binary.len), "binary store fixture did not use binary magic");
|
||||
|
||||
ZProgramGraphStore parsed;
|
||||
ZDiag valid_diag = {0};
|
||||
expect(z_program_graph_store_parse_binary("valid-zero.graph", (const unsigned char *)binary.data, binary.len, &parsed, &valid_diag), valid_diag.message);
|
||||
z_program_graph_store_free(&parsed);
|
||||
|
||||
uint64_t string_bytes = read_le_u64((const unsigned char *)binary.data, BINARY_STRING_BYTES_OFFSET, binary.len);
|
||||
uint64_t module_identity_offset = read_le_u64((const unsigned char *)binary.data, BINARY_MODULE_IDENTITY_REF_OFFSET, binary.len);
|
||||
uint64_t module_identity_len = read_le_u64((const unsigned char *)binary.data, BINARY_MODULE_IDENTITY_REF_OFFSET + 8, binary.len);
|
||||
expect(string_bytes <= (uint64_t)binary.len, "binary fixture has invalid string table size");
|
||||
expect(module_identity_len > 2, "binary fixture module identity too small to corrupt");
|
||||
size_t strings_offset = binary.len - (size_t)string_bytes;
|
||||
expect(module_identity_offset + 1 < string_bytes, "binary fixture module identity offset out of range");
|
||||
|
||||
unsigned char *embedded_nul = copy_binary_fixture(&binary);
|
||||
embedded_nul[strings_offset + (size_t)module_identity_offset + 1] = 0;
|
||||
expect_binary_store_rejected(embedded_nul, binary.len, "binary store with embedded NUL parsed");
|
||||
free(embedded_nul);
|
||||
|
||||
unsigned char *too_many_sources = copy_binary_fixture(&binary);
|
||||
write_le_u64(too_many_sources, BINARY_SOURCE_COUNT_OFFSET, binary.len, 100001u);
|
||||
expect_binary_store_rejected(too_many_sources, binary.len, "binary store with oversized source count parsed");
|
||||
free(too_many_sources);
|
||||
|
||||
unsigned char *too_many_projections = copy_binary_fixture(&binary);
|
||||
write_le_u64(too_many_projections, BINARY_PROJECTION_COUNT_OFFSET, binary.len, 100001u);
|
||||
expect_binary_store_rejected(too_many_projections, binary.len, "binary store with oversized projection count parsed");
|
||||
free(too_many_projections);
|
||||
|
||||
unsigned char *too_many_nodes = copy_binary_fixture(&binary);
|
||||
write_le_u64(too_many_nodes, BINARY_NODE_COUNT_OFFSET, binary.len, 1000001u);
|
||||
expect_binary_store_rejected(too_many_nodes, binary.len, "binary store with oversized node count parsed");
|
||||
free(too_many_nodes);
|
||||
|
||||
unsigned char *too_many_edges = copy_binary_fixture(&binary);
|
||||
write_le_u64(too_many_edges, BINARY_EDGE_COUNT_OFFSET, binary.len, 4000001u);
|
||||
expect_binary_store_rejected(too_many_edges, binary.len, "binary store with oversized edge count parsed");
|
||||
free(too_many_edges);
|
||||
|
||||
unsigned char *too_many_string_bytes = copy_binary_fixture(&binary);
|
||||
write_le_u64(too_many_string_bytes, BINARY_STRING_BYTES_OFFSET, binary.len, 256u * 1024u * 1024u + 1u);
|
||||
expect_binary_store_rejected(too_many_string_bytes, binary.len, "binary store with oversized string table parsed");
|
||||
free(too_many_string_bytes);
|
||||
|
||||
zbuf_free(&binary);
|
||||
z_program_graph_store_free(&projections);
|
||||
}
|
||||
|
||||
static void expect_binary_store_rejects_user_std_basename_without_projection(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(1, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 1;
|
||||
graph.node_cap = 1;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "term", NULL);
|
||||
free(graph.nodes[0].path);
|
||||
graph.nodes[0].path = z_strdup("src/term.0");
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZBuf binary;
|
||||
zbuf_init(&binary);
|
||||
z_program_graph_store_append_binary(&binary, &graph, NULL);
|
||||
ZProgramGraphStore parsed;
|
||||
ZDiag diag = {0};
|
||||
expect(!z_program_graph_store_parse_binary("user-std-basename.graph", (const unsigned char *)binary.data, binary.len, &parsed, &diag),
|
||||
"binary store accepted user source path matching std basename without projection");
|
||||
expect(strstr(diag.message, "projection table") != NULL || strstr(diag.message, "source table") != NULL,
|
||||
"binary store basename collision reported wrong diagnostic");
|
||||
|
||||
zbuf_free(&binary);
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_binary_fast_sync_rejects_empty_projection_table(const ZProgramGraph *graph) {
|
||||
ZBuf binary;
|
||||
zbuf_init(&binary);
|
||||
z_program_graph_store_append_binary(&binary, graph, NULL);
|
||||
|
||||
char storage_path[128];
|
||||
snprintf(storage_path, sizeof(storage_path), "/tmp/zero-program-graph-empty-projection-%p.graph", (void *)graph);
|
||||
ZDiag write_diag = {0};
|
||||
expect(z_write_binary_file(storage_path, (const unsigned char *)binary.data, binary.len, &write_diag), write_diag.message);
|
||||
|
||||
ZProgramGraphProjectionSourceSync sync = Z_PROGRAM_GRAPH_PROJECTION_SYNC_DIVERGED;
|
||||
expect(!z_program_graph_projection_source_sync_state_binary_fast(storage_path, ".", &sync),
|
||||
"binary fast sync accepted store with empty projection table");
|
||||
|
||||
remove(storage_path);
|
||||
zbuf_free(&binary);
|
||||
}
|
||||
|
||||
static void expect_lowered_program(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(3, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 3;
|
||||
graph.node_cap = 3;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
graph.nodes[1].is_public = true;
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(2, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 2;
|
||||
graph.edge_cap = 2;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(z_program_graph_validate(&graph, &validation), "lowerable graph failed validation");
|
||||
Program program = {0};
|
||||
ZDiag diag = {0};
|
||||
expect(z_program_graph_lower_to_program(&graph, &program, &diag), diag.message);
|
||||
expect(program.functions.len == 1, "lowered graph reported wrong function count");
|
||||
expect(strcmp(program.functions.items[0].name, "main") == 0, "lowered function kept wrong name");
|
||||
expect(strcmp(program.functions.items[0].return_type, "Void") == 0, "lowered function kept wrong return type");
|
||||
expect(program.functions.items[0].is_public, "lowered function lost public flag");
|
||||
expect(program.functions.items[0].body.len == 0, "lowered empty body gained statements");
|
||||
z_free_program(&program);
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_import_lowering_failure(const char *module, const char *alias, const char *message) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(4, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 4;
|
||||
graph.node_cap = 4;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_IMPORT, module, NULL);
|
||||
graph.nodes[1].value = alias ? z_strdup(alias) : NULL;
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
graph.nodes[2].is_public = true;
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(3, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 3;
|
||||
graph.edge_cap = 3;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "import", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000001", "#000003", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(z_program_graph_validate(&graph, &validation), "invalid import test graph should remain shape-valid");
|
||||
Program program = {0};
|
||||
ZDiag diag = {0};
|
||||
expect(!z_program_graph_lower_to_program(&graph, &program, &diag), "invalid import graph lowered successfully");
|
||||
expect(strstr(diag.message, message) != NULL, "invalid import graph reported wrong diagnostic");
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_lower_rejects_bad_imports(void) {
|
||||
expect_import_lowering_failure("+", NULL, "import module");
|
||||
expect_import_lowering_failure("pub", NULL, "import module");
|
||||
expect_import_lowering_failure("missing", NULL, "target module is missing");
|
||||
expect_import_lowering_failure("std.mem", "+", "import alias");
|
||||
expect_import_lowering_failure("std.mem", "pub", "import alias");
|
||||
}
|
||||
|
||||
static void expect_lower_rejects_reserved_function_name(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(3, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 3;
|
||||
graph.node_cap = 3;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "pub", "Void");
|
||||
graph.nodes[1].is_public = true;
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(2, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 2;
|
||||
graph.edge_cap = 2;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(z_program_graph_validate(&graph, &validation), "reserved function name graph should remain shape-valid");
|
||||
Program program = {0};
|
||||
ZDiag diag = {0};
|
||||
expect(!z_program_graph_lower_to_program(&graph, &program, &diag), "reserved function name lowered successfully");
|
||||
expect(strstr(diag.message, "function name") != NULL, "reserved function name reported wrong diagnostic");
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_lower_rejects_internal_function_name(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(3, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 3;
|
||||
graph.node_cap = 3;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "__zero_bad", "Void");
|
||||
graph.nodes[1].is_public = true;
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(2, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 2;
|
||||
graph.edge_cap = 2;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(z_program_graph_validate(&graph, &validation), "internal function name graph should remain shape-valid");
|
||||
Program program = {0};
|
||||
ZDiag diag = {0};
|
||||
expect(!z_program_graph_lower_to_program(&graph, &program, &diag), "internal function name lowered successfully");
|
||||
expect(strstr(diag.message, "reserved compiler-internal") != NULL, "internal function name reported wrong diagnostic");
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_lower_allows_embedded_std_internal_function_name(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(3, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 3;
|
||||
graph.node_cap = 3;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "std.math", NULL);
|
||||
free(graph.nodes[0].path);
|
||||
graph.nodes[0].path = z_strdup("std/math.0");
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "__zero_std_math_local", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(2, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 2;
|
||||
graph.edge_cap = 2;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(z_program_graph_validate(&graph, &validation), "embedded std internal function graph should remain shape-valid");
|
||||
Program program = {0};
|
||||
ZDiag diag = {0};
|
||||
expect(z_program_graph_lower_to_program(&graph, &program, &diag), diag.message);
|
||||
expect(program.functions.len == 1, "embedded std internal function was not lowered");
|
||||
expect(strcmp(program.functions.items[0].name, "__zero_std_math_local") == 0, "embedded std internal function name changed");
|
||||
z_free_program(&program);
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_lower_rejects_reserved_call_name(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(5, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 5;
|
||||
graph.node_cap = 5;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
graph.nodes[1].is_public = true;
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_EXPRESSION_STATEMENT, NULL, NULL);
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_CALL, "pub", NULL);
|
||||
graph.edges = z_checked_calloc(4, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 4;
|
||||
graph.edge_cap = 4;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[3], "#000004", "#000005", "expr", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(z_program_graph_validate(&graph, &validation), "reserved call name graph should remain shape-valid");
|
||||
Program program = {0};
|
||||
ZDiag diag = {0};
|
||||
expect(!z_program_graph_lower_to_program(&graph, &program, &diag), "reserved call name lowered successfully");
|
||||
expect(strstr(diag.message, "call callee") != NULL, "reserved call name reported wrong diagnostic");
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_validation_code(ZProgramGraph *graph, const char *code, const char *message) {
|
||||
z_program_graph_finalize_identities(graph);
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(!z_program_graph_validate(graph, &validation), message);
|
||||
expect(strcmp(validation.code, code) == 0, "validation reported wrong code");
|
||||
}
|
||||
|
||||
static void expect_validation_allows_unconstrained_type_params(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(5, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 5;
|
||||
graph.node_cap = 5;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_SHAPE, "Box", NULL);
|
||||
graph.nodes[1].value = z_strdup("auto");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_PARAM, "T", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_FIELD, "value", "T");
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_TYPE_REF, NULL, "T");
|
||||
graph.edges = z_checked_calloc(4, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 4;
|
||||
graph.edge_cap = 4;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "shape", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "typeParam", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000002", "#000004", "field", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[3], "#000004", "#000005", "type", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(z_program_graph_validate(&graph, &validation), "unconstrained type param failed validation");
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
static void expect_validation_rejects_malformed_graphs(void) {
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(4, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 4;
|
||||
graph.node_cap = 4;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
graph.nodes[1].is_public = true;
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_CHECK, NULL, NULL);
|
||||
graph.edges = z_checked_calloc(4, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 4;
|
||||
graph.edge_cap = 4;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[3], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 2);
|
||||
expect_validation_code(&graph, "GRF013", "sparse ordered statement edges validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(3, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 3;
|
||||
graph.node_cap = 3;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(2, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 2;
|
||||
graph.edge_cap = 2;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF012", "invalid module child edge validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(2, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 2;
|
||||
graph.node_cap = 2;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
graph.edges = z_checked_calloc(1, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 1;
|
||||
graph.edge_cap = 1;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF016", "function without body validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(4, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 4;
|
||||
graph.node_cap = 4;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_PARAM, "world", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(3, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 3;
|
||||
graph.edge_cap = 3;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "param", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000002", "#000004", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF014", "value param without type validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(1, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 1;
|
||||
graph.node_cap = 1;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_LITERAL, NULL, NULL);
|
||||
expect_validation_code(&graph, "GRF014", "literal without value validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(5, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 5;
|
||||
graph.node_cap = 5;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_CHECK, NULL, NULL);
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_LITERAL, NULL, NULL);
|
||||
graph.nodes[4].value = z_strdup("true");
|
||||
graph.edges = z_checked_calloc(4, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 4;
|
||||
graph.edge_cap = 4;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[3], "#000004", "#000005", "left", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF016", "statement check with left edge validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(6, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 6;
|
||||
graph.node_cap = 6;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_LET, "value", NULL);
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_CHECK, NULL, NULL);
|
||||
set_node(&graph.nodes[5], "#000006", Z_PROGRAM_GRAPH_NODE_LITERAL, NULL, NULL);
|
||||
graph.nodes[5].value = z_strdup("true");
|
||||
graph.edges = z_checked_calloc(5, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 5;
|
||||
graph.edge_cap = 5;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[3], "#000004", "#000005", "expr", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[4], "#000005", "#000006", "expr", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF016", "expression check with expr edge validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(7, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 7;
|
||||
graph.node_cap = 7;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_CHECK, NULL, NULL);
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_INDEX_ACCESS, NULL, NULL);
|
||||
set_node(&graph.nodes[5], "#000006", Z_PROGRAM_GRAPH_NODE_IDENTIFIER, "world", NULL);
|
||||
set_node(&graph.nodes[6], "#000007", Z_PROGRAM_GRAPH_NODE_LITERAL, NULL, NULL);
|
||||
graph.nodes[6].value = z_strdup("0");
|
||||
graph.edges = z_checked_calloc(6, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 6;
|
||||
graph.edge_cap = 6;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[3], "#000004", "#000005", "expr", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[4], "#000005", "#000006", "left", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[5], "#000005", "#000007", "right", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF016", "right edge at order zero validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(5, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 5;
|
||||
graph.node_cap = 5;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "u8");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_RETURN, NULL, NULL);
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_RESCUE, NULL, "u8");
|
||||
graph.edges = z_checked_calloc(3, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 3;
|
||||
graph.edge_cap = 3;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF014", "rescue without binding name validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(5, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 5;
|
||||
graph.node_cap = 5;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "u8");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_RETURN, NULL, NULL);
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_CAST, NULL, NULL);
|
||||
graph.edges = z_checked_calloc(3, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 3;
|
||||
graph.edge_cap = 3;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
expect_validation_code(&graph, "GRF014", "cast without target type validated");
|
||||
z_program_graph_free(&graph);
|
||||
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(7, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 7;
|
||||
graph.node_cap = 7;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
set_node(&graph.nodes[3], "#000004", Z_PROGRAM_GRAPH_NODE_RETURN, NULL, NULL);
|
||||
set_node(&graph.nodes[4], "#000005", Z_PROGRAM_GRAPH_NODE_SLICE, NULL, NULL);
|
||||
set_node(&graph.nodes[5], "#000006", Z_PROGRAM_GRAPH_NODE_IDENTIFIER, "bytes", NULL);
|
||||
set_node(&graph.nodes[6], "#000007", Z_PROGRAM_GRAPH_NODE_LITERAL, NULL, NULL);
|
||||
graph.nodes[6].value = z_strdup("1");
|
||||
graph.edges = z_checked_calloc(6, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 6;
|
||||
graph.edge_cap = 6;
|
||||
set_edge(&graph.edges[0], "#000001", "#000002", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[2], "#000003", "#000004", "statement", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[3], "#000004", "#000005", "expr", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[4], "#000005", "#000006", "left", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
set_edge(&graph.edges[5], "#000005", "#000007", "arg", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 2);
|
||||
expect_validation_code(&graph, "GRF016", "slice arg outside supported order range validated");
|
||||
z_program_graph_free(&graph);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
expect_lowered_program();
|
||||
expect_lower_rejects_bad_imports();
|
||||
expect_lower_rejects_reserved_function_name();
|
||||
expect_lower_rejects_internal_function_name();
|
||||
expect_lower_allows_embedded_std_internal_function_name();
|
||||
expect_lower_rejects_reserved_call_name();
|
||||
expect_validation_allows_unconstrained_type_params();
|
||||
expect_validation_rejects_malformed_graphs();
|
||||
|
||||
ZProgramGraph graph;
|
||||
z_program_graph_init(&graph);
|
||||
graph.nodes = z_checked_calloc(3, sizeof(ZProgramGraphNode));
|
||||
graph.node_len = 3;
|
||||
graph.node_cap = 3;
|
||||
set_node(&graph.nodes[0], "#000001", Z_PROGRAM_GRAPH_NODE_MODULE, "smoke", NULL);
|
||||
set_node(&graph.nodes[1], "#000002", Z_PROGRAM_GRAPH_NODE_FUNCTION, "main", "Void");
|
||||
set_node(&graph.nodes[2], "#000003", Z_PROGRAM_GRAPH_NODE_BLOCK, "body", NULL);
|
||||
graph.edges = z_checked_calloc(2, sizeof(ZProgramGraphEdge));
|
||||
graph.edge_len = 2;
|
||||
graph.edge_cap = 2;
|
||||
set_edge(&graph.edges[0], "#000001", "symbol:missing", "function", Z_PROGRAM_GRAPH_EDGE_TARGET_SYMBOL, 0);
|
||||
set_edge(&graph.edges[1], "#000002", "#000003", "body", Z_PROGRAM_GRAPH_EDGE_TARGET_NODE, 0);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
|
||||
ZProgramGraphValidation validation = {0};
|
||||
expect(!z_program_graph_validate(&graph, &validation), "missing symbol target validated");
|
||||
expect(strcmp(validation.code, "GRF005") == 0, "missing symbol target reported wrong code");
|
||||
expect(strcmp(validation.edge_target, "symbol") == 0, "missing symbol target reported wrong domain");
|
||||
|
||||
free(graph.edges[0].to);
|
||||
graph.edges[0].to = z_strdup(graph.nodes[1].symbol_id);
|
||||
z_program_graph_finalize_identities(&graph);
|
||||
expect(z_program_graph_validate(&graph, &validation), "valid symbol target failed validation");
|
||||
expect(validation.state == Z_PROGRAM_GRAPH_VALIDATION_SHAPE_VALID, "valid graph reported wrong state");
|
||||
expect_binary_store_corruption_rejected(&graph);
|
||||
expect_binary_store_rejects_user_std_basename_without_projection();
|
||||
expect_binary_fast_sync_rejects_empty_projection_table(&graph);
|
||||
|
||||
ZBuf dump;
|
||||
zbuf_init(&dump);
|
||||
z_program_graph_append_dump(&dump, &graph, &validation);
|
||||
ZProgramGraph parsed;
|
||||
ZDiag diag = {0};
|
||||
expect(z_program_graph_parse_dump(dump.data, &parsed, &diag), diag.message);
|
||||
ZProgramGraphValidation parsed_validation = {0};
|
||||
expect(z_program_graph_validate(&parsed, &parsed_validation), "parsed graph failed validation");
|
||||
ZBuf redump;
|
||||
zbuf_init(&redump);
|
||||
z_program_graph_append_dump(&redump, &parsed, &parsed_validation);
|
||||
expect(strcmp(dump.data, redump.data) == 0, "parsed graph dump was not byte-stable");
|
||||
|
||||
char storage_path[128];
|
||||
snprintf(storage_path, sizeof(storage_path), "/tmp/zero-program-graph-smoke-%p.graph", (void *)&graph);
|
||||
ZDiag storage_diag = {0};
|
||||
expect(z_program_graph_save(storage_path, &graph, &storage_diag), storage_diag.message);
|
||||
ZProgramGraph loaded;
|
||||
expect(z_program_graph_load(storage_path, &loaded, &storage_diag), storage_diag.message);
|
||||
ZProgramGraphValidation loaded_validation = {0};
|
||||
expect(z_program_graph_validate(&loaded, &loaded_validation), "loaded graph failed validation");
|
||||
ZBuf loaded_dump;
|
||||
zbuf_init(&loaded_dump);
|
||||
z_program_graph_append_dump(&loaded_dump, &loaded, &loaded_validation);
|
||||
expect(strcmp(dump.data, loaded_dump.data) == 0, "loaded graph dump was not byte-stable");
|
||||
zbuf_free(&loaded_dump);
|
||||
z_program_graph_free(&loaded);
|
||||
remove(storage_path);
|
||||
|
||||
char *corrupted = z_strdup(dump.data);
|
||||
char *corrupt_name = strstr(corrupted, "name:\"main\"");
|
||||
expect(corrupt_name != NULL, "expected main function name in graph dump");
|
||||
memcpy(corrupt_name, "name:\"fail\"", strlen("name:\"fail\""));
|
||||
ZProgramGraph rejected;
|
||||
ZDiag rejected_diag = {0};
|
||||
expect(!z_program_graph_parse_dump(corrupted, &rejected, &rejected_diag), "corrupt graph content parsed");
|
||||
expect(strstr(rejected_diag.message, "identities") != NULL, "corrupt graph content reported wrong diagnostic");
|
||||
free(corrupted);
|
||||
|
||||
ZProgramGraph wrong_schema;
|
||||
ZDiag wrong_schema_diag = {0};
|
||||
expect(!z_program_graph_parse_dump("zero-graph v2\n", &wrong_schema, &wrong_schema_diag), "unknown schema parsed");
|
||||
expect(strstr(wrong_schema_diag.message, "schema") != NULL, "unknown schema reported wrong diagnostic");
|
||||
|
||||
const char *failed_dump =
|
||||
"zero-graph v1\n"
|
||||
"origin source-text\n"
|
||||
"module \"main\"\n"
|
||||
"hash \"\"\n"
|
||||
"validation \"decoded\" failed\n"
|
||||
"diagnostic code:\"GRF001\" message:\"program graph construction failed\"\n";
|
||||
ZProgramGraph failed_artifact;
|
||||
ZDiag failed_artifact_diag = {0};
|
||||
expect(!z_program_graph_parse_dump(failed_dump, &failed_artifact, &failed_artifact_diag), "failed validation artifact parsed");
|
||||
expect(strstr(failed_artifact_diag.message, "failed validation") != NULL, "failed validation artifact reported wrong diagnostic");
|
||||
|
||||
ZBuf trailing_extra;
|
||||
zbuf_init(&trailing_extra);
|
||||
zbuf_append(&trailing_extra, dump.data);
|
||||
zbuf_append(&trailing_extra, "\nextra\n");
|
||||
ZProgramGraph trailing_artifact;
|
||||
ZDiag trailing_artifact_diag = {0};
|
||||
expect(!z_program_graph_parse_dump(trailing_extra.data, &trailing_artifact, &trailing_artifact_diag), "trailing content parsed");
|
||||
expect(strstr(trailing_artifact_diag.message, "unexpected content") != NULL, "trailing content reported wrong diagnostic");
|
||||
|
||||
zbuf_free(&trailing_extra);
|
||||
zbuf_free(&redump);
|
||||
z_program_graph_free(&parsed);
|
||||
zbuf_free(&dump);
|
||||
z_program_graph_free(&graph);
|
||||
puts("program graph smoke ok");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#include "zero.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void z_backend_blocker_set(ZBackendBlocker *blocker,
|
||||
const char *target,
|
||||
const char *object_format,
|
||||
const char *backend,
|
||||
const char *stage,
|
||||
const char *unsupported_feature) {
|
||||
if (!blocker) return;
|
||||
*blocker = (ZBackendBlocker){0};
|
||||
blocker->present = true;
|
||||
snprintf(blocker->target, sizeof(blocker->target), "%s", target ? target : "");
|
||||
snprintf(blocker->object_format, sizeof(blocker->object_format), "%s", object_format ? object_format : "");
|
||||
snprintf(blocker->backend, sizeof(blocker->backend), "%s", backend ? backend : "");
|
||||
snprintf(blocker->stage, sizeof(blocker->stage), "%s", stage ? stage : "");
|
||||
snprintf(blocker->unsupported_feature, sizeof(blocker->unsupported_feature), "%s", unsupported_feature ? unsupported_feature : "");
|
||||
}
|
||||
|
||||
bool z_write_binary_file(const char *path, const unsigned char *data, size_t len, ZDiag *diag) {
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (!file) {
|
||||
if (diag) snprintf(diag->message, sizeof(diag->message), "failed to write binary test file");
|
||||
return false;
|
||||
}
|
||||
size_t wrote = fwrite(data ? data : (const unsigned char *)"", 1, len, file);
|
||||
fclose(file);
|
||||
return wrote == len;
|
||||
}
|
||||
|
||||
void z_free_source(SourceInput *input) {
|
||||
if (!input) return;
|
||||
free(input->source_file);
|
||||
free(input->source);
|
||||
for (size_t i = 0; i < input->source_file_count; i++) free(input->source_files[i]);
|
||||
free(input->source_files);
|
||||
for (size_t i = 0; i < input->module_count; i++) {
|
||||
free(input->module_names[i]);
|
||||
free(input->module_paths[i]);
|
||||
}
|
||||
free(input->module_names);
|
||||
free(input->module_paths);
|
||||
for (size_t i = 0; i < input->symbol_count; i++) {
|
||||
free(input->symbol_modules[i]);
|
||||
free(input->symbol_kinds[i]);
|
||||
free(input->symbol_names[i]);
|
||||
}
|
||||
free(input->symbol_modules);
|
||||
free(input->symbol_kinds);
|
||||
free(input->symbol_names);
|
||||
*input = (SourceInput){0};
|
||||
}
|
||||
Reference in New Issue
Block a user