1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
#include "shell.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int failures = 0;
static int checks = 0;
#define CHECK(cond, msg) \
do { \
checks++; \
if (!(cond)) { \
failures++; \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, (msg)); \
} \
} while (0)
/* Spawn a shell, confirm it is a live process, then reap and free it. */
static void test_spawn_and_reap(void)
{
interactive_shell *s = shell_spawn();
CHECK(s != NULL, "shell_spawn returns a shell");
if (!s)
return;
CHECK(s->pid > 0, "shell has a live pid");
CHECK(s->master >= 0, "shell has a master fd");
/* Let it settle, then confirm it is still alive (not yet reaped). */
usleep(100 * 1000);
CHECK(s->pid > 0, "shell still alive before reap");
/* Free kills it and reaps; afterwards the pid is 0. */
shell_free(s);
}
/* Writing to a freed/closed shell must not crash. */
static void test_write_after_free_is_safe(void)
{
interactive_shell *s = shell_spawn();
CHECK(s != NULL, "shell_spawn");
if (!s)
return;
shell_free(s);
/* shell_write on a NULL shell is guarded by the caller; on a freed one we
* cannot call it (use-after-free), so just confirm free was clean. */
}
/* A shell that `cd`s and then exits must leave its final directory recorded
* (regression: the cwd was read after waitpid, when /proc/<pid>/cwd is gone,
* so `shell_final_cwd` returned NULL and llsh stayed in the old directory). */
static void test_final_cwd_after_exit(void)
{
setenv("SHELL", "/bin/bash", 1);
interactive_shell *s = shell_spawn();
CHECK(s != NULL, "shell_spawn");
if (!s)
return;
/* The sleep holds the shell alive after the cd, so the cwd poll (which
* only sees a live process) is guaranteed to observe /tmp. */
const char *cmd = "cd /tmp\nsleep 0.3\nexit\n";
for (size_t i = 0; i < strlen(cmd); i++)
CHECK(shell_write(s, cmd + i, 1) == 0, "write to shell");
bool reaped = false;
for (int i = 0; i < 200 && !reaped; i++) {
if (shell_reap_if_dead(s))
reaped = true;
else
usleep(25 * 1000);
}
CHECK(reaped, "shell reaped after exit");
char *cwd = shell_final_cwd(s);
CHECK(cwd != NULL, "final cwd is known");
if (cwd) {
CHECK(xstreq(cwd, "/tmp"), "final cwd is the shell's last directory");
free(cwd);
}
shell_free(s);
}
/* Output the shell prints is captured (sanitized, terminal escapes stripped)
* alongside streaming to stdout, so llsh can record a shell-mode visit
* (commands and output) in the conversation context. `shell_captured`
* returns the text and resets the buffer. */
static void test_output_captured(void)
{
setenv("SHELL", "/bin/bash", 1);
interactive_shell *s = shell_spawn();
CHECK(s != NULL, "shell_spawn");
if (!s)
return;
/* `echo OUTMARKER-$((1+1))` prints `OUTMARKER-2`, which only appears in
* the command's *output* (the input echo shows the unexpanded form), so a
* match proves the output was captured, not just the typed command. */
const char *cmd = "echo OUTMARKER-$((1+1))\n";
for (size_t i = 0; i < strlen(cmd); i++)
CHECK(shell_write(s, cmd + i, 1) == 0, "write to shell");
char *cap = NULL;
for (int i = 0; i < 200 && !cap; i++) {
usleep(50 * 1000);
char *c = shell_captured(s);
if (strstr(c, "OUTMARKER-2"))
cap = c; /* keep it */
else
free(c);
}
CHECK(cap != NULL, "captured output contains the command's output");
if (cap) {
/* The sanitized text must have no terminal escapes left. */
CHECK(strchr(cap, '\x1B') == NULL, "captured output has no ESC");
fprintf(stderr, "\n=== CAPTURED (%zu bytes) ===\n%s\n=== END ===\n",
strlen(cap), cap);
}
free(cap);
shell_free(s);
}
int main(int argc, char **argv)
{
if (argc > 1 && xstreq(argv[1], "dump")) {
test_output_captured();
return 0;
}
test_spawn_and_reap();
test_write_after_free_is_safe();
test_final_cwd_after_exit();
test_output_captured();
printf("shell: %d checks, %d failures\n", checks, failures);
return failures == 0 ? 0 : 1;
}
|