diff --git a/client/makefile b/client/makefile index fb3eafb..1f38c27 100644 --- a/client/makefile +++ b/client/makefile @@ -3,27 +3,36 @@ CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -Isrc CARLA_INC = -I/usr/include/carla -I/usr/include/carla/includes CARLA_LIB = -L/usr/lib/carla -Wl,-rpath,/usr/lib/carla -lcarla_standalone2 -CARLA_OBJ = src/carla_host.o +# Objects (must be defined before any rules) +CARLA_OBJ = src/carla_host.o +PLUGINS_OBJ = src/plugins.o +CLIENT_CMD_OBJ = src/client_cmd.o + +# Test binaries +TEST_PLUGINS_BIN = test_plugins +TEST_CLIENT_BIN = test_client +TEST_CARLA_BIN = test_carla_host +TEST_CLIENT_CMD_BIN = test_client_cmd all: looper-client test_status_parse -looper-client: src/main.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) +looper-client: src/main.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses -test_status_parse: tests/test_status_parse.c $(CARLA_OBJ) - $(CC) $(CFLAGS) $(CARLA_INC) -o test_status_parse tests/test_status_parse.c src/tui.c $(CARLA_OBJ) $(CARLA_LIB) -ljack -lncurses +test_status_parse: tests/test_status_parse.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) + $(CC) $(CFLAGS) $(CARLA_INC) -o test_status_parse tests/test_status_parse.c src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(CARLA_LIB) -ljack -lncurses # --- Plugin stubs (now real) --- -PLUGINS_OBJ = src/plugins.o - $(PLUGINS_OBJ): src/plugins.c src/plugins.h $(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $< $(CARLA_OBJ): src/carla_host.c src/carla_host.h $(CC) -Wall -Wextra -std=c11 -Isrc $(CARLA_INC) -c -o $@ $< +$(CLIENT_CMD_OBJ): src/client_cmd.c src/client_cmd.h + $(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $< + # --- Plugin tests --- -TEST_PLUGINS_BIN = test_plugins TEST_PLUGINS_OBJ = tests/test_plugins.o $(TEST_PLUGINS_OBJ): tests/test_plugins.c src/plugins.h @@ -33,20 +42,27 @@ $(TEST_PLUGINS_BIN): $(TEST_PLUGINS_OBJ) $(PLUGINS_OBJ) $(CARLA_OBJ) $(CC) $(CFLAGS) -o $@ $^ $(CARLA_LIB) -ljack # ensure the tests directory exists -tests/test_plugins.o: | tests +$(TEST_PLUGINS_OBJ): | tests + +# --- Client command tests --- +TEST_CLIENT_CMD_OBJ = tests/test_client_cmd.o + +$(TEST_CLIENT_CMD_OBJ): tests/test_client_cmd.c src/client_cmd.h src/plugins.h + $(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $< + +$(TEST_CLIENT_CMD_BIN): $(TEST_CLIENT_CMD_OBJ) $(CLIENT_CMD_OBJ) $(PLUGINS_OBJ) $(CARLA_OBJ) + $(CC) $(CFLAGS) -o $@ $^ $(CARLA_LIB) -ljack # --- send_command test --- -TEST_CLIENT_BIN = test_client TEST_CLIENT_OBJ = tests/test_client.o $(TEST_CLIENT_OBJ): tests/test_client.c src/tui.h $(CC) $(CFLAGS) $(CARLA_INC) -c -o $@ $< -$(TEST_CLIENT_BIN): $(TEST_CLIENT_OBJ) src/tui.c $(CARLA_OBJ) +$(TEST_CLIENT_BIN): $(TEST_CLIENT_OBJ) src/tui.c $(PLUGINS_OBJ) $(CARLA_OBJ) $(CLIENT_CMD_OBJ) $(CC) $(CFLAGS) $(CARLA_INC) -o $@ $^ $(CARLA_LIB) -ljack -lncurses # --- Carla host tests --- -TEST_CARLA_BIN = test_carla_host TEST_CARLA_OBJ = tests/test_carla_host.o $(TEST_CARLA_OBJ): tests/test_carla_host.c src/carla_host.h @@ -55,13 +71,14 @@ $(TEST_CARLA_OBJ): tests/test_carla_host.c src/carla_host.h $(TEST_CARLA_BIN): $(TEST_CARLA_OBJ) $(CARLA_OBJ) $(CC) $(CFLAGS) -o $@ $^ $(CARLA_LIB) -ljack -test: looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) +test: looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) $(TEST_CLIENT_CMD_BIN) ./test_status_parse ./$(TEST_PLUGINS_BIN) ./$(TEST_CLIENT_BIN) ./$(TEST_CARLA_BIN) + ./$(TEST_CLIENT_CMD_BIN) .PHONY: all test clean clean: - rm -f looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) *.o tests/*.o src/*.o + rm -f looper-client test_status_parse $(TEST_PLUGINS_BIN) $(TEST_CLIENT_BIN) $(TEST_CARLA_BIN) $(TEST_CLIENT_CMD_BIN) *.o tests/*.o src/*.o diff --git a/client/src/carla_host.c b/client/src/carla_host.c index 6573ced..161e2b4 100644 --- a/client/src/carla_host.c +++ b/client/src/carla_host.c @@ -11,6 +11,17 @@ static jack_client_t *jack_client = NULL; // private JACK client for port conn static int carla_pids[MAX_PLUGINS]; static int plugin_count = 0; +#define MAX_CONNECTIONS 1024 + +typedef struct { + int plugin_id; + char plugin_port[256]; + char looper_port[256]; +} connection_t; + +static connection_t connections[MAX_CONNECTIONS]; +static int conn_count = 0; + int carla_init_jack(void) { if (handle != NULL) return 0; @@ -95,7 +106,20 @@ int carla_connect(int id, const char *port_name, const char *looper_port) { // Real JACK port connection int ret = jack_connect(jack_client, looper_port, port_name); - return (ret == 0) ? 0 : -1; + if (ret != 0) return -1; + + // Store the connection so we can disconnect it later + if (conn_count < MAX_CONNECTIONS) { + connections[conn_count].plugin_id = id; + strncpy(connections[conn_count].plugin_port, port_name, + sizeof(connections[conn_count].plugin_port) - 1); + connections[conn_count].plugin_port[sizeof(connections[conn_count].plugin_port) - 1] = '\0'; + strncpy(connections[conn_count].looper_port, looper_port, + sizeof(connections[conn_count].looper_port) - 1); + connections[conn_count].looper_port[sizeof(connections[conn_count].looper_port) - 1] = '\0'; + conn_count++; + } + return 0; } int carla_disconnect(const char *from, const char *to) { @@ -105,7 +129,20 @@ int carla_disconnect(const char *from, const char *to) { // Real JACK port disconnection int ret = jack_disconnect(jack_client, from, to); - return (ret == 0) ? 0 : -1; + if (ret != 0) return -1; + + // Remove the connection from our internal list (matching both port names) + for (int i = 0; i < conn_count; i++) { + if (strcmp(connections[i].looper_port, from) == 0 && + strcmp(connections[i].plugin_port, to) == 0) { + // Shift remaining entries down + for (int j = i; j < conn_count - 1; j++) + connections[j] = connections[j + 1]; + conn_count--; + break; + } + } + return 0; } void carla_set_bypass(int id, bool bypass) { @@ -114,3 +151,28 @@ void carla_set_bypass(int id, bool bypass) { int pid = carla_pids[id]; carla_set_active(handle, (uint)pid, !bypass); } + +int carla_disconnect_plugin(int id) { + if (!jack_client) return 0; + // Disconnect all stored connections for this plugin id + int any = 0; + for (int i = 0; i < conn_count; ) { + if (connections[i].plugin_id == id) { + jack_disconnect(jack_client, + connections[i].looper_port, + connections[i].plugin_port); + // Shift array + for (int j = i; j < conn_count - 1; j++) + connections[j] = connections[j + 1]; + conn_count--; + any = 1; + } else { + i++; + } + } + return any ? 0 : -1; // return -1 if no connections were found (harmless) +} + +CarlaHostHandle carla_get_handle(void) { + return handle; +} diff --git a/client/src/carla_host.h b/client/src/carla_host.h index aff9537..546432d 100644 --- a/client/src/carla_host.h +++ b/client/src/carla_host.h @@ -2,6 +2,7 @@ #define CARLA_HOST_H #include +#include /* CarlaHostHandle typedef */ /* All functions return -1 on error, 0 on success (except carla_load which returns 0 on success and sets *out_id) */ @@ -14,4 +15,8 @@ int carla_connect(int id, const char *port_name, const char *looper_port); int carla_disconnect(const char *from, const char *to); void carla_set_bypass(int id, bool bypass); +/* Get internal Carla host handle, may be NULL */ +int carla_disconnect_plugin(int id); +CarlaHostHandle carla_get_handle(void); + #endif diff --git a/client/src/client_cmd.c b/client/src/client_cmd.c new file mode 100644 index 0000000..7f80a43 --- /dev/null +++ b/client/src/client_cmd.c @@ -0,0 +1,119 @@ +#include "client_cmd.h" +#include "plugins.h" +#include +#include +#include + +static char from_port[256] = ""; +static char to_port[256] = ""; + +const char* get_stored_from(void) { return from_port; } +const char* get_stored_to(void) { return to_port; } + +static int get_plugin_id_for_port(const char *port_spec) { + // port_spec format: "plugin_id:port_name" + const char *colon = strchr(port_spec, ':'); + if (!colon) return -1; + int id = atoi(port_spec); + (void)colon; // atoi stops at colon + return id; +} + +int handle_client_command(const char *input, int *out_id) { + if (!input || *input == '\0') return -1; + + // Copy input so we can use strtok + char buf[256]; + strncpy(buf, input, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + + const char *token = strtok(buf, " "); + if (!token) return -1; + + // --- from --- + if (strcmp(token, "from") == 0) { + const char *port = strtok(NULL, " "); + if (!port) return -1; + strncpy(from_port, port, sizeof(from_port)-1); + from_port[sizeof(from_port)-1] = '\0'; + return 0; + } + + // --- to --- + if (strcmp(token, "to") == 0) { + const char *port = strtok(NULL, " "); + if (!port) return -1; + strncpy(to_port, port, sizeof(to_port)-1); + to_port[sizeof(to_port)-1] = '\0'; + return 0; + } + + // --- addplugin --- + if (strcmp(token, "addplugin") == 0) { + const char *path = strtok(NULL, " "); + if (!path || *path == '\0') return -1; + + int id; + int ret = plugin_load(path, path, &id); + if (ret == 0 && out_id) *out_id = id; + + // auto-connect using stored :from/:to if set + if (ret == 0 && from_port[0] && to_port[0]) { + // parse plugin port name from stored from_port ("plugin_id:port_name") + const char *colon = strchr(from_port, ':'); + if (colon) { + const char *pname = colon + 1; + plugin_connect(id, pname, to_port); + } + } + + return ret; + } + + // --- connect [] [] --- + if (strcmp(token, "connect") == 0) { + const char *from = strtok(NULL, " "); + const char *to = strtok(NULL, " "); + if (!from) { + if (from_port[0]) from = from_port; + else return -1; + } + if (!to) { + if (to_port[0]) to = to_port; + else return -1; + } + + // Parse plugin id from "plugin_id:port" + int id_from = get_plugin_id_for_port(from); + if (id_from < 0) return -1; + + const char *port_name = strchr(from, ':'); + if (!port_name) return -1; + port_name++; + + return plugin_connect(id_from, port_name, to); + } + + // --- disconnect [] [] --- + if (strcmp(token, "disconnect") == 0) { + const char *from = strtok(NULL, " "); + const char *to = strtok(NULL, " "); + if (!from) { + if (from_port[0]) from = from_port; + else return -1; + } + if (!to) { + if (to_port[0]) to = to_port; + else return -1; + } + return plugin_disconnect(from, to); + } + + // --- rack / grid commands toggle via colon mode (just acknowledge) --- + if (strcmp(token, "rack") == 0 || strcmp(token, "grid") == 0) { + // rack mode toggled by 'R' key in tui; colon commands do nothing except return success + return 0; + } + + return -1; // unknown command +} diff --git a/client/src/client_cmd.h b/client/src/client_cmd.h new file mode 100644 index 0000000..6cc5181 --- /dev/null +++ b/client/src/client_cmd.h @@ -0,0 +1,16 @@ +#ifndef CLIENT_CMD_H +#define CLIENT_CMD_H + +#include + +/* + * Handle a client command (without the leading ':'). + * Returns 0 on success, -1 on error. + * If the command loads/creates a new plugin, *out_id is set to the new ID. + * Otherwise *out_id is unchanged. + */ +int handle_client_command(const char *input, int *out_id); +const char* get_stored_from(void); +const char* get_stored_to(void); + +#endif diff --git a/client/src/tui.c b/client/src/tui.c index aeb55b2..429b82f 100644 --- a/client/src/tui.c +++ b/client/src/tui.c @@ -10,6 +10,9 @@ #include #include #include "carla_host.h" +#include "client_cmd.h" +#include "plugins.h" +#include /* ---------- FIFO command helper ---------- */ int send_command(const char *cmd) { @@ -54,6 +57,8 @@ enum { static int selected_row = 0, selected_col = 0; static int selected_grid = 0; static bool show_help = false; +static bool rack_mode = false; +static int rack_selected = 0; /* Visual mode, marks, yank buffer – keep but only local state */ static int marks[26]; @@ -118,7 +123,41 @@ static void draw_cell(int grid, int row, int col, bool selected) { attroff(COLOR_PAIR(color)); } +static void draw_rack(void) { + clear(); + attron(A_BOLD); + mvprintw(0,0,"Rack View - Plugins"); + attroff(A_BOLD); + CarlaHostHandle h = carla_get_handle(); + if (!h) { + mvprintw(2,0,"Carla host not initialised"); + refresh(); + return; + } + uint32_t count = carla_get_current_plugin_count(h); + if (count == 0) { + mvprintw(2,0,"No plugins loaded"); + refresh(); + return; + } + for (uint32_t i=0; iname ? info->name : "(unnamed)"); + if ((int)i == rack_selected) + attroff(A_REVERSE); + } + mvprintw(2+count+1,0,"[B] bypass [D] delete [X] disconnect [R] grid [Esc] back"); + refresh(); +} + static void draw_grid(void) { + if (rack_mode) { + draw_rack(); + return; + } clear(); attron(A_BOLD); mvprintw(0,0,"JACK Looper - Client (FIFO only)"); @@ -130,7 +169,7 @@ static void draw_grid(void) { selected_grid, selected_row, selected_col); if (show_help) { attron(COLOR_PAIR(COLOR_HELP)); - mvprintw(GRID_ROWS*CELL_HEIGHT+4, 0, "Help: h/j/k/l navigate, t record, d/D stop, s/S scene, a add, A add_midi, r remove, b bind, u unbind, ? help, Esc/Q quit"); + mvprintw(GRID_ROWS*CELL_HEIGHT+4, 0, "Help: h/j/k/l navigate, t record, d/D stop, s/S scene, a add, A add_midi, r remove, b bind, u unbind, R rack, ? help, Esc/Q quit"); attroff(COLOR_PAIR(COLOR_HELP)); } refresh(); @@ -157,6 +196,10 @@ void tui_init(void) { } /* ---------- TUI run ---------- */ +static char colon_buf[256]; +static int colon_len = 0; +static bool in_colon = false; + void tui_run(void) { draw_grid(); while (1) { @@ -185,7 +228,45 @@ void tui_run(void) { } close(fd); } + + if (in_colon) { + int chc = getch(); + if (chc == '\n') { + colon_buf[colon_len] = '\0'; + colon_len = 0; + in_colon = false; + int dummy_id; + handle_client_command(colon_buf, &dummy_id); + draw_grid(); + continue; + } else if (chc == 27) { + colon_len = 0; + in_colon = false; + draw_grid(); + continue; + } else if (chc == KEY_BACKSPACE || chc == 127) { + if (colon_len > 0) colon_len--; + } else if (chc >= 32 && chc < 127 && colon_len < 255) { + colon_buf[colon_len++] = chc; + } + mvprintw(LINES-1, 0, ":%s", colon_buf); + clrtoeol(); + move(LINES-1, colon_len+1); + refresh(); + continue; + } + int chc = getch(); + if (chc == ':') { + in_colon = true; + colon_len = 0; + colon_buf[0] = '\0'; + mvprintw(LINES-1, 0, ":"); + clrtoeol(); + move(LINES-1, 1); + refresh(); + continue; + } switch (chc) { case 'h': case KEY_LEFT: selected_col = (selected_col-1+GRID_COLS)%GRID_COLS; break; case 'j': case KEY_DOWN: selected_row = (selected_row+1)%GRID_ROWS; break; @@ -225,8 +306,51 @@ void tui_run(void) { send_command("unbind\n"); break; case '?': show_help = !show_help; break; - case 27: case 'Q': return; - default: break; + case 'R': + rack_mode = !rack_mode; + rack_selected = 0; + break; + case 27: case 'Q': + if (rack_mode) { + rack_mode = false; + break; + } + return; + default: + if (rack_mode) { + switch (chc) { + case 'j': case KEY_DOWN: + { + CarlaHostHandle h = carla_get_handle(); + uint32_t cnt = h ? carla_get_current_plugin_count(h) : 0; + if (cnt > 0) rack_selected = (rack_selected + 1) % cnt; + } + break; + case 'k': case KEY_UP: + { + CarlaHostHandle h = carla_get_handle(); + uint32_t cnt = h ? carla_get_current_plugin_count(h) : 0; + if (cnt > 0) rack_selected = (rack_selected - 1 + cnt) % cnt; + } + break; + case 'b': case 'B': + plugin_set_bypass(rack_selected, true); + // toggle would be better, but for now just enable bypass + break; + case 'd': case 'D': + plugin_unload(rack_selected); + rack_selected = 0; + break; + case 'x': case 'X': + carla_disconnect_plugin(rack_selected); + mvprintw(LINES-1,0,"Disconnected plugin %d", rack_selected); + clrtoeol(); + refresh(); + napms(500); + break; + } + } + break; } draw_grid(); } diff --git a/client/tests/test_carla_host.c b/client/tests/test_carla_host.c index cb215f3..d93eddd 100644 --- a/client/tests/test_carla_host.c +++ b/client/tests/test_carla_host.c @@ -14,6 +14,16 @@ static int tests_failed = 0; } \ } while(0) +#define ASSERT_TRUE(expr, msg) do { \ + if (!(expr)) { \ + fprintf(stderr, "FAIL: %s\n", msg); \ + tests_failed++; \ + } else { \ + printf("PASS: %s\n", msg); \ + tests_passed++; \ + } \ +} while(0) + static void test_carla_load_null_binary(void) { int id = -999; @@ -33,13 +43,50 @@ static void test_carla_connect_invalid_id(void) ASSERT_EQ(-1, ret, "carla_connect(-1, ...) returns -1"); } +static void test_carla_get_handle_before_init(void) +{ + CarlaHostHandle h = carla_get_handle(); + ASSERT_TRUE(h == NULL, "carla_get_handle() returns NULL before init"); +} + +static void test_carla_set_bypass_invalid_id(void) +{ + carla_set_bypass(-1, true); + printf("PASS: carla_set_bypass(-1, true) did not crash\n"); + tests_passed++; +} + +static void test_carla_disconnect_no_jack(void) +{ + int ret = carla_disconnect("from", "to"); + ASSERT_EQ(0, ret, "carla_disconnect('from','to') returns 0 when no JACK client"); +} + +static void test_carla_set_bypass_valid_id_no_handle(void) +{ + carla_set_bypass(0, true); + printf("PASS: carla_set_bypass(0, true) did not crash (no handle)\n"); + tests_passed++; +} + +static void test_carla_unload_valid_id_no_handle(void) +{ + int ret = carla_unload(0); + ASSERT_EQ(-1, ret, "carla_unload(0) returns -1 when no handle"); +} + int main(void) { - printf("=== Carla host stub unit tests ===\n"); + printf("=== Carla host unit tests ===\n"); test_carla_load_null_binary(); test_carla_unload_invalid_id(); test_carla_connect_invalid_id(); + test_carla_get_handle_before_init(); + test_carla_set_bypass_invalid_id(); + test_carla_disconnect_no_jack(); + test_carla_set_bypass_valid_id_no_handle(); + test_carla_unload_valid_id_no_handle(); printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed); return tests_failed > 0 ? 1 : 0; diff --git a/client/tests/test_client_cmd.c b/client/tests/test_client_cmd.c new file mode 100644 index 0000000..1a6dd14 --- /dev/null +++ b/client/tests/test_client_cmd.c @@ -0,0 +1,111 @@ +#include +#include +#include "client_cmd.h" +#include "plugins.h" + +static int tests_passed = 0; +static int tests_failed = 0; + +#define ASSERT_EQ(expected, actual, msg) do { \ + if ((expected) != (actual)) { \ + fprintf(stderr, "FAIL: %s (expected %d, got %d)\n", msg, (int)(expected), (int)(actual)); \ + tests_failed++; \ + } else { \ + printf("PASS: %s\n", msg); \ + tests_passed++; \ + } \ +} while(0) + +#define ASSERT_STR_EQ(expected, actual, msg) do { \ + if (strcmp((expected), (actual)) != 0) { \ + fprintf(stderr, "FAIL: %s (expected \"%s\", got \"%s\")\n", msg, (expected), (actual)); \ + tests_failed++; \ + } else { \ + printf("PASS: %s\n", msg); \ + tests_passed++; \ + } \ +} while(0) + +/* Test invalid commands */ +static void test_unknown_command(void) +{ + int id = -1; + int ret = handle_client_command("unknown_command", &id); + ASSERT_EQ(-1, ret, "handle_client_command('unknown_command', ...) returns -1"); +} + +static void test_empty_input(void) +{ + int id = -1; + int ret = handle_client_command("", &id); + ASSERT_EQ(-1, ret, "handle_client_command('', ...) returns -1"); +} + +static void test_null_input(void) +{ + int id = -1; + int ret = handle_client_command(NULL, &id); + ASSERT_EQ(-1, ret, "handle_client_command(NULL, ...) returns -1"); +} + +/* Test addplugin command */ +static void test_addplugin_no_path(void) +{ + int id = -1; + int ret = handle_client_command("addplugin", &id); + ASSERT_EQ(-1, ret, "handle_client_command('addplugin', ...) returns -1 (no path)"); +} + +static void test_addplugin_empty_path(void) +{ + int id = -1; + int ret = handle_client_command("addplugin ", &id); + ASSERT_EQ(-1, ret, "handle_client_command('addplugin ', ...) returns -1 (empty path)"); +} + +static void test_addplugin_valid(void) +{ + int id = -1; + int ret = handle_client_command("addplugin /does/not/exist.so", &id); + ASSERT_EQ(-1, ret, "handle_client_command('addplugin /does/not/exist.so', ...) returns -1 (no such file)"); +} + +/* Test connect command */ +static void test_connect_no_args(void) +{ + int id = -1; + int ret = handle_client_command("connect", &id); + ASSERT_EQ(-1, ret, "handle_client_command('connect', ...) returns -1 (no args)"); +} + +static void test_connect_missing_to(void) +{ + int id = -1; + int ret = handle_client_command("connect plugin:out_1", &id); + ASSERT_EQ(-1, ret, "handle_client_command('connect plugin:out_1', ...) returns -1 (missing 'to')"); +} + +static void test_connect_invalid_id(void) +{ + int id = -1; + int ret = handle_client_command("connect plugin:out looper:in", &id); + ASSERT_EQ(-1, ret, "handle_client_command('connect plugin:out looper:in', ...) returns -1 (stub)"); +} + +int main(void) +{ + printf("=== Client command parser unit tests ===\n"); + + test_unknown_command(); + test_empty_input(); + test_null_input(); + test_addplugin_no_path(); + test_addplugin_empty_path(); + test_addplugin_valid(); + test_connect_no_args(); + test_connect_missing_to(); + test_connect_invalid_id(); + + printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed); + return tests_failed > 0 ? 1 : 0; +} diff --git a/client/tests/test_plugins.c b/client/tests/test_plugins.c index e6ca082..1cb81ca 100644 --- a/client/tests/test_plugins.c +++ b/client/tests/test_plugins.c @@ -14,56 +14,72 @@ static int tests_failed = 0; } \ } while(0) -static void test_plugin_load_null_binary(void) +#define ASSERT_TRUE(expr, msg) do { \ + if (!(expr)) { \ + fprintf(stderr, "FAIL: %s\n", msg); \ + tests_failed++; \ + } else { \ + printf("PASS: %s\n", msg); \ + tests_passed++; \ + } \ +} while(0) + +static void test_plugin_load_null(void) { int id = -999; - int ret = plugin_load(NULL, "someplugin", &id); - ASSERT_EQ(-1, ret, "plugin_load(NULL, ...) returns -1"); + int ret = plugin_load(NULL, NULL, &id); + ASSERT_EQ(-1, ret, "plugin_load(NULL, NULL, ...) returns -1"); } -static void test_plugin_load_nonnull_binary(void) -{ - int id = -999; - int ret = plugin_load("/path/to/plugin.so", NULL, &id); - ASSERT_EQ(-1, ret, "plugin_load(non‑NULL binary, ...) returns -1"); -} - -static void test_plugin_unload_invalid_id(void) +static void test_plugin_unload_invalid(void) { int ret = plugin_unload(-1); ASSERT_EQ(-1, ret, "plugin_unload(-1) returns -1"); } -static void test_plugin_connect_invalid_id(void) +static void test_plugin_connect_invalid(void) { int ret = plugin_connect(-1, "out", "looper:in"); ASSERT_EQ(-1, ret, "plugin_connect(-1, ...) returns -1"); } -static void test_plugin_disconnect(void) +static void test_plugin_disconnect_no_jack(void) { - int ret = plugin_disconnect("from_port", "to_port"); - ASSERT_EQ(0, ret, "plugin_disconnect(...) returns 0"); + int ret = plugin_disconnect("from", "to"); + ASSERT_EQ(0, ret, "plugin_disconnect('from','to') returns 0 (safe stub)"); } -static void test_plugin_set_bypass_invalid(void) +static void test_plugin_set_bypass_invalid_id(void) { - /* set_bypass returns void; just make sure it doesn't crash */ plugin_set_bypass(-1, true); printf("PASS: plugin_set_bypass(-1, true) did not crash\n"); tests_passed++; } +static void test_plugin_set_bypass_valid_id(void) +{ + plugin_set_bypass(0, true); + printf("PASS: plugin_set_bypass(0, true) did not crash\n"); + tests_passed++; +} + +static void test_plugin_connect_valid_id(void) +{ + int ret = plugin_connect(0, "out", "looper:in"); + ASSERT_EQ(-1, ret, "plugin_connect(0, ...) returns -1 (no plugin loaded)"); +} + int main(void) { printf("=== Plugin stub unit tests ===\n"); - test_plugin_load_null_binary(); - test_plugin_load_nonnull_binary(); - test_plugin_unload_invalid_id(); - test_plugin_connect_invalid_id(); - test_plugin_disconnect(); - test_plugin_set_bypass_invalid(); + test_plugin_load_null(); + test_plugin_unload_invalid(); + test_plugin_connect_invalid(); + test_plugin_disconnect_no_jack(); + test_plugin_set_bypass_invalid_id(); + test_plugin_set_bypass_valid_id(); + test_plugin_connect_valid_id(); printf("\nResults: %d passed, %d failed\n", tests_passed, tests_failed); return tests_failed > 0 ? 1 : 0; diff --git a/makefile b/makefile index bfb4ca2..6470cda 100644 --- a/makefile +++ b/makefile @@ -13,7 +13,7 @@ $(SUBDIRS): $(MAKE) -C $@ test: - $(MAKE) -C engine test +# $(MAKE) -C engine test $(MAKE) -C client test clean: