feat: add TUI client with FIFO communication and status display
This commit is contained in:
committed by
Loic Coenen (aider)
parent
5341cb676a
commit
971372eac9
BIN
engine/integration_test
Executable file
BIN
engine/integration_test
Executable file
Binary file not shown.
BIN
engine/looper
Executable file
BIN
engine/looper
Executable file
Binary file not shown.
@@ -13,13 +13,17 @@ src/%.o: src/%.c
|
||||
|
||||
integration: looper tests/integration.c
|
||||
$(CC) $(CFLAGS) -o integration_test tests/integration.c -ljack -lm
|
||||
|
||||
test_status_fifo: looper tests/test_status_fifo.c
|
||||
$(CC) $(CFLAGS) -o test_status_fifo tests/test_status_fifo.c -ljack -lm
|
||||
|
||||
test: integration test_status_fifo
|
||||
./test_status_fifo
|
||||
./integration_test
|
||||
|
||||
test: integration
|
||||
|
||||
.PHONY: clean integration test
|
||||
.PHONY: clean integration test_status_fifo test
|
||||
clean:
|
||||
rm -f looper integration_test src/*.o
|
||||
rm -f looper integration_test test_status_fifo src/*.o
|
||||
|
||||
check:
|
||||
cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem --suppress=normalCheckLevelMaxBranches src/*.c --library=posix
|
||||
|
||||
BIN
engine/src/channel.o
Normal file
BIN
engine/src/channel.o
Normal file
Binary file not shown.
BIN
engine/src/looper.o
Normal file
BIN
engine/src/looper.o
Normal file
Binary file not shown.
BIN
engine/src/main.o
Normal file
BIN
engine/src/main.o
Normal file
Binary file not shown.
BIN
engine/src/midi.o
Normal file
BIN
engine/src/midi.o
Normal file
Binary file not shown.
BIN
engine/src/pipe.o
Normal file
BIN
engine/src/pipe.o
Normal file
Binary file not shown.
BIN
engine/src/queue.o
Normal file
BIN
engine/src/queue.o
Normal file
Binary file not shown.
BIN
engine/test_status_fifo
Executable file
BIN
engine/test_status_fifo
Executable file
Binary file not shown.
16
engine/tests/makefile
Normal file
16
engine/tests/makefile
Normal file
@@ -0,0 +1,16 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -I../src -I$(JACK_CFLAGS)
|
||||
LDFLAGS = -ljack -lpthread -lm
|
||||
|
||||
all: test_status_fifo
|
||||
|
||||
test_status_fifo: test_status_fifo.c ../src/looper.c ../src/channel.c ../src/midi.c ../src/queue.c ../src/pipe.c
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
test: test_status_fifo
|
||||
./test_status_fifo
|
||||
|
||||
.PHONY: all test clean
|
||||
|
||||
clean:
|
||||
rm -f test_status_fifo
|
||||
@@ -6,33 +6,10 @@
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#define STATUS_FIFO "/tmp/looper_status"
|
||||
#define CMD_FIFO "/tmp/looper_cmd"
|
||||
|
||||
static int write_cmd(const char *cmd) {
|
||||
int fd = open(CMD_FIFO, O_WRONLY | O_NONBLOCK);
|
||||
if (fd < 0) return -1;
|
||||
size_t len = strlen(cmd);
|
||||
int n = write(fd, cmd, len);
|
||||
if (n == (int)len && cmd[len-1] != '\n')
|
||||
write(fd, "\n", 1);
|
||||
close(fd);
|
||||
return (n >= 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
static int read_status_line(char *buf, size_t bufsize) {
|
||||
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0) return -1;
|
||||
FILE *f = fdopen(fd, "r");
|
||||
if (!f) { close(fd); return -1; }
|
||||
if (fgets(buf, bufsize, f) == NULL) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
#define CMD_FIFO "/tmp/looper_cmd"
|
||||
|
||||
static pid_t start_looper(void) {
|
||||
pid_t pid = fork();
|
||||
@@ -47,33 +24,83 @@ static pid_t start_looper(void) {
|
||||
return pid;
|
||||
}
|
||||
|
||||
/* Drain any stale data from the status FIFO */
|
||||
static void drain_fifo(void) {
|
||||
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0) return;
|
||||
char buf[4096];
|
||||
while (read(fd, buf, sizeof(buf)) > 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* Read the first status line with a timeout (milliseconds).
|
||||
* Returns 0 on success, -1 on timeout/error. */
|
||||
static int read_status_line_timeout(char *buf, size_t bufsize, int timeout_ms) {
|
||||
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
|
||||
if (fd < 0) return -1;
|
||||
|
||||
fd_set set;
|
||||
struct timeval tv;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(fd, &set);
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
int ret = select(fd + 1, &set, NULL, NULL, &tv);
|
||||
if (ret <= 0) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int n = read(fd, buf, bufsize - 1);
|
||||
close(fd);
|
||||
if (n <= 0) return -1;
|
||||
buf[n] = '\0';
|
||||
|
||||
/* keep only the first line */
|
||||
char *nl = strchr(buf, '\n');
|
||||
if (nl) *nl = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_status_after_record(void) {
|
||||
printf("Test: status FIFO reports recording state\n");
|
||||
printf("Test: status FIFO reports RECORD state after record command\n");
|
||||
pid_t pid = start_looper();
|
||||
if (pid < 0) return 1;
|
||||
usleep(200000);
|
||||
if (write_cmd("record 0") != 0) {
|
||||
fprintf(stderr, " FAIL: cannot write record command\n");
|
||||
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
usleep(500000);
|
||||
char line[256];
|
||||
if (read_status_line(line, sizeof(line)) != 0) {
|
||||
fprintf(stderr, " FAIL: cannot read status line\n");
|
||||
|
||||
/* Give looper time to start main loop and begin writing status */
|
||||
usleep(1500000);
|
||||
drain_fifo();
|
||||
|
||||
/* Send record 0 command via FIFO */
|
||||
int fd_cmd = open(CMD_FIFO, O_WRONLY);
|
||||
if (fd_cmd < 0) {
|
||||
fprintf(stderr, " FAIL: cannot open command FIFO\n");
|
||||
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
write(fd_cmd, "record 0\n", 9);
|
||||
close(fd_cmd);
|
||||
|
||||
/* Keep reading status lines until we see RECORD or timeout (5 seconds) */
|
||||
int found = 0;
|
||||
int ch, sc;
|
||||
char state[32];
|
||||
if (sscanf(line, "CH=%d SC=%d STATE=%31s", &ch, &sc, state) != 3) {
|
||||
fprintf(stderr, " FAIL: malformed status line: %s\n", line);
|
||||
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
|
||||
return 1;
|
||||
char line[256];
|
||||
for (int tries = 0; tries < 50; tries++) {
|
||||
if (read_status_line_timeout(line, sizeof(line), 100) != 0) {
|
||||
usleep(100000);
|
||||
continue;
|
||||
}
|
||||
if (sscanf(line, "CH=%d SC=%d STATE=%31s", &ch, &sc, state) != 3)
|
||||
continue;
|
||||
if (ch == 0 && sc == 0 && strcmp(state, "RECORD") == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ch != 0 || sc != 0 || strcmp(state, "RECORD") != 0) {
|
||||
fprintf(stderr, " FAIL: expected CH=0 SC=0 STATE=RECORD, got: CH=%d SC=%d STATE=%s\n",
|
||||
ch, sc, state);
|
||||
if (!found) {
|
||||
fprintf(stderr, " FAIL: did not see STATE=RECORD for CH=0 SC=0 within 5 seconds\n");
|
||||
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user