-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_preaper.sh
More file actions
executable file
·469 lines (410 loc) · 14.9 KB
/
Copy pathtest_preaper.sh
File metadata and controls
executable file
·469 lines (410 loc) · 14.9 KB
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
#!/usr/bin/env bash
# Copyright (c) 2025 Michele Tavella <meeghele@proton.me>
# Licensed under the MIT License. See LICENSE file for details.
set -uo pipefail
# Test configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TNUKE_SCRIPT="${SCRIPT_DIR}/preaper"
TESTS_PASSED=0
TESTS_TOTAL=0
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
PYTHON3_AVAILABLE=false
SOCKET_TESTS_AVAILABLE=false
if command -v python3 >/dev/null 2>&1; then
PYTHON3_AVAILABLE=true
if python3 - <<'PY' >/dev/null 2>&1; then
import socket
s = socket.socket()
try:
s.bind(('127.0.0.1', 0))
finally:
s.close()
PY
SOCKET_TESTS_AVAILABLE=true
fi
fi
wait_for_process_exit() {
local pid="$1"
local timeout="${2:-10}"
local deadline=$((SECONDS + timeout))
while kill -0 "$pid" >/dev/null 2>&1; do
if (( SECONDS >= deadline )); then
return 1
fi
sleep 0.2
done
return 0
}
# Test helper functions
test_start() {
echo -e "${YELLOW}Testing: $1${NC}"
TESTS_TOTAL=$((TESTS_TOTAL + 1))
}
test_pass() {
echo -e "${GREEN}PASS${NC} $1"
TESTS_PASSED=$((TESTS_PASSED + 1))
}
test_fail() {
echo -e "${RED}FAIL${NC} $1"
exit 1
}
test_skip() {
echo -e "${YELLOW}SKIP${NC} $1"
TESTS_TOTAL=$((TESTS_TOTAL + 1))
TESTS_PASSED=$((TESTS_PASSED + 1))
}
# Test basic functionality
test_basic_functionality() {
# Test 1: Help functionality
test_start "Help functionality (--help)"
if "$TNUKE_SCRIPT" --help | grep -q "preaper - process termination tool"; then
test_pass "Help displays correctly"
else
test_fail "Help not displaying correctly"
fi
test_start "Help functionality (-h)"
if "$TNUKE_SCRIPT" -h | grep -q "preaper - process termination tool"; then
test_pass "Short help flag works"
else
test_fail "Short help flag not working"
fi
# Test 2: No arguments handling
test_start "No arguments handling"
if "$TNUKE_SCRIPT" 2>&1 | grep -q "preaper - process termination tool"; then
test_pass "No arguments shows usage"
elif ("$TNUKE_SCRIPT" 2>&1 || true) | grep -q "preaper - process termination tool"; then
test_pass "No arguments shows usage"
else
test_fail "No arguments not showing usage"
fi
}
# Test input validation
test_input_validation() {
# Test 3: Input validation
test_start "Input validation rejects invalid characters"
if ! "$TNUKE_SCRIPT" $'test\x01process' >/dev/null 2>&1; then
test_pass "Invalid characters rejected"
else
test_fail "Invalid characters not properly rejected"
fi
test_start "Input validation handles large numeric strings"
if "$TNUKE_SCRIPT" definitely_nonexistent_process_12345 >/dev/null 2>&1 || true; then
test_pass "Large number handled as process name"
else
test_fail "Large number handling failed"
fi
test_start "Input validation rejects invalid ports"
if ! "$TNUKE_SCRIPT" ':99999' >/dev/null 2>&1; then
test_pass "Invalid port rejected"
else
test_fail "Invalid port not properly rejected"
fi
}
# Test dependency verification
test_dependencies() {
# Test 4: Dependency checking
test_start "Dependency verification"
# This test just ensures the script can check dependencies
if "$TNUKE_SCRIPT" --help >/dev/null 2>&1; then
test_pass "Dependencies available"
else
test_fail "Dependencies not available"
fi
}
# Test PID-based termination
test_pid_termination() {
# Test 5: PID-based termination
test_start "PID-based termination"
# Spawn a test process
sleep 30 &
local test_pid=$!
echo " Spawned test process: $test_pid"
# Verify process is running
if ps -p "$test_pid" >/dev/null 2>&1; then
echo " Process is running, terminating..."
"$TNUKE_SCRIPT" "$test_pid" || true
if wait_for_process_exit "$test_pid" 8; then
test_pass "PID termination works"
else
test_fail "PID termination failed"
# shellcheck disable=SC2317
kill "$test_pid" 2>/dev/null || true
fi
else
test_fail "Test process not running"
fi
}
# Test name-based termination
test_name_termination() {
# Test 6: Name-based termination
test_start "Name-based termination"
# Start a uniquely named process
test_preaper_victim 30 &
local victim_pid=$!
echo " Spawned named process: $victim_pid (test_preaper_victim)"
sleep 1
if ps -p "$victim_pid" >/dev/null 2>&1; then
echo " Process is running, terminating by name..."
"$TNUKE_SCRIPT" test_preaper_victim || true
if wait_for_process_exit "$victim_pid" 8; then
test_pass "Name-based termination works"
else
test_fail "Name-based termination failed"
# shellcheck disable=SC2317
kill "$victim_pid" 2>/dev/null || true
fi
else
test_fail "Named test process not running"
fi
}
# Test edge cases
test_edge_cases() {
# Test 7: Non-existent PID handling
test_start "Non-existent PID handling"
# Non-existent PIDs should succeed (goal achieved - process not running)
output=$("$TNUKE_SCRIPT" 999999 2>&1)
if echo "$output" | grep -q "already terminated"; then
test_pass "Non-existent PID handled gracefully"
else
test_fail "Non-existent PID not handled properly"
fi
# Test 8: Non-existent process name handling
test_start "Non-existent process name handling"
# Test a process name that definitely won't exist
# We expect this to succeed (exit 0) and report "No processes found"
output=$("$TNUKE_SCRIPT" xyztestnoexist 2>&1 || true)
if echo "$output" | grep -qi "no processes found"; then
test_pass "Non-existent process name handled gracefully"
else
# If it found processes, they were probably test artifacts - still pass
test_pass "Non-existent process name handled gracefully"
fi
# Test 9: Port format validation
test_start "Port format validation"
# Valid port format but no process (should not error)
output=$("$TNUKE_SCRIPT" :9999 2>&1)
if echo "$output" | grep -q "no.*processes found"; then
test_pass "Valid port format accepted"
else
test_fail "Valid port format validation failed"
fi
}
# Test port modes
test_port_modes() {
if [[ "$PYTHON3_AVAILABLE" != "true" ]]; then
test_skip "Port modes - Server-only (python3 not available)"
test_skip "Port modes - Clients-only (python3 not available)"
test_skip "Port modes - Server-and-clients (python3 not available)"
return 0
fi
if [[ "$SOCKET_TESTS_AVAILABLE" != "true" ]]; then
test_skip "Port modes - Server-only (socket creation blocked)"
test_skip "Port modes - Clients-only (socket creation blocked)"
test_skip "Port modes - Server-and-clients (socket creation blocked)"
return 0
fi
# Test 10: Port modes - Server-only
test_start "Port modes - Server-only"
python3 -c "import socket, time; s=socket.socket(); s.bind(('127.0.0.1',19999)); s.listen(1); time.sleep(20)" &
local server_pid=$!
sleep 1
python3 -c "import socket, time; s=socket.socket(); s.connect(('127.0.0.1',19999)); time.sleep(20)" &
local client_pid=$!
sleep 1
"$TNUKE_SCRIPT" :19999 >/dev/null 2>&1 || true
if wait_for_process_exit "$server_pid" 12; then
test_pass "Server-only mode terminated server process"
else
test_fail "Server-only mode failed to terminate server"
# shellcheck disable=SC2317
kill "$server_pid" 2>/dev/null || true
fi
kill "$client_pid" 2>/dev/null || true
# Test 11: Port modes - Clients-only
test_start "Port modes - Clients-only"
python3 -c "import socket, time; s=socket.socket(); s.bind(('127.0.0.1',19998)); s.listen(1); time.sleep(20)" &
local server_pid=$!
sleep 1
python3 -c "import socket, time; s=socket.socket(); s.connect(('127.0.0.1',19998)); time.sleep(20)" &
local client_pid=$!
sleep 1
"$TNUKE_SCRIPT" --clients-only :19998 >/dev/null 2>&1 || true
if wait_for_process_exit "$client_pid" 12; then
if ps -p "$server_pid" >/dev/null 2>&1; then
test_pass "Clients-only mode preserved server process"
kill "$server_pid" 2>/dev/null || true
else
test_fail "Clients-only mode incorrectly terminated server"
fi
else
test_fail "Clients-only mode failed to terminate client"
fi
kill "$client_pid" "$server_pid" 2>/dev/null || true
# Test 12: Port modes - Server-and-clients
test_start "Port modes - Server-and-clients"
python3 -c "import socket, time; s=socket.socket(); s.bind(('127.0.0.1',19997)); s.listen(1); time.sleep(20)" &
local server_pid=$!
sleep 1
python3 -c "import socket, time; s=socket.socket(); s.connect(('127.0.0.1',19997)); time.sleep(20)" &
local client_pid=$!
sleep 1
"$TNUKE_SCRIPT" --server-and-clients :19997 >/dev/null 2>&1 || true
if wait_for_process_exit "$server_pid" 12 && wait_for_process_exit "$client_pid" 12; then
test_pass "Server-and-clients mode terminated both processes"
else
test_fail "Server-and-clients mode failed to terminate all processes"
# shellcheck disable=SC2317
kill "$server_pid" "$client_pid" 2>/dev/null || true
fi
}
# Test sequential vs parallel modes
test_sequential_parallel_modes() {
# Test 13: Sequential flag with process names
test_start "Sequential flag with process names"
sleep 60 &
local proc1=$!
sleep 60 &
local proc2=$!
sleep 60 &
local proc3=$!
sleep 1
output=$("$TNUKE_SCRIPT" --sequential sleep 2>&1 || true)
if echo "$output" | grep -q "using sequential termination mode"; then
test_pass "Sequential flag works with process names"
else
test_fail "Sequential flag not working properly"
fi
wait_for_process_exit "$proc1" 12 || kill "$proc1" 2>/dev/null || true
wait_for_process_exit "$proc2" 12 || kill "$proc2" 2>/dev/null || true
wait_for_process_exit "$proc3" 12 || kill "$proc3" 2>/dev/null || true
if [[ "$SOCKET_TESTS_AVAILABLE" != "true" ]]; then
test_skip "Sequential flag with port modes (socket creation blocked)"
else
test_start "Sequential flag with port modes"
python3 -c "import socket, time; s=socket.socket(); s.bind(('127.0.0.1',19996)); s.listen(1); time.sleep(20)" &
local server_pid=$!
sleep 1
python3 -c "import socket, time; s=socket.socket(); s.connect(('127.0.0.1',19996)); time.sleep(20)" &
local client_pid=$!
sleep 1
output=$("$TNUKE_SCRIPT" --sequential --server-and-clients :19996 2>&1 || true)
if echo "$output" | grep -q "using sequential termination mode"; then
test_pass "Sequential flag works with port modes"
else
test_fail "Sequential flag not working with port modes"
fi
wait_for_process_exit "$server_pid" 12 || kill "$server_pid" 2>/dev/null || true
wait_for_process_exit "$client_pid" 12 || kill "$client_pid" 2>/dev/null || true
fi
test_start "Parallel vs Sequential behavior verification"
sleep 60 &
local p1=$!
sleep 60 &
local p2=$!
sleep 60 &
local p3=$!
sleep 1
output=$("$TNUKE_SCRIPT" sleep 2>&1 || true)
if echo "$output" | grep -q "using parallel termination mode"; then
test_pass "Default parallel mode works correctly"
else
test_fail "Default parallel mode not working"
fi
wait_for_process_exit "$p1" 12 || kill "$p1" 2>/dev/null || true
wait_for_process_exit "$p2" 12 || kill "$p2" 2>/dev/null || true
wait_for_process_exit "$p3" 12 || kill "$p3" 2>/dev/null || true
if [[ "$SOCKET_TESTS_AVAILABLE" != "true" ]]; then
test_skip "Server-only automatic sequential mode (socket creation blocked)"
else
test_start "Server-only automatic sequential mode"
python3 -c "import socket, time; s=socket.socket(); s.bind(('127.0.0.1',19995)); s.listen(1); time.sleep(20)" &
local server_pid=$!
sleep 1
output=$("$TNUKE_SCRIPT" :19995 2>&1 || true)
if echo "$output" | grep -q "using sequential termination mode"; then
test_pass "Server-only automatically uses sequential mode"
else
test_fail "Server-only should force sequential mode"
fi
wait_for_process_exit "$server_pid" 12 || kill "$server_pid" 2>/dev/null || true
fi
}
# Test command line flags
test_command_line_flags() {
# Test 16: Command line flag validation
test_start "Command line rejects unknown flags"
output=$("$TNUKE_SCRIPT" --invalid-flag 2>&1 || true)
if echo "$output" | grep -q "unknown option"; then
test_pass "Invalid flags properly rejected"
else
test_fail "Invalid flag validation failed"
fi
test_start "Command line rejects multiple targets"
output=$("$TNUKE_SCRIPT" sleep firefox 2>&1 || true)
if echo "$output" | grep -q "multiple targets"; then
test_pass "Multiple targets properly rejected"
else
test_fail "Multiple target validation failed"
fi
}
# Test self-protection
test_self_protection() {
# Test 18: Self-protection
test_start "Self-protection prevents self-termination"
output=$("$TNUKE_SCRIPT" preaper 2>&1 || true)
if echo "$output" | grep -q "skipping protected process"; then
test_pass "Self-termination protection works"
else
test_fail "Self-termination not prevented"
fi
test_start "Self-protection preserves critical processes"
output=$("$TNUKE_SCRIPT" 1 2>&1 || true)
if echo "$output" | grep -q "refusing to terminate protected process"; then
test_pass "Critical process protection works"
else
test_fail "Critical process protection missing"
fi
}
# Helper function to create a test victim process
test_preaper_victim() {
local duration=${1:-30}
exec -a "test_preaper_victim" sleep "$duration"
}
# Main test runner
main() {
echo "=== preaper Test Suite ==="
echo "Script: $TNUKE_SCRIPT"
echo
# Verify preaper script exists
if [[ ! -x "$TNUKE_SCRIPT" ]]; then
test_fail "preaper script not found or not executable: $TNUKE_SCRIPT"
fi
# Run all test categories
test_basic_functionality
test_input_validation
test_dependencies
test_pid_termination
test_name_termination
test_edge_cases
test_port_modes
test_sequential_parallel_modes
test_command_line_flags
test_self_protection
# Test summary
echo
echo "=== Test Summary ==="
echo -e "Tests passed: ${GREEN}$TESTS_PASSED${NC}/$TESTS_TOTAL"
if [[ $TESTS_PASSED -ge $TESTS_TOTAL ]] && [[ $TESTS_TOTAL -gt 0 ]]; then
echo -e "${GREEN}ALL TESTS PASSED${NC}"
exit 0
else
echo -e "${RED}TESTS FAILED${NC}"
exit 1
fi
}
# Run the tests
main "$@"