222 lines · 6.2 KB · C
RawDownload
// SPDX-License-Identifier: ISC/* * test_lua_plugin -- verify that Lua plugins load and tools get registered. * Only built at all when lib/'s lua feature is enabled (test/meson.build); * see the clmlua split in lib/meson.build. */#include <stdio.h>#include <stdlib.h>#include <string.h> #include <cjson/cJSON.h>#include <uv.h> #include "clm/clm.h"#include "clm/host_uv.h"#include "clm/internal.h"#include "clm/lua_plugin.h" static int failures; #define CHECK(cond, msg)                                                       \	do {                                                                    \		if (!(cond)) {                                                  \			fprintf(stderr, "FAIL: %s (%s:%d)\n", (msg), __FILE__, \			    __LINE__);                                          \			failures++;                                            \		}                                                              \	} while (0) static inttest_plugin_loads(void){	struct clm_agent *agent = NULL;	struct clm_lua_env *env = NULL;	uv_loop_t *loop = uv_default_loop();	struct clm_cfg cfg = {		.api_key = "test",		.base_url = "http://127.0.0.1:1/v1/chat/completions",		.provider = CLM_PROVIDER_OPENAI,		.model = "test",		.max_iterations = 1,		.stream = 0,	};	int r; 	struct clm_host *host = NULL;	r = clm_host_uv_new(loop, &host);	CHECK(r == 0, "host creation");	r = clm_agent_new(&cfg, host, NULL, NULL, &agent);	CHECK(r == 0, "agent creation");	if (r != 0)		return 1; 	r = clm_lua_env_new(agent, &env);	CHECK(r == 0, "lua env creation"); 	r = clm_lua_load_plugins(env, "plugins");	CHECK(r == 0, "plugin loading"); 	/* Verify the reverse_string tool was registered. */	int found = 0;	struct clm_tool *rt;	TAILQ_FOREACH(rt, &agent->tools, entries) {		if (strcmp(rt->name, "reverse_string") == 0) {			found = 1;			CHECK(rt->description != NULL, "tool has description");			CHECK(rt->params_schema != NULL, "tool has params_schema");			CHECK(rt->invoke != NULL, "tool has invoke function");			break;		}	}	CHECK(found, "reverse_string tool found in registry"); 	/* Verify the schema is valid JSON. */	if (found) {		TAILQ_FOREACH(rt, &agent->tools, entries) {			if (strcmp(rt->name, "reverse_string") != 0)				continue;			cJSON *schema = cJSON_Parse(rt->params_schema);			CHECK(schema != NULL, "params_schema is valid JSON");			if (schema) {				cJSON *props =				    cJSON_GetObjectItemCaseSensitive(schema, "properties");				CHECK(props != NULL, "schema has properties");				if (props) {					cJSON *text_prop =					    cJSON_GetObjectItemCaseSensitive(props, "text");					CHECK(text_prop != NULL,					    "schema has 'text' property");				}				cJSON_Delete(schema);			}		}	} 	clm_lua_env_free(env);	clm_agent_free(agent);	clm_host_uv_free(host);	return 0;} static inttest_nonexistent_dir(void){	struct clm_agent *agent = NULL;	struct clm_lua_env *env = NULL;	uv_loop_t *loop = uv_default_loop();	struct clm_cfg cfg = {		.api_key = "test",		.base_url = "http://127.0.0.1:1/v1/chat/completions",		.provider = CLM_PROVIDER_OPENAI,		.model = "test",		.max_iterations = 1,		.stream = 0,	};	int r; 	struct clm_host *host = NULL;	r = clm_host_uv_new(loop, &host);	CHECK(r == 0, "host creation");	r = clm_agent_new(&cfg, host, NULL, NULL, &agent);	CHECK(r == 0, "agent creation (nonexistent)");	if (r != 0)		return 1; 	r = clm_lua_env_new(agent, &env);	CHECK(r == 0, "lua env creation (nonexistent)"); 	/* Should succeed gracefully when directory doesn't exist. */	r = clm_lua_load_plugins(env, "/nonexistent/path/to/plugins");	CHECK(r == 0, "nonexistent dir returns 0"); 	clm_lua_env_free(env);	clm_agent_free(agent);	clm_host_uv_free(host);	return 0;} static inttool_registered(struct clm_agent *agent, const char *name){	struct clm_tool *t;	TAILQ_FOREACH(t, &agent->tools, entries)		if (strcmp(t->name, name) == 0)			return 1;	return 0;} /* * Load the self-test plugin dir, which contains one clean plugin plus three * that must fail to load gracefully: an explicit error(), an indirect nil * call, and an infinite loop at file scope (bounded by the load-time CPU cap). * The clean plugin's marker tool must be registered; reaching this point at * all proves the loop plugin did not hang the process. */static inttest_sandbox_and_load_failures(void){	struct clm_agent *agent = NULL;	struct clm_lua_env *env = NULL;	uv_loop_t *loop = uv_default_loop();	struct clm_cfg cfg = {		.api_key = "test",		.base_url = "http://127.0.0.1:1/v1/chat/completions",		.provider = CLM_PROVIDER_OPENAI,		.model = "test",		.max_iterations = 1,		.stream = 0,	};	int r; 	struct clm_host *host = NULL;	r = clm_host_uv_new(loop, &host);	CHECK(r == 0, "host creation");	r = clm_agent_new(&cfg, host, NULL, NULL, &agent);	CHECK(r == 0, "agent creation (sandbox)");	if (r != 0)		return 1; 	r = clm_lua_env_new(agent, &env);	CHECK(r == 0, "lua env creation (sandbox)"); 	/* Returns 0 even though individual plugins fail; failures are skipped.	 * Reaching the next line proves loop_at_load.lua did not hang. */	r = clm_lua_load_plugins(env, "test/plugins");	CHECK(r == 0, "self-test plugin dir loads (failures skipped, no hang)"); 	/* The clean, sandbox-asserting plugin loaded and registered its marker:	 * the sandbox denies os/io/require/load and exposes only safe libs. */	CHECK(tool_registered(agent, "sandbox_ok_marker"),	    "sandbox-clean plugin loaded (no unsafe globals reachable)"); 	/* The failing plugins registered nothing (they raised before/instead of	 * registering); they must not have leaked tools into the registry. */	CHECK(!tool_registered(agent, "fail_explicit_marker"),	    "explicit-error load failure registered no tool");	CHECK(!tool_registered(agent, "fail_indirect_marker"),	    "indirect-error load failure registered no tool");	CHECK(!tool_registered(agent, "loop_marker"),	    "infinite-loop load failure registered no tool"); 	clm_lua_env_free(env);	clm_agent_free(agent);	clm_host_uv_free(host);	return 0;} intmain(void){	printf("test_lua_plugin: running...\n");	test_plugin_loads();	test_nonexistent_dir();	test_sandbox_and_load_failures(); 	if (failures > 0) {		printf("test_lua_plugin: %d failure(s)\n", failures);		return 1;	}	printf("test_lua_plugin: PASS\n");	return 0;}