diff options
| author | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:27:44 +0300 |
|---|---|---|
| committer | Mehmet Samet Duman <yongdohyun@projecttick.org> | 2026-04-02 18:27:44 +0300 |
| commit | e99e87d16cdba1de0cfd84ce1a231ad3371e43db (patch) | |
| tree | b0fb3898bdcd7ece9ebd4769b6e3695f1fd022f4 /corebinutils/pkill/tests | |
| parent | 4f84a681e5fc545999076703b5920d4c4cf29a78 (diff) | |
| parent | c07449c1ae05a076b9e554267513c794c86e3ba5 (diff) | |
| download | Project-Tick-e99e87d16cdba1de0cfd84ce1a231ad3371e43db.tar.gz Project-Tick-e99e87d16cdba1de0cfd84ce1a231ad3371e43db.zip | |
Add 'corebinutils/pkill/' from commit 'c07449c1ae05a076b9e554267513c794c86e3ba5'
git-subtree-dir: corebinutils/pkill
git-subtree-mainline: 4f84a681e5fc545999076703b5920d4c4cf29a78
git-subtree-split: c07449c1ae05a076b9e554267513c794c86e3ba5
Diffstat (limited to 'corebinutils/pkill/tests')
| -rw-r--r-- | corebinutils/pkill/tests/spin_helper.c | 190 | ||||
| -rw-r--r-- | corebinutils/pkill/tests/test.sh | 730 |
2 files changed, 920 insertions, 0 deletions
diff --git a/corebinutils/pkill/tests/spin_helper.c b/corebinutils/pkill/tests/spin_helper.c new file mode 100644 index 0000000000..bf3ec550ba --- /dev/null +++ b/corebinutils/pkill/tests/spin_helper.c @@ -0,0 +1,190 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Klara, Inc. + * Copyright (c) 2026 Project Tick. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Linux-native test helper for pkill/pgrep tests. + * Replaces BSD __DECONST and <sys/cdefs.h>. + * + * Usage: + * spin_helper --spin flagfile sentinel + * Creates flagfile, then spins until killed. + * + * spin_helper --short flagfile sentinel + * Re-execs with short arguments, then spins. + * + * spin_helper --long flagfile sentinel + * Re-execs with maximally long arguments, then spins. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static volatile sig_atomic_t got_signal; + +static void +sighandler(int signo) +{ + got_signal = signo; +} + +static void +exec_shortargs(char *argv[]) +{ + char *nargv[] = { argv[0], (char *)"--spin", argv[2], argv[3], NULL }; + char *nenvp[] = { NULL }; + + execve(argv[0], nargv, nenvp); + perror("execve"); + _exit(1); +} + +static void +exec_largeargs(char *argv[]) +{ + size_t bufsz; + char *s = NULL; + char *nargv[] = { + argv[0], (char *)"--spin", argv[2], NULL, argv[3], NULL + }; + char *nenvp[] = { NULL }; + + /* + * Compute maximum argument size. Account for each argument + NUL + * terminator, plus an extra NUL. + */ + long arg_max = sysconf(_SC_ARG_MAX); + + if (arg_max <= 0) + arg_max = 131072; + + bufsz = (size_t)arg_max - + ((strlen(argv[0]) + 1) + sizeof("--spin") + + (strlen(argv[2]) + 1) + (strlen(argv[3]) + 1) + 1); + + /* + * Keep trying with smaller sizes until execve stops returning + * E2BIG. + */ + do { + char *ns = realloc(s, bufsz + 1); + + if (ns == NULL) + abort(); + s = ns; + memset(s, 'x', bufsz); + s[bufsz] = '\0'; + nargv[3] = s; + + execve(argv[0], nargv, nenvp); + bufsz--; + } while (errno == E2BIG && bufsz > 0); + + perror("execve"); + _exit(1); +} + +int +main(int argc, char *argv[]) +{ + if (argc > 1 && strcmp(argv[1], "--spin") == 0) { + int fd; + struct sigaction sa; + + if (argc < 4) { + fprintf(stderr, + "usage: %s --spin flagfile sentinel\n", + argv[0]); + return 1; + } + + /* Install signal handler for SIGUSR1 and SIGTERM. */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighandler; + sigemptyset(&sa.sa_mask); + sigaction(SIGUSR1, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + /* Create flag file to indicate readiness. */ + fd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { + perror(argv[2]); + return 1; + } + /* Write our PID to the flag file. */ + dprintf(fd, "%d\n", (int)getpid()); + close(fd); + + /* Spin until signal received. */ + while (got_signal == 0) + pause(); + + /* + * Write received signal name to the sentinel file + * so the test can verify which signal was delivered. + */ + { + const char *sname = "UNKNOWN"; + + if (got_signal == SIGUSR1) + sname = "USR1"; + else if (got_signal == SIGTERM) + sname = "TERM"; + + FILE *fp = fopen(argv[argc - 1], "w"); + + if (fp != NULL) { + fprintf(fp, "%s\n", sname); + fclose(fp); + } + } + + return 0; + } + + if (argc != 4) { + fprintf(stderr, + "usage: %s [--short | --long] flagfile sentinel\n", + argv[0]); + return 1; + } + + if (strcmp(argv[1], "--short") == 0) + exec_shortargs(argv); + else + exec_largeargs(argv); + + return 1; +} diff --git a/corebinutils/pkill/tests/test.sh b/corebinutils/pkill/tests/test.sh new file mode 100644 index 0000000000..94a271a45d --- /dev/null +++ b/corebinutils/pkill/tests/test.sh @@ -0,0 +1,730 @@ +#!/bin/sh +# Comprehensive test suite for pkill/pgrep Linux port. +# Follows the same pattern as bin/kill/tests/test.sh. +set -eu + +ROOT=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +PKILL_BIN=${PKILL_BIN:-"$ROOT/out/pkill"} +PGREP_BIN=${PGREP_BIN:-"$ROOT/out/pgrep"} +HELPER_BIN=${HELPER_BIN:-"$ROOT/out/spin_helper"} +CC=${CC:-cc} +TMPDIR=${TMPDIR:-/tmp} +WORKDIR=$(mktemp -d "$TMPDIR/pkill-test.XXXXXX") +STDOUT_FILE="$WORKDIR/stdout" +STDERR_FILE="$WORKDIR/stderr" + +# Unique name: must be <= 15 chars to stay within Linux /proc/PID/stat comm limit. +# Format: pkt + truncated_PID. "pkt" (3) + "_" (1) + up to 11 PID digits. +# In practice PID is at most 7 digits on Linux, so max = 11 chars. +UNIQUE="pkt_$$" +# Guard against pathological shells where $$ is very long. +if [ ${#UNIQUE} -gt 15 ]; then + _suffix=$(echo "$$" | tail -c8) + UNIQUE="pkt_$_suffix" +fi +HELPER_COPY="$WORKDIR/$UNIQUE" + +PIDS_TO_KILL="" + +export LC_ALL=C + +cleanup() { + for p in $PIDS_TO_KILL; do + kill -KILL "$p" 2>/dev/null || true + wait "$p" 2>/dev/null || true + done + rm -rf "$WORKDIR" +} +trap cleanup EXIT INT TERM + +PASS_COUNT=0 +FAIL_COUNT=0 + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + FAIL_COUNT=$((FAIL_COUNT + 1)) + exit 1 +} + +pass() { + PASS_COUNT=$((PASS_COUNT + 1)) + printf ' ok: %s\n' "$1" +} + +assert_eq() { + name=$1; expected=$2; actual=$3 + if [ "$expected" != "$actual" ]; then + printf 'FAIL: %s\n' "$name" >&2 + printf '--- expected ---\n%s\n--- actual ---\n%s\n' \ + "$expected" "$actual" >&2 + exit 1 + fi +} + +assert_contains() { + name=$1; text=$2; pattern=$3 + case $text in + *"$pattern"*) ;; + *) fail "$name: expected to contain '$pattern', got: $text" ;; + esac +} + +assert_not_contains() { + name=$1; text=$2; pattern=$3 + case $text in + *"$pattern"*) fail "$name: should not contain '$pattern'" ;; + *) ;; + esac +} + +assert_empty() { + name=$1; text=$2 + if [ -n "$text" ]; then + printf 'FAIL: %s\n--- expected empty ---\n--- actual ---\n%s\n' \ + "$name" "$text" >&2 + exit 1 + fi +} + +assert_status() { + name=$1; expected=$2; actual=$3 + if [ "$expected" -ne "$actual" ]; then + printf 'FAIL: %s\nexpected status: %s\nactual status: %s\n' \ + "$name" "$expected" "$actual" >&2 + exit 1 + fi +} + +run_capture() { + if "$@" >"$STDOUT_FILE" 2>"$STDERR_FILE"; then + LAST_STATUS=0 + else + LAST_STATUS=$? + fi + LAST_STDOUT=$(cat "$STDOUT_FILE") + LAST_STDERR=$(cat "$STDERR_FILE") +} + +# Start a helper process with a unique command name. +# Uses --spin directly so that comm matches the helper copy's basename +# immediately without any execve race. +# Sets HELPER_PID, HELPER_SENTINEL. +start_helper() { + tag=$1 + flag="$WORKDIR/${tag}.flag" + sentinel="$WORKDIR/${tag}.sentinel" + rm -f "$flag" "$sentinel" + + # --spin: creates flag file then waits for signal. + "$HELPER_COPY" --spin "$flag" "$sentinel" & + HELPER_PID=$! + PIDS_TO_KILL="$PIDS_TO_KILL $HELPER_PID" + + # Wait for flag file to appear (readiness signal). + tries=0 + while [ ! -f "$flag" ]; do + tries=$((tries + 1)) + if [ $tries -gt 100 ]; then + fail "helper '$tag' (pid=$HELPER_PID) did not become ready" + fi + sleep 0.05 + done + HELPER_SENTINEL="$sentinel" +} + +# Start two helpers for oldest/newest tests. +start_two_helpers() { + start_helper "older" + OLDER_PID=$HELPER_PID + OLDER_SENTINEL=$HELPER_SENTINEL + sleep 0.2 # ensure different starttime + start_helper "newer" + NEWER_PID=$HELPER_PID + NEWER_SENTINEL=$HELPER_SENTINEL +} + +stop_helper() { + pid=$1 + kill -KILL "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + PIDS_TO_KILL=$(echo "$PIDS_TO_KILL" | sed "s/ *$pid//g") +} + +wait_helper_exit() { + pid=$1 + tries=0 + while kill -0 "$pid" 2>/dev/null; do + tries=$((tries + 1)) + if [ $tries -gt 50 ]; then + fail "helper pid=$pid did not exit" + fi + sleep 0.1 + done + wait "$pid" 2>/dev/null || true + PIDS_TO_KILL=$(echo "$PIDS_TO_KILL" | sed "s/ *$pid//g") +} + +# ============================================================ +# Prerequisite checks +# ============================================================ +[ -x "$PKILL_BIN" ] || fail "missing pkill binary: $PKILL_BIN" +[ -x "$PGREP_BIN" ] || fail "missing pgrep binary: $PGREP_BIN" +[ -x "$HELPER_BIN" ] || fail "missing helper binary: $HELPER_BIN" +[ -d /proc ] || fail "/proc not available" + +# Create a helper copy with unique name. +cp "$HELPER_BIN" "$HELPER_COPY" +chmod +x "$HELPER_COPY" + +printf '=== pkill/pgrep test suite ===\n' +printf 'helper name: %s\n' "$UNIQUE" + +# ============================================================ +# 1. Usage / error tests +# ============================================================ + +# pgrep with no arguments → status 2 +run_capture "$PGREP_BIN" +assert_status "pgrep no args: status" 2 "$LAST_STATUS" +assert_empty "pgrep no args: stdout" "$LAST_STDOUT" +assert_contains "pgrep no args: stderr" "$LAST_STDERR" "usage:" +pass "pgrep no args → usage error" + +# pkill with no arguments → status 2 +run_capture "$PKILL_BIN" +assert_status "pkill no args: status" 2 "$LAST_STATUS" +assert_empty "pkill no args: stdout" "$LAST_STDOUT" +assert_contains "pkill no args: stderr" "$LAST_STDERR" "usage:" +pass "pkill no args → usage error" + +# Bad regex → status 2 +run_capture "$PGREP_BIN" '[invalid' +assert_status "bad regex: status" 2 "$LAST_STATUS" +assert_empty "bad regex: stdout" "$LAST_STDOUT" +assert_contains "bad regex: stderr" "$LAST_STDERR" "Cannot compile regular expression" +pass "bad regex → error" + +# -n and -o together → status 3 +run_capture "$PGREP_BIN" -n -o 'anything' +assert_status "-n -o together: status" 3 "$LAST_STATUS" +assert_contains "-n -o together: stderr" "$LAST_STDERR" "mutually exclusive" +pass "-n -o together → error" + +# BSD-only options produce explicit errors on Linux +run_capture "$PGREP_BIN" -j any 'test' +assert_status "-j: status" 2 "$LAST_STATUS" +assert_contains "-j: stderr" "$LAST_STDERR" "not supported on Linux" +pass "-j → explicit unsupported error" + +run_capture "$PGREP_BIN" -c user 'test' +assert_status "-c: status" 2 "$LAST_STATUS" +assert_contains "-c: stderr" "$LAST_STDERR" "not supported on Linux" +pass "-c → explicit unsupported error" + +run_capture "$PGREP_BIN" -M /dev/null 'test' +assert_status "-M: status" 2 "$LAST_STATUS" +assert_contains "-M: stderr" "$LAST_STDERR" "not supported on Linux" +pass "-M → explicit unsupported error" + +run_capture "$PGREP_BIN" -N /dev/null 'test' +assert_status "-N: status" 2 "$LAST_STATUS" +assert_contains "-N: stderr" "$LAST_STDERR" "not supported on Linux" +pass "-N → explicit unsupported error" + +# -L without -F → error +run_capture "$PGREP_BIN" -L 'test' +assert_status "-L without -F: status" 3 "$LAST_STATUS" +assert_contains "-L without -F: stderr" "$LAST_STDERR" "doesn't make sense" +pass "-L without -F → error" + +# pgrep-only options in pkill mode → usage +run_capture "$PKILL_BIN" -d, 'test' +assert_status "pkill -d: status" 2 "$LAST_STATUS" +pass "pkill -d → usage error" + +run_capture "$PKILL_BIN" -q 'test' +assert_status "pkill -q: status" 2 "$LAST_STATUS" +pass "pkill -q → usage error" + +# pkill-only option in pgrep mode → usage +run_capture "$PGREP_BIN" -I 'test' +assert_status "pgrep -I: status" 2 "$LAST_STATUS" +pass "pgrep -I → usage error" + +# ============================================================ +# 2. pgrep basic matching +# ============================================================ +start_helper basic + +run_capture "$PGREP_BIN" "$UNIQUE" +assert_status "pgrep basic: status" 0 "$LAST_STATUS" +assert_contains "pgrep basic: stdout" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep basic match" + +# Ensure pgrep does not match itself. +assert_not_contains "pgrep self-exclusion" "$LAST_STDOUT" "$$" +pass "pgrep excludes self" + +stop_helper "$HELPER_PID" + +# No match → status 1 +run_capture "$PGREP_BIN" "nonexistent_process_name_$$$RANDOM" +assert_status "pgrep no match: status" 1 "$LAST_STATUS" +assert_empty "pgrep no match: stdout" "$LAST_STDOUT" +pass "pgrep no match → status 1" + +# ============================================================ +# 3. pgrep -x (exact match) +# ============================================================ +start_helper exact + +run_capture "$PGREP_BIN" -x "$UNIQUE" +assert_status "pgrep -x exact: status" 0 "$LAST_STATUS" +assert_contains "pgrep -x exact: stdout" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -x exact match" + +# Partial name should NOT match with -x. +partial=$(echo "$UNIQUE" | cut -c1-6) +run_capture "$PGREP_BIN" -x "$partial" +# It should not match the helper (could match other things though) +assert_not_contains "pgrep -x partial" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -x rejects partial match" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 4. pgrep -f (match full args) +# ============================================================ +start_helper fullarg + +run_capture "$PGREP_BIN" -f -- "--spin.*$WORKDIR" +assert_status "pgrep -f: status" 0 "$LAST_STATUS" +assert_contains "pgrep -f: stdout" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -f matches full cmdline" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 5. pgrep -i (case insensitive) +# ============================================================ +start_helper casematch + +UPPER=$(echo "$UNIQUE" | tr '[:lower:]' '[:upper:]') +run_capture "$PGREP_BIN" -i "$UPPER" +assert_status "pgrep -i: status" 0 "$LAST_STATUS" +assert_contains "pgrep -i: stdout" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -i case insensitive" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 6. pgrep -l (long output) +# ============================================================ +start_helper longout + +run_capture "$PGREP_BIN" -l "$UNIQUE" +assert_status "pgrep -l: status" 0 "$LAST_STATUS" +assert_contains "pgrep -l: stdout" "$LAST_STDOUT" "$HELPER_PID" +# Long format should include process name. +assert_contains "pgrep -l shows name" "$LAST_STDOUT" "$UNIQUE" +pass "pgrep -l long output" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 7. pgrep -l -f (long + full args) +# ============================================================ +start_helper longfull + +run_capture "$PGREP_BIN" -l -f "$UNIQUE" +assert_status "pgrep -lf: status" 0 "$LAST_STATUS" +# Should show PID and full cmdline +assert_contains "pgrep -lf: shows PID" "$LAST_STDOUT" "$HELPER_PID" +assert_contains "pgrep -lf: shows --spin" "$LAST_STDOUT" "--spin" +pass "pgrep -l -f long + full args" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 8. pgrep -v (inverse) +# ============================================================ +start_helper inverse + +# -v should NOT include the helper in results when it matches. +# We search for patterns that DO match, and check helper PID is absent. +run_capture "$PGREP_BIN" -v "$UNIQUE" +assert_status "pgrep -v: status" 0 "$LAST_STATUS" +assert_not_contains "pgrep -v no helper" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -v inverse match" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 9. pgrep -q (quiet) +# ============================================================ +start_helper quiettest + +run_capture "$PGREP_BIN" -q "$UNIQUE" +assert_status "pgrep -q: status" 0 "$LAST_STATUS" +assert_empty "pgrep -q: stdout" "$LAST_STDOUT" +pass "pgrep -q quiet mode" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 10. pgrep -d (delimiter) +# ============================================================ +start_helper delim1 +PID1=$HELPER_PID +start_helper delim2 +PID2=$HELPER_PID + +run_capture "$PGREP_BIN" -d, "$UNIQUE" +assert_status "pgrep -d: status" 0 "$LAST_STATUS" +# Output should contain comma-separated PIDs +assert_contains "pgrep -d: comma" "$LAST_STDOUT" "," +pass "pgrep -d custom delimiter" + +stop_helper "$PID1" +stop_helper "$PID2" + +# ============================================================ +# 11. pgrep -n (newest) / -o (oldest) +# ============================================================ +start_two_helpers + +run_capture "$PGREP_BIN" -n "$UNIQUE" +assert_status "pgrep -n: status" 0 "$LAST_STATUS" +assert_contains "pgrep -n: newest" "$LAST_STDOUT" "$NEWER_PID" +assert_not_contains "pgrep -n: not older" "$LAST_STDOUT" "$OLDER_PID" +pass "pgrep -n selects newest" + +run_capture "$PGREP_BIN" -o "$UNIQUE" +assert_status "pgrep -o: status" 0 "$LAST_STATUS" +assert_contains "pgrep -o: oldest" "$LAST_STDOUT" "$OLDER_PID" +assert_not_contains "pgrep -o: not newer" "$LAST_STDOUT" "$NEWER_PID" +pass "pgrep -o selects oldest" + +stop_helper "$OLDER_PID" +stop_helper "$NEWER_PID" + +# ============================================================ +# 12. pgrep -P ppid (parent PID filter) +# ============================================================ +start_helper ppidtest + +PPID_OF_HELPER=$(awk '{print $4}' "/proc/$HELPER_PID/stat" 2>/dev/null || echo "") +if [ -n "$PPID_OF_HELPER" ]; then + run_capture "$PGREP_BIN" -P "$PPID_OF_HELPER" "$UNIQUE" + assert_status "pgrep -P: status" 0 "$LAST_STATUS" + assert_contains "pgrep -P: match" "$LAST_STDOUT" "$HELPER_PID" + pass "pgrep -P parent PID filter" + + # Wrong parent → no match + run_capture "$PGREP_BIN" -P 1 "$UNIQUE" + if [ "$PPID_OF_HELPER" != "1" ]; then + assert_status "pgrep -P wrong parent: status" 1 "$LAST_STATUS" + pass "pgrep -P wrong parent → no match" + fi +else + printf ' SKIP: pgrep -P (cannot read ppid)\n' +fi + +stop_helper "$HELPER_PID" + +# ============================================================ +# 13. pgrep -u euid (effective user ID filter) +# ============================================================ +start_helper euidtest + +MY_EUID=$(id -u) +run_capture "$PGREP_BIN" -u "$MY_EUID" "$UNIQUE" +assert_status "pgrep -u euid: status" 0 "$LAST_STATUS" +assert_contains "pgrep -u euid: match" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -u effective UID filter" + +# Wrong euid → no match +run_capture "$PGREP_BIN" -u 99999 "$UNIQUE" +assert_status "pgrep -u wrong euid: status" 1 "$LAST_STATUS" +pass "pgrep -u wrong UID → no match" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 14. pgrep -U ruid (real user ID filter) +# ============================================================ +start_helper ruidtest + +MY_RUID=$(id -ru) +run_capture "$PGREP_BIN" -U "$MY_RUID" "$UNIQUE" +assert_status "pgrep -U ruid: status" 0 "$LAST_STATUS" +assert_contains "pgrep -U ruid: match" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -U real UID filter" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 15. pgrep -G rgid (real group ID filter) +# ============================================================ +start_helper rgidtest + +MY_RGID=$(id -rg) +run_capture "$PGREP_BIN" -G "$MY_RGID" "$UNIQUE" +assert_status "pgrep -G rgid: status" 0 "$LAST_STATUS" +assert_contains "pgrep -G rgid: match" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -G real GID filter" + +# Wrong GID +run_capture "$PGREP_BIN" -G 99999 "$UNIQUE" +assert_status "pgrep -G wrong gid: status" 1 "$LAST_STATUS" +pass "pgrep -G wrong GID → no match" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 16. pgrep -g pgrp (process group filter) +# ============================================================ +start_helper pgrptest + +# The helper's pgrp is its own PID (if it calls setpgid) or inherited. +# Read from proc. +HELPER_PGRP=$(awk '{print $5}' "/proc/$HELPER_PID/stat" 2>/dev/null || echo "") +if [ -n "$HELPER_PGRP" ] && [ "$HELPER_PGRP" != "0" ]; then + run_capture "$PGREP_BIN" -g "$HELPER_PGRP" "$UNIQUE" + assert_status "pgrep -g: status" 0 "$LAST_STATUS" + assert_contains "pgrep -g: match" "$LAST_STDOUT" "$HELPER_PID" + pass "pgrep -g process group filter" +else + printf ' SKIP: pgrep -g (cannot read pgrp)\n' +fi + +stop_helper "$HELPER_PID" + +# ============================================================ +# 17. pgrep -s sid (session ID filter) +# ============================================================ +start_helper sidtest + +HELPER_SID=$(awk '{print $6}' "/proc/$HELPER_PID/stat" 2>/dev/null || echo "") +if [ -n "$HELPER_SID" ] && [ "$HELPER_SID" != "0" ]; then + run_capture "$PGREP_BIN" -s "$HELPER_SID" "$UNIQUE" + assert_status "pgrep -s: status" 0 "$LAST_STATUS" + assert_contains "pgrep -s: match" "$LAST_STDOUT" "$HELPER_PID" + pass "pgrep -s session ID filter" +else + printf ' SKIP: pgrep -s (cannot read sid)\n' +fi + +stop_helper "$HELPER_PID" + +# ============================================================ +# 18. pgrep -F pidfile +# ============================================================ +start_helper pidfile + +PIDFILE="$WORKDIR/test.pid" +echo "$HELPER_PID" > "$PIDFILE" + +run_capture "$PGREP_BIN" -F "$PIDFILE" +assert_status "pgrep -F: status" 0 "$LAST_STATUS" +assert_contains "pgrep -F: match" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -F pidfile" + +# Non-existent pidfile → error +run_capture "$PGREP_BIN" -F "$WORKDIR/nonexistent.pid" +assert_status "pgrep -F nonexist: status" 3 "$LAST_STATUS" +assert_contains "pgrep -F nonexist: stderr" "$LAST_STDERR" "Cannot open" +pass "pgrep -F nonexistent → error" + +# Empty pidfile → error +> "$WORKDIR/empty.pid" +run_capture "$PGREP_BIN" -F "$WORKDIR/empty.pid" +assert_status "pgrep -F empty: status" 3 "$LAST_STATUS" +assert_contains "pgrep -F empty: stderr" "$LAST_STDERR" "empty" +pass "pgrep -F empty pidfile → error" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 19. pkill basic (send SIGTERM) +# ============================================================ +start_helper pkill_basic + +run_capture "$PKILL_BIN" "$UNIQUE" +assert_status "pkill basic: status" 0 "$LAST_STATUS" + +# Wait for helper to receive signal and exit. +wait_helper_exit "$HELPER_PID" + +# Check sentinel file for signal name. +if [ -f "$HELPER_SENTINEL" ]; then + signame=$(cat "$HELPER_SENTINEL") + assert_eq "pkill basic: signal" "TERM" "$signame" +fi +pass "pkill basic SIGTERM" + +# ============================================================ +# 20. pkill -SIGUSR1 (custom signal) +# ============================================================ +start_helper pkill_sig + +run_capture "$PKILL_BIN" -USR1 "$UNIQUE" +assert_status "pkill -USR1: status" 0 "$LAST_STATUS" + +wait_helper_exit "$HELPER_PID" + +if [ -f "$HELPER_SENTINEL" ]; then + signame=$(cat "$HELPER_SENTINEL") + assert_eq "pkill -USR1: signal" "USR1" "$signame" +fi +pass "pkill -USR1 custom signal" + +# ============================================================ +# 21. pkill -<number> (numeric signal) +# ============================================================ +start_helper pkill_num + +# SIGUSR1 is typically 10 +run_capture "$PKILL_BIN" -10 "$UNIQUE" +assert_status "pkill -10: status" 0 "$LAST_STATUS" + +wait_helper_exit "$HELPER_PID" + +if [ -f "$HELPER_SENTINEL" ]; then + signame=$(cat "$HELPER_SENTINEL") + assert_eq "pkill -10: signal" "USR1" "$signame" +fi +pass "pkill -<number> numeric signal" + +# ============================================================ +# 22. pkill -f (match full args) +# ============================================================ +start_helper pkill_full + +run_capture "$PKILL_BIN" -f -- "--spin.*$WORKDIR" +assert_status "pkill -f: status" 0 "$LAST_STATUS" + +wait_helper_exit "$HELPER_PID" +pass "pkill -f matches full cmdline" + +# ============================================================ +# 23. pkill with -l (list mode) +# ============================================================ +start_helper pkill_list + +run_capture "$PKILL_BIN" -l "$UNIQUE" +assert_status "pkill -l: status" 0 "$LAST_STATUS" +assert_contains "pkill -l: stdout" "$LAST_STDOUT" "kill" +assert_contains "pkill -l: stdout" "$LAST_STDOUT" "$HELPER_PID" +pass "pkill -l shows kill command" + +wait_helper_exit "$HELPER_PID" + +# ============================================================ +# 24. pkill no match → status 1 +# ============================================================ +run_capture "$PKILL_BIN" "nonexistent_process_$$$RANDOM" +assert_status "pkill no match: status" 1 "$LAST_STATUS" +pass "pkill no match → status 1" + +# ============================================================ +# 25. pgrep -S (include kernel threads) +# ============================================================ +# This should not error out. We just check it runs. +run_capture "$PGREP_BIN" -S 'kthreadd' +# kthreadd might or might not be visible depending on environment. +# Just verify it doesn't crash. +if [ "$LAST_STATUS" -ne 0 ] && [ "$LAST_STATUS" -ne 1 ]; then + fail "pgrep -S unexpected status: $LAST_STATUS" +fi +pass "pgrep -S kernel threads (no crash)" + +# ============================================================ +# 26. pgrep -a (include ancestors) +# ============================================================ +start_helper ancestors + +# Without -a, our shell (ancestor) might be excluded from results. +# With -a, ancestors are included. Hard to test precisely, but +# verify -a doesn't cause errors. +run_capture "$PGREP_BIN" -a "$UNIQUE" +assert_status "pgrep -a: status" 0 "$LAST_STATUS" +pass "pgrep -a include ancestors (no crash)" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 27. Multiple patterns +# ============================================================ +start_helper multi1 +PID_M1=$HELPER_PID + +run_capture "$PGREP_BIN" "$UNIQUE" +assert_status "multi pattern: status" 0 "$LAST_STATUS" +assert_contains "multi pattern: match" "$LAST_STDOUT" "$PID_M1" +pass "multiple pattern matching" + +stop_helper "$PID_M1" + +# ============================================================ +# 28. User name filter (if available) +# ============================================================ +start_helper usertest + +MY_USER=$(id -un) +run_capture "$PGREP_BIN" -u "$MY_USER" "$UNIQUE" +assert_status "pgrep -u username: status" 0 "$LAST_STATUS" +assert_contains "pgrep -u username: match" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -u username filter" + +# Unknown user → error +run_capture "$PGREP_BIN" -u "nonexistent_user_xyz_$$" "$UNIQUE" +assert_status "pgrep -u unknown user: status" 2 "$LAST_STATUS" +assert_contains "pgrep -u unknown: stderr" "$LAST_STDERR" "Unknown user" +pass "pgrep -u unknown user → error" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 29. Group name filter +# ============================================================ +start_helper grouptest + +MY_GROUP=$(id -gn) +run_capture "$PGREP_BIN" -G "$MY_GROUP" "$UNIQUE" +assert_status "pgrep -G groupname: status" 0 "$LAST_STATUS" +assert_contains "pgrep -G groupname: match" "$LAST_STDOUT" "$HELPER_PID" +pass "pgrep -G groupname filter" + +# Unknown group → error +run_capture "$PGREP_BIN" -G "nonexistent_group_xyz_$$" "$UNIQUE" +assert_status "pgrep -G unknown group: status" 2 "$LAST_STATUS" +assert_contains "pgrep -G unknown: stderr" "$LAST_STDERR" "Unknown group" +pass "pgrep -G unknown group → error" + +stop_helper "$HELPER_PID" + +# ============================================================ +# 30. pgrep -F -L (pidfile with lock check) +# ============================================================ +start_helper locktest + +LOCKPID_FILE="$WORKDIR/locked.pid" +echo "$HELPER_PID" > "$LOCKPID_FILE" + +# File is not locked, so -L should report error. +run_capture "$PGREP_BIN" -F "$LOCKPID_FILE" -L +assert_status "pgrep -F -L unlocked: status" 3 "$LAST_STATUS" +assert_contains "pgrep -F -L: stderr" "$LAST_STDERR" "can be locked" +pass "pgrep -F -L detects unlocked pidfile" + +stop_helper "$HELPER_PID" + +# ============================================================ +# Summary +# ============================================================ +printf '\n=== %d tests passed ===\n' "$PASS_COUNT" +if [ "$FAIL_COUNT" -ne 0 ]; then + printf '=== %d tests FAILED ===\n' "$FAIL_COUNT" + exit 1 +fi |
