/* Tool capability metadata: platform declarations, availability probes and * the shadowing rule (an app-provided tool may take over a name whose * built-in cannot run on this host). */ #define _POSIX_C_SOURCE 200809L #include "cst_tools.h" #include #include #include static int nfail; #define EXPECT(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \ nfail++; \ } \ } while (0) static int echo_run(const cst_tool_ctx_t *ctx, const ctl_tool_call_t *c, cst_tool_result_t *r) { (void)ctx; (void)c; r->out = strdup("ran"); r->out_len = r->out ? strlen(r->out) : 0; r->exit_code = 0; return 0; } static int probe_fail(char *why, size_t why_sz) { snprintf(why, why_sz, "no notification daemon on this host"); return -1; } static const char *const ANDROID_ONLY[] = {"android", NULL}; static void test_platform_gating(void) { cst_tool_def_t d; memset(&d, 0, sizeof(d)); d.name = "meta_android_only"; d.desc = "Android-only test tool."; d.perm = CST_TP_ASK; d.perm_name = "notify"; d.platforms = ANDROID_ONLY; d.run = echo_run; d.owner = "test:meta"; EXPECT(cst_tool_register(&d) == 0, "register an android-only tool"); const cst_tool_def_t *t = cst_tool_find("meta_android_only"); EXPECT(t != NULL, "it is in the registry"); if (!t) return; EXPECT(strcmp(cst_tool_perm_name(t), "notify") == 0, "perm_name is kept"); setenv("CST_PLATFORM", "linux", 1); cst_tool_meta_reload(); char why[160]; EXPECT(cst_tool_available(t, why, sizeof(why)) != 0, "an android-only tool is unavailable on linux"); EXPECT(strstr(why, "android") != NULL, "and says what it is for"); EXPECT(!cst_tool_active(NULL, t), "so it is not offered at all"); /* the model must not be able to force it either: the call is answered with * an error result (run() reports 0 for a tool-level failure, the exit code * and the text carry it) */ ctl_tool_call_t call; memset(&call, 0, sizeof(call)); snprintf(call.name, sizeof(call.name), "meta_android_only"); cst_tool_result_t r; cst_tool_ctx_t ctx; memset(&ctx, 0, sizeof(ctx)); ctx.workspace_root = "."; EXPECT(cst_tool_run(&ctx, &call, &r) == 0 && r.out && r.exit_code != 0 && strstr(r.out, "cannot run here") != NULL, "calling it anyway is refused with a reason"); EXPECT(strstr(r.out, "android") != NULL, "the reason names the platform"); cst_tool_result_free(&r); setenv("CST_PLATFORM", "android", 1); cst_tool_meta_reload(); EXPECT(cst_tool_available(t, why, sizeof(why)) == 0, "available once the host is an android device"); EXPECT(cst_tool_active(NULL, t), "and offered to the model"); char *names = cst_tools_names(NULL); EXPECT(names && strstr(names, "meta_android_only") != NULL, "it shows up in the active tool names"); free(names); /* a Termux host counts as android; "termux" is not matched by "android" */ setenv("CST_PLATFORM", "termux", 1); cst_tool_meta_reload(); EXPECT(cst_tool_available(t, why, sizeof(why)) == 0, "a Termux host is an android device"); /* the desktop pseudo-platform covers linux / win / macos */ cst_tool_def_t desk; memset(&desk, 0, sizeof(desk)); desk.name = "meta_desktop"; desk.desc = "Desktop-only test tool."; desk.perm = CST_TP_READ; desk.platforms = CST_PLAT_DESKTOP; desk.run = echo_run; desk.owner = "test:meta"; EXPECT(cst_tool_register(&desk) == 0, "register a desktop-only tool"); const cst_tool_def_t *dt = cst_tool_find("meta_desktop"); setenv("CST_PLATFORM", "linux", 1); cst_tool_meta_reload(); EXPECT(dt && cst_tool_available(dt, why, sizeof(why)) == 0, "desktop on linux"); setenv("CST_PLATFORM", "android", 1); cst_tool_meta_reload(); EXPECT(dt && cst_tool_available(dt, why, sizeof(why)) != 0, "not on android"); } static void test_probe(void) { cst_tool_def_t d; memset(&d, 0, sizeof(d)); d.name = "meta_no_daemon"; d.desc = "A tool whose service is missing."; d.perm = CST_TP_ASK; d.available = probe_fail; d.run = echo_run; d.owner = "test:meta"; EXPECT(cst_tool_register(&d) == 0, "register a tool with a failing probe"); const cst_tool_def_t *t = cst_tool_find("meta_no_daemon"); char why[160]; setenv("CST_PLATFORM", "linux", 1); cst_tool_meta_reload(); EXPECT(t && cst_tool_available(t, why, sizeof(why)) != 0, "probe says no"); EXPECT(strstr(why, "notification daemon") != NULL, "the probe's own reason is passed on"); EXPECT(t && !cst_tool_active(NULL, t), "and it is not offered"); /* the cache answers the same way, and a reload re-probes */ EXPECT(t && cst_tool_available(t, NULL, 0) != 0, "cached answer"); cst_tool_meta_reload(); EXPECT(t && cst_tool_available(t, why, sizeof(why)) != 0, "re-probed answer"); } static void test_shadowing(void) { /* A tool the app provides may take a built-in name when that built-in * cannot run here; the app's entry then shadows it. */ cst_tool_def_t d; memset(&d, 0, sizeof(d)); d.name = "clipboard_read"; d.desc = "Read the clipboard (provided by the app)."; d.perm = CST_TP_READ; d.perm_name = "read"; d.run = echo_run; d.owner = "app:test"; int registered = cst_tool_register(&d) == 0; const cst_tool_def_t *t = cst_tool_find("clipboard_read"); EXPECT(t != NULL, "clipboard_read stays findable either way"); if (registered) { EXPECT(t && t->owner && strcmp(t->owner, "app:test") == 0, "the app's tool shadows the unavailable built-in"); EXPECT(t && cst_tool_active(NULL, t), "and it is offered"); EXPECT(cst_tool_unregister_owner("app:test") == 1, "dropping it again"); t = cst_tool_find("clipboard_read"); EXPECT(t && (t->owner == NULL || strcmp(t->owner, "app:test") != 0), "the built-in is back"); } else { EXPECT(t && cst_tool_available(t, NULL, 0) == 0, "a name is only taken by a tool that can run here"); } } static void test_perm_names(void) { const cst_tool_def_t *t = cst_tool_find("delete_file"); EXPECT(t && strcmp(cst_tool_perm_name(t), "delete") == 0, "delete_file is a delete, not just a write"); t = cst_tool_find("read_file"); EXPECT(t && strcmp(cst_tool_perm_name(t), "read") == 0, "read_file is read"); t = cst_tool_find("run_shell"); EXPECT(t && strcmp(cst_tool_perm_name(t), "shell") == 0, "carried by class"); t = cst_tool_find("clipboard_write"); EXPECT(t && strcmp(cst_tool_perm_name(t), "write") == 0, "clipboard_write"); EXPECT(cst_platform_name() != NULL && cst_platform_name()[0], "host platform has a name"); } int main(void) { test_platform_gating(); test_probe(); test_shadowing(); test_perm_names(); cst_tool_unregister_owner("test:meta"); cst_tool_unregister_owner("app:test"); if (nfail) { fprintf(stderr, "test_tool_meta: %d failed\n", nfail); return 1; } printf("test_tool_meta: OK\n"); return 0; }