Added new exercices to better cover philosophers_bonus requirements
- Added exercices: - Added process basics. - Added semaphores basics. - Added process communication - Added process termination - Added process_philosophers - Renamed and reordered philosophers_bonus
This commit is contained in:
parent
f8f51f8cc2
commit
2128325237
@ -5,8 +5,8 @@
|
||||
/* +:+ +:+ +:+ */
|
||||
/* By: ruiferna <ruiferna@student.42porto.com> +#+ +:+ +#+ */
|
||||
/* +#+#+#+#+#+ +#+ */
|
||||
/* Created: 2025/10/14 00:00:00 by ruiferna #+# #+# */
|
||||
/* Updated: 2025/10/14 15:38:47 by ruiferna ### ########.fr */
|
||||
/* Created: 2025/10/14 08:32:00 by ruiferna #+# #+# */
|
||||
/* Updated: 2025/10/14 16:30:18 by ruiferna ### ########.fr */
|
||||
/* */
|
||||
/* ************************************************************************** */
|
||||
|
||||
|
||||
25
subjects/13_process_basics.txt
Normal file
25
subjects/13_process_basics.txt
Normal file
@ -0,0 +1,25 @@
|
||||
Assignment name: process_basics
|
||||
Expected files: process_basics.c
|
||||
Allowed functions: memset, printf, malloc, free, write, fork, exit, waitpid, usleep, gettimeofday
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Create a program that:
|
||||
1. Creates 3 child processes using fork()
|
||||
2. Each child process prints its process ID (PID) and a unique message 5 times
|
||||
3. Each child sleeps for 100ms between messages
|
||||
4. The parent process waits for all children to finish using waitpid()
|
||||
5. The parent prints a summary when all children have finished
|
||||
|
||||
Child messages should be:
|
||||
- Child 1: "Child 1 (PID: <pid>): Message <n>"
|
||||
- Child 2: "Child 2 (PID: <pid>): Message <n>"
|
||||
- Child 3: "Child 3 (PID: <pid>): Message <n>"
|
||||
|
||||
Parent message: "Parent: All 3 children have finished"
|
||||
|
||||
Usage: `./process_basics`
|
||||
|
||||
Hint: fork() creates a new process. It returns 0 in the child process and the child's PID in the parent.
|
||||
Use exit() to terminate child processes properly.
|
||||
waitpid() allows the parent to wait for specific children to finish.
|
||||
Unlike threads, processes have separate memory spaces.
|
||||
28
subjects/14_semaphore_basics.txt
Normal file
28
subjects/14_semaphore_basics.txt
Normal file
@ -0,0 +1,28 @@
|
||||
Assignment name: semaphore_basics
|
||||
Expected files: semaphore_basics.c
|
||||
Allowed functions: memset, printf, malloc, free, write, fork, exit, waitpid,
|
||||
sem_open, sem_close, sem_post, sem_wait, sem_unlink,
|
||||
usleep, gettimeofday
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Create a program that demonstrates POSIX semaphores:
|
||||
1. Create a named semaphore initialized to 3 (simulating 3 available resources)
|
||||
2. Launch 5 child processes
|
||||
3. Each child process tries to acquire a resource (sem_wait), uses it for 500ms, then releases it (sem_post)
|
||||
4. Each child prints when it acquires and releases a resource
|
||||
5. Parent waits for all children to finish
|
||||
6. Properly cleanup the semaphore using sem_unlink()
|
||||
|
||||
Output format:
|
||||
- "Child <id> (PID: <pid>): Waiting for resource..."
|
||||
- "Child <id> (PID: <pid>): Acquired resource (available: <count>)"
|
||||
- "Child <id> (PID: <pid>): Using resource..."
|
||||
- "Child <id> (PID: <pid>): Released resource"
|
||||
|
||||
Usage: `./semaphore_basics`
|
||||
|
||||
Hint: Named POSIX semaphores (sem_open) work across processes, unlike thread mutexes.
|
||||
sem_wait() decrements the semaphore (blocks if 0), sem_post() increments it.
|
||||
Always call sem_unlink() to remove the semaphore from the system.
|
||||
Semaphore names must start with '/' (e.g., "/my_semaphore").
|
||||
Use O_CREAT | O_EXCL flags with sem_open, and handle if semaphore already exists.
|
||||
28
subjects/15_process_communication.txt
Normal file
28
subjects/15_process_communication.txt
Normal file
@ -0,0 +1,28 @@
|
||||
Assignment name: process_communication
|
||||
Expected files: process_communication.c
|
||||
Allowed functions: memset, printf, malloc, free, write, fork, exit, waitpid,
|
||||
sem_open, sem_close, sem_post, sem_wait, sem_unlink,
|
||||
usleep, gettimeofday
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Implement a producer-consumer pattern using processes and semaphores:
|
||||
1. Create 2 semaphores: one for empty slots (init: 5) and one for filled slots (init: 0)
|
||||
2. Launch 2 producer processes and 2 consumer processes
|
||||
3. Producers "produce" items (increment a counter) and signal consumers
|
||||
4. Consumers wait for items, "consume" them, and signal producers
|
||||
5. Each process performs 10 operations
|
||||
6. Use a third semaphore to protect the shared counter (mutual exclusion)
|
||||
|
||||
Since processes don't share memory, print actions instead of actually sharing data:
|
||||
- Producer: "Producer <id>: Produced item <n> (empty slots: <count>)"
|
||||
- Consumer: "Consumer <id>: Consumed item <n> (filled slots: <count>)"
|
||||
|
||||
Parent waits for all children and cleans up semaphores.
|
||||
|
||||
Usage: `./process_communication`
|
||||
|
||||
Hint: This demonstrates how semaphores coordinate processes (like mutexes for threads).
|
||||
You need 3 semaphores: empty slots, filled slots, and mutex for critical sections.
|
||||
The pattern is similar to producer_consumer but with processes instead of threads.
|
||||
Since processes don't share memory, focus on demonstrating synchronization patterns.
|
||||
sem_wait() on empty before producing, sem_post() on filled after producing.
|
||||
29
subjects/16_process_termination.txt
Normal file
29
subjects/16_process_termination.txt
Normal file
@ -0,0 +1,29 @@
|
||||
Assignment name: process_termination
|
||||
Expected files: process_termination.c
|
||||
Allowed functions: memset, printf, malloc, free, write, fork, kill, exit, waitpid,
|
||||
sem_open, sem_close, sem_post, sem_wait, sem_unlink,
|
||||
usleep, gettimeofday
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Create a program that demonstrates process termination and cleanup:
|
||||
1. Launch 5 child processes that run in a loop (each iteration takes 500ms)
|
||||
2. Each child has a random "death time" between 2-5 seconds
|
||||
3. Parent process monitors all children using waitpid() with WNOHANG
|
||||
4. When a child "dies" (exits), parent detects it and kills all remaining children using kill()
|
||||
5. Parent prints which child died first and performs cleanup
|
||||
|
||||
Output format:
|
||||
- "Child <id> (PID: <pid>): Iteration <n>"
|
||||
- "Child <id> (PID: <pid>): Dying after <time>ms"
|
||||
- "Parent: Child <id> (PID: <pid>) has died!"
|
||||
- "Parent: Terminating remaining children..."
|
||||
- "Parent: Child <id> (PID: <pid>) terminated"
|
||||
- "Parent: All processes cleaned up"
|
||||
|
||||
Usage: `./process_termination`
|
||||
|
||||
Hint: This simulates what happens in philosophers_bonus when one philosopher dies.
|
||||
Use waitpid() with WNOHANG to check if a child has exited without blocking.
|
||||
kill(pid, SIGTERM) or kill(pid, SIGKILL) terminates a process.
|
||||
Proper cleanup is critical - all child processes must be terminated.
|
||||
This is why philosophers_bonus needs a monitoring mechanism in the parent.
|
||||
37
subjects/17_process_philosophers.txt
Normal file
37
subjects/17_process_philosophers.txt
Normal file
@ -0,0 +1,37 @@
|
||||
Assignment name: process_philosophers
|
||||
Expected files: process_philosophers.c
|
||||
Allowed functions: memset, printf, malloc, free, write, fork, kill, exit,
|
||||
pthread_create, pthread_detach, pthread_join, waitpid,
|
||||
sem_open, sem_close, sem_post, sem_wait, sem_unlink,
|
||||
usleep, gettimeofday
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Implement a simplified philosophers problem using processes and semaphores:
|
||||
1. Create N child processes (one per philosopher)
|
||||
2. Use a semaphore initialized to N to represent the forks in the middle of the table
|
||||
3. Each philosopher process:
|
||||
- Waits to acquire 2 forks (sem_wait called twice)
|
||||
- Eats for a specified time
|
||||
- Releases 2 forks (sem_post called twice)
|
||||
- Sleeps for a specified time
|
||||
- Repeats for a specified number of meals
|
||||
4. Each philosopher has a monitoring thread that checks if it's starving
|
||||
5. Parent process waits for all children; if any dies, kills all others
|
||||
|
||||
Arguments: `number_of_philosophers time_to_die time_to_eat time_to_sleep number_of_meals`
|
||||
|
||||
Output format:
|
||||
- "[timestamp] Philosopher <id>: has taken a fork"
|
||||
- "[timestamp] Philosopher <id>: is eating"
|
||||
- "[timestamp] Philosopher <id>: is sleeping"
|
||||
- "[timestamp] Philosopher <id>: is thinking"
|
||||
- "[timestamp] Philosopher <id>: died"
|
||||
|
||||
Usage: `./process_philosophers 5 800 200 200 7`
|
||||
|
||||
Hint: This combines everything: processes, semaphores, monitoring, and termination.
|
||||
Each philosopher is a process with its own monitoring thread (just like bonus).
|
||||
The semaphore represents all forks in the middle (different from mandatory version).
|
||||
Use pthread_create within each child process for the death monitor thread.
|
||||
Parent monitors children with waitpid(WNOHANG) and kills all if one dies.
|
||||
This is almost the complete philosophers_bonus, but simplified.
|
||||
@ -59,7 +59,12 @@ if [ $# -eq 1 ]; then
|
||||
9) run_test "$TESTER_DIR/test_9_death_monitor.sh" "Exercise 9: Death Monitor" ;;
|
||||
10) run_test "$TESTER_DIR/test_10_philosophers_args.sh" "Exercise 10: Philosophers Args" ;;
|
||||
11) run_test "$TESTER_DIR/test_11_race_detector.sh" "Exercise 11: Race Detector" ;;
|
||||
12) run_test "$TESTER_DIR/test_12_philosophers_bonus.sh" "Exercise 12: Philosophers Bonus" ;;
|
||||
13) run_test "$TESTER_DIR/test_13_process_basics.sh" "Exercise 13: Process Basics" ;;
|
||||
14) run_test "$TESTER_DIR/test_14_semaphore_basics.sh" "Exercise 14: Semaphore Basics" ;;
|
||||
15) run_test "$TESTER_DIR/test_15_process_communication.sh" "Exercise 15: Process Communication" ;;
|
||||
16) run_test "$TESTER_DIR/test_16_process_termination.sh" "Exercise 16: Process Termination" ;;
|
||||
17) run_test "$TESTER_DIR/test_17_process_philosophers.sh" "Exercise 17: Process Philosophers" ;;
|
||||
18) run_test "$TESTER_DIR/test_18_philosophers_bonus.sh" "Exercise 18: Philosophers Bonus" ;;
|
||||
*)
|
||||
echo "Usage: $0 [exercise_number]"
|
||||
echo "Example: $0 1 (to test only exercise 1)"
|
||||
@ -80,7 +85,12 @@ else
|
||||
run_test "$TESTER_DIR/test_9_death_monitor.sh" "Exercise 9: Death Monitor"
|
||||
run_test "$TESTER_DIR/test_10_philosophers_args.sh" "Exercise 10: Philosophers Args"
|
||||
run_test "$TESTER_DIR/test_11_race_detector.sh" "Exercise 11: Race Detector"
|
||||
run_test "$TESTER_DIR/test_12_philosophers_bonus.sh" "Exercise 12: Philosophers Bonus"
|
||||
run_test "$TESTER_DIR/test_12_process_basics.sh" "Exercise 12: Process Basics"
|
||||
run_test "$TESTER_DIR/test_13_semaphore_basics.sh" "Exercise 13: Semaphore Basics"
|
||||
run_test "$TESTER_DIR/test_14_process_communication.sh" "Exercise 14: Process Communication"
|
||||
run_test "$TESTER_DIR/test_15_process_termination.sh" "Exercise 15: Process Termination"
|
||||
run_test "$TESTER_DIR/test_16_process_philosophers.sh" "Exercise 16: Process Philosophers"
|
||||
run_test "$TESTER_DIR/test_17_philosophers_bonus.sh" "Exercise 17: Philosophers Bonus"
|
||||
fi
|
||||
|
||||
# Final summary
|
||||
|
||||
172
testers/test_12_process_basics.sh
Executable file
172
testers/test_12_process_basics.sh
Executable file
@ -0,0 +1,172 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tester for process_basics exercise
|
||||
# Tests basic process creation with fork and waitpid
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
EXERCISE="process_basics"
|
||||
EXE_PATH=""
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Determine the project root directory, which is one level up from the 'testers' directory.
|
||||
PROJECT_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
echo "========================================"
|
||||
echo "Testing: $EXERCISE"
|
||||
echo "========================================"
|
||||
|
||||
# --- Compilation and Executable Finding Logic ---
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE"
|
||||
RENDU_EXE_PATH="$PROJECT_ROOT/rendu/$EXERCISE"
|
||||
SOURCE_FILE="$PROJECT_ROOT/${EXERCISE}.c"
|
||||
RENDU_SOURCE_FILE="$PROJECT_ROOT/rendu/${EXERCISE}.c"
|
||||
|
||||
# Prefer executable in 'rendu' directory
|
||||
if [ -f "$RENDU_EXE_PATH" ]; then
|
||||
EXE_PATH="$RENDU_EXE_PATH"
|
||||
# Fallback to root directory executable
|
||||
elif [ -f "$EXE_PATH" ]; then
|
||||
: # EXE_PATH is already set correctly
|
||||
# If no executable, try to compile from source
|
||||
else
|
||||
COMPILE_CANDIDATE=""
|
||||
if [ -f "$RENDU_SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$RENDU_SOURCE_FILE"
|
||||
elif [ -f "$SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$SOURCE_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "$COMPILE_CANDIDATE" ]; then
|
||||
echo -e "${YELLOW}Executable not found, attempting to compile from $COMPILE_CANDIDATE...${NC}"
|
||||
# Compile into the root directory
|
||||
gcc -Wall -Wextra -Werror "$COMPILE_CANDIDATE" -o "$PROJECT_ROOT/$EXERCISE"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Compilation successful.${NC}"
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE" # Use the newly compiled executable
|
||||
else
|
||||
echo -e "${RED}✗ Compilation failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Executable and source file for '$EXERCISE' not found.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${YELLOW}Using executable at: $EXE_PATH${NC}"
|
||||
# --- End of Logic ---
|
||||
|
||||
# Test 1: Program creates 3 child processes
|
||||
echo -n "Test 1: Creates 3 child processes... "
|
||||
OUTPUT=$(timeout 5 "$EXE_PATH" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
CHILD_COUNT=$(echo "$OUTPUT" | grep -c "Child [123]")
|
||||
if [ "$CHILD_COUNT" -ge 3 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected output from 3 children, found: $CHILD_COUNT"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ FAILED (timeout or crash)${NC}"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 2: Each child prints PID
|
||||
echo -n "Test 2: Each child prints PID... "
|
||||
PID_COUNT=$(echo "$OUTPUT" | grep -oE "PID: [0-9]+" | wc -l)
|
||||
if [ "$PID_COUNT" -ge 15 ]; then # 3 children * 5 messages each
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected PIDs in output, found: $PID_COUNT references"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 3: Each child prints 5 messages
|
||||
echo -n "Test 3: Each child prints 5 messages... "
|
||||
for i in 1 2 3; do
|
||||
COUNT=$(echo "$OUTPUT" | grep "Child $i" | grep -c "Message")
|
||||
if [ "$COUNT" -ne 5 ]; then
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Child $i printed $COUNT messages instead of 5"
|
||||
((FAILED++))
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ $? -eq 0 ] && [ "$COUNT" -eq 5 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
fi
|
||||
|
||||
# Test 4: Parent waits for all children
|
||||
echo -n "Test 4: Parent waits for all children... "
|
||||
if echo "$OUTPUT" | grep -q "Parent: All 3 children have finished"; then
|
||||
# Verify parent message comes after all child messages
|
||||
LAST_CHILD_LINE=$(echo "$OUTPUT" | grep -n "Child" | tail -1 | cut -d: -f1)
|
||||
PARENT_LINE=$(echo "$OUTPUT" | grep -n "Parent: All 3 children have finished" | cut -d: -f1)
|
||||
if [ "$PARENT_LINE" -gt "$LAST_CHILD_LINE" ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Parent message appeared before all children finished"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Parent summary message not found"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 5: No zombie processes (check process cleanup)
|
||||
echo -n "Test 5: Process cleanup (no zombies)... "
|
||||
# Run the program in background and check for zombie processes
|
||||
"$EXE_PATH" > /dev/null 2>&1 &
|
||||
PARENT_PID=$!
|
||||
sleep 1
|
||||
ZOMBIE_COUNT=$(ps aux | grep "$PARENT_PID" | grep -c "defunct")
|
||||
wait $PARENT_PID
|
||||
if [ "$ZOMBIE_COUNT" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Found $ZOMBIE_COUNT zombie processes"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 6: Check for memory leaks with valgrind
|
||||
if command -v valgrind &> /dev/null; then
|
||||
echo -n "Test 6: Memory leak check... "
|
||||
VALGRIND_OUTPUT=$(valgrind --leak-check=full --error-exitcode=42 --trace-children=yes "$EXE_PATH" 2>&1)
|
||||
if [ $? -ne 42 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Memory leaks detected!"
|
||||
echo "$VALGRIND_OUTPUT" | grep -A 5 "LEAK SUMMARY"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⊘ Test 6: Valgrind not installed, skipping memory test${NC}"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo "========================================"
|
||||
echo -e "Results: ${GREEN}$PASSED passed${NC}, ${RED}$FAILED failed${NC}"
|
||||
echo "========================================"
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
176
testers/test_13_semaphore_basics.sh
Executable file
176
testers/test_13_semaphore_basics.sh
Executable file
@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tester for semaphore_basics exercise
|
||||
# Tests POSIX named semaphores usage
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
EXERCISE="semaphore_basics"
|
||||
EXE_PATH=""
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Determine the project root directory, which is one level up from the 'testers' directory.
|
||||
PROJECT_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
echo "========================================"
|
||||
echo "Testing: $EXERCISE"
|
||||
echo "========================================"
|
||||
|
||||
# --- Compilation and Executable Finding Logic ---
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE"
|
||||
RENDU_EXE_PATH="$PROJECT_ROOT/rendu/$EXERCISE"
|
||||
SOURCE_FILE="$PROJECT_ROOT/${EXERCISE}.c"
|
||||
RENDU_SOURCE_FILE="$PROJECT_ROOT/rendu/${EXERCISE}.c"
|
||||
|
||||
# Prefer executable in 'rendu' directory
|
||||
if [ -f "$RENDU_EXE_PATH" ]; then
|
||||
EXE_PATH="$RENDU_EXE_PATH"
|
||||
# Fallback to root directory executable
|
||||
elif [ -f "$EXE_PATH" ]; then
|
||||
: # EXE_PATH is already set correctly
|
||||
# If no executable, try to compile from source
|
||||
else
|
||||
COMPILE_CANDIDATE=""
|
||||
if [ -f "$RENDU_SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$RENDU_SOURCE_FILE"
|
||||
elif [ -f "$SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$SOURCE_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "$COMPILE_CANDIDATE" ]; then
|
||||
echo -e "${YELLOW}Executable not found, attempting to compile from $COMPILE_CANDIDATE...${NC}"
|
||||
# Compile with -pthread flag for semaphores
|
||||
gcc -Wall -Wextra -Werror -pthread "$COMPILE_CANDIDATE" -o "$PROJECT_ROOT/$EXERCISE"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Compilation successful.${NC}"
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE" # Use the newly compiled executable
|
||||
else
|
||||
echo -e "${RED}✗ Compilation failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Executable and source file for '$EXERCISE' not found.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${YELLOW}Using executable at: $EXE_PATH${NC}"
|
||||
# --- End of Logic ---
|
||||
|
||||
# Clean up any leftover semaphores before testing
|
||||
sem_unlink /test_semaphore 2>/dev/null || true
|
||||
|
||||
# Test 1: Program creates and cleans up semaphore
|
||||
echo -n "Test 1: Basic execution... "
|
||||
OUTPUT=$(timeout 10 "$EXE_PATH" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Program failed to execute properly (exit code: $EXIT_CODE)"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 2: Creates 5 child processes
|
||||
echo -n "Test 2: Creates 5 child processes... "
|
||||
CHILD_COUNT=$(echo "$OUTPUT" | grep -oE "Child [0-9]+" | sort -u | wc -l)
|
||||
if [ "$CHILD_COUNT" -eq 5 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected 5 unique children, found: $CHILD_COUNT"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 3: Resource acquisition and release
|
||||
echo -n "Test 3: Resource acquisition/release... "
|
||||
ACQUIRED_COUNT=$(echo "$OUTPUT" | grep -c "Acquired resource")
|
||||
RELEASED_COUNT=$(echo "$OUTPUT" | grep -c "Released resource")
|
||||
if [ "$ACQUIRED_COUNT" -eq 5 ] && [ "$RELEASED_COUNT" -eq 5 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected 5 acquire and 5 release, got: $ACQUIRED_COUNT acquire, $RELEASED_COUNT release"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 4: Semaphore limits access (max 3 concurrent)
|
||||
echo -n "Test 4: Semaphore limits concurrent access... "
|
||||
# Check that we see "Waiting for resource" messages (proof that not all can access at once)
|
||||
WAITING_COUNT=$(echo "$OUTPUT" | grep -c "Waiting for resource")
|
||||
if [ "$WAITING_COUNT" -gt 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC} ($WAITING_COUNT wait events)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠ PARTIAL${NC}"
|
||||
echo "Expected some processes to wait (semaphore initialized to 3, but 5 processes)"
|
||||
((PASSED++))
|
||||
fi
|
||||
|
||||
# Test 5: Proper semaphore cleanup (sem_unlink called)
|
||||
echo -n "Test 5: Semaphore cleanup... "
|
||||
# Run the program again - if semaphore wasn't cleaned up, it might fail to create
|
||||
OUTPUT2=$(timeout 10 "$EXE_PATH" 2>&1)
|
||||
EXIT_CODE2=$?
|
||||
if [ $EXIT_CODE2 -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Second execution failed - semaphore may not have been properly cleaned up"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 6: No zombie processes
|
||||
echo -n "Test 6: No zombie processes... "
|
||||
"$EXE_PATH" > /dev/null 2>&1 &
|
||||
PARENT_PID=$!
|
||||
sleep 1
|
||||
ZOMBIE_COUNT=$(ps aux | grep "$PARENT_PID" | grep -c "defunct" || echo "0")
|
||||
wait $PARENT_PID 2>/dev/null
|
||||
if [ "$ZOMBIE_COUNT" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Found $ZOMBIE_COUNT zombie processes"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 7: Check for memory leaks with valgrind
|
||||
if command -v valgrind &> /dev/null; then
|
||||
echo -n "Test 7: Memory leak check... "
|
||||
VALGRIND_OUTPUT=$(valgrind --leak-check=full --error-exitcode=42 --trace-children=yes "$EXE_PATH" 2>&1)
|
||||
if [ $? -ne 42 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Memory leaks detected!"
|
||||
echo "$VALGRIND_OUTPUT" | grep -A 5 "LEAK SUMMARY"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⊘ Test 7: Valgrind not installed, skipping memory test${NC}"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
sem_unlink /test_semaphore 2>/dev/null || true
|
||||
|
||||
# Summary
|
||||
echo "========================================"
|
||||
echo -e "Results: ${GREEN}$PASSED passed${NC}, ${RED}$FAILED failed${NC}"
|
||||
echo "========================================"
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
190
testers/test_14_process_communication.sh
Executable file
190
testers/test_14_process_communication.sh
Executable file
@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tester for process_communication exercise
|
||||
# Tests producer-consumer pattern with processes and semaphores
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
EXERCISE="process_communication"
|
||||
EXE_PATH=""
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Determine the project root directory, which is one level up from the 'testers' directory.
|
||||
PROJECT_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
echo "========================================"
|
||||
echo "Testing: $EXERCISE"
|
||||
echo "========================================"
|
||||
|
||||
# --- Compilation and Executable Finding Logic ---
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE"
|
||||
RENDU_EXE_PATH="$PROJECT_ROOT/rendu/$EXERCISE"
|
||||
SOURCE_FILE="$PROJECT_ROOT/${EXERCISE}.c"
|
||||
RENDU_SOURCE_FILE="$PROJECT_ROOT/rendu/${EXERCISE}.c"
|
||||
|
||||
# Prefer executable in 'rendu' directory
|
||||
if [ -f "$RENDU_EXE_PATH" ]; then
|
||||
EXE_PATH="$RENDU_EXE_PATH"
|
||||
# Fallback to root directory executable
|
||||
elif [ -f "$EXE_PATH" ]; then
|
||||
: # EXE_PATH is already set correctly
|
||||
# If no executable, try to compile from source
|
||||
else
|
||||
COMPILE_CANDIDATE=""
|
||||
if [ -f "$RENDU_SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$RENDU_SOURCE_FILE"
|
||||
elif [ -f "$SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$SOURCE_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "$COMPILE_CANDIDATE" ]; then
|
||||
echo -e "${YELLOW}Executable not found, attempting to compile from $COMPILE_CANDIDATE...${NC}"
|
||||
gcc -Wall -Wextra -Werror -pthread "$COMPILE_CANDIDATE" -o "$PROJECT_ROOT/$EXERCISE"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Compilation successful.${NC}"
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE" # Use the newly compiled executable
|
||||
else
|
||||
echo -e "${RED}✗ Compilation failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Executable and source file for '$EXERCISE' not found.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${YELLOW}Using executable at: $EXE_PATH${NC}"
|
||||
# --- End of Logic ---
|
||||
|
||||
# Test 1: Program executes successfully
|
||||
echo -n "Test 1: Basic execution... "
|
||||
OUTPUT=$(timeout 15 "$EXE_PATH" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Program failed to execute (exit code: $EXIT_CODE)"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 2: Creates 2 producers and 2 consumers
|
||||
echo -n "Test 2: Creates 2 producers and 2 consumers... "
|
||||
PRODUCER_COUNT=$(echo "$OUTPUT" | grep -oE "Producer [0-9]+" | sort -u | wc -l)
|
||||
CONSUMER_COUNT=$(echo "$OUTPUT" | grep -oE "Consumer [0-9]+" | sort -u | wc -l)
|
||||
if [ "$PRODUCER_COUNT" -eq 2 ] && [ "$CONSUMER_COUNT" -eq 2 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected 2 producers and 2 consumers, got: $PRODUCER_COUNT producers, $CONSUMER_COUNT consumers"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 3: Each producer produces 10 items
|
||||
echo -n "Test 3: Producers produce items... "
|
||||
PRODUCED_TOTAL=$(echo "$OUTPUT" | grep -c "Produced item")
|
||||
if [ "$PRODUCED_TOTAL" -ge 15 ]; then # 2 producers * 10 items = 20, allow some margin
|
||||
echo -e "${GREEN}✓ PASSED${NC} ($PRODUCED_TOTAL items produced)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected ~20 produced items, got: $PRODUCED_TOTAL"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 4: Each consumer consumes 10 items
|
||||
echo -n "Test 4: Consumers consume items... "
|
||||
CONSUMED_TOTAL=$(echo "$OUTPUT" | grep -c "Consumed item")
|
||||
if [ "$CONSUMED_TOTAL" -ge 15 ]; then # 2 consumers * 10 items = 20, allow some margin
|
||||
echo -e "${GREEN}✓ PASSED${NC} ($CONSUMED_TOTAL items consumed)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected ~20 consumed items, got: $CONSUMED_TOTAL"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 5: Synchronization - no deadlock
|
||||
echo -n "Test 5: No deadlock... "
|
||||
# If the program completed successfully, there was no deadlock
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Program may have deadlocked (timeout or abnormal exit)"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 6: Proper semaphore usage (interleaved production/consumption)
|
||||
echo -n "Test 6: Production/consumption interleaving... "
|
||||
# Extract sequence of produce/consume actions
|
||||
FIRST_PRODUCE=$(echo "$OUTPUT" | grep -n "Produced item" | head -1 | cut -d: -f1)
|
||||
FIRST_CONSUME=$(echo "$OUTPUT" | grep -n "Consumed item" | head -1 | cut -d: -f1)
|
||||
LAST_PRODUCE=$(echo "$OUTPUT" | grep -n "Produced item" | tail -1 | cut -d: -f1)
|
||||
LAST_CONSUME=$(echo "$OUTPUT" | grep -n "Consumed item" | tail -1 | cut -d: -f1)
|
||||
|
||||
# Check that production and consumption are interleaved (not all production then all consumption)
|
||||
if [ -n "$FIRST_PRODUCE" ] && [ -n "$FIRST_CONSUME" ] && [ -n "$LAST_PRODUCE" ] && [ -n "$LAST_CONSUME" ]; then
|
||||
if [ "$FIRST_CONSUME" -lt "$LAST_PRODUCE" ] && [ "$FIRST_PRODUCE" -lt "$LAST_CONSUME" ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠ PARTIAL${NC}"
|
||||
echo "Production and consumption may not be properly synchronized"
|
||||
((PASSED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Could not verify interleaving"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 7: No zombie processes
|
||||
echo -n "Test 7: No zombie processes... "
|
||||
"$EXE_PATH" > /dev/null 2>&1 &
|
||||
PARENT_PID=$!
|
||||
sleep 1
|
||||
ZOMBIE_COUNT=$(ps aux | grep "$PARENT_PID" | grep -c "defunct" || echo "0")
|
||||
wait $PARENT_PID 2>/dev/null
|
||||
if [ "$ZOMBIE_COUNT" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Found $ZOMBIE_COUNT zombie processes"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 8: Check for memory leaks with valgrind
|
||||
if command -v valgrind &> /dev/null; then
|
||||
echo -n "Test 8: Memory leak check... "
|
||||
VALGRIND_OUTPUT=$(valgrind --leak-check=full --error-exitcode=42 --trace-children=yes "$EXE_PATH" 2>&1)
|
||||
if [ $? -ne 42 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Memory leaks detected!"
|
||||
echo "$VALGRIND_OUTPUT" | grep -A 5 "LEAK SUMMARY"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⊘ Test 8: Valgrind not installed, skipping memory test${NC}"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo "========================================"
|
||||
echo -e "Results: ${GREEN}$PASSED passed${NC}, ${RED}$FAILED failed${NC}"
|
||||
echo "========================================"
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
198
testers/test_15_process_termination.sh
Executable file
198
testers/test_15_process_termination.sh
Executable file
@ -0,0 +1,198 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tester for process_termination exercise
|
||||
# Tests process monitoring and termination with kill()
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
EXERCISE="process_termination"
|
||||
EXE_PATH=""
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Determine the project root directory, which is one level up from the 'testers' directory.
|
||||
PROJECT_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
echo "========================================"
|
||||
echo "Testing: $EXERCISE"
|
||||
echo "========================================"
|
||||
|
||||
# --- Compilation and Executable Finding Logic ---
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE"
|
||||
RENDU_EXE_PATH="$PROJECT_ROOT/rendu/$EXERCISE"
|
||||
SOURCE_FILE="$PROJECT_ROOT/${EXERCISE}.c"
|
||||
RENDU_SOURCE_FILE="$PROJECT_ROOT/rendu/${EXERCISE}.c"
|
||||
|
||||
# Prefer executable in 'rendu' directory
|
||||
if [ -f "$RENDU_EXE_PATH" ]; then
|
||||
EXE_PATH="$RENDU_EXE_PATH"
|
||||
# Fallback to root directory executable
|
||||
elif [ -f "$EXE_PATH" ]; then
|
||||
: # EXE_PATH is already set correctly
|
||||
# If no executable, try to compile from source
|
||||
else
|
||||
COMPILE_CANDIDATE=""
|
||||
if [ -f "$RENDU_SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$RENDU_SOURCE_FILE"
|
||||
elif [ -f "$SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$SOURCE_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "$COMPILE_CANDIDATE" ]; then
|
||||
echo -e "${YELLOW}Executable not found, attempting to compile from $COMPILE_CANDIDATE...${NC}"
|
||||
gcc -Wall -Wextra -Werror -pthread "$COMPILE_CANDIDATE" -o "$PROJECT_ROOT/$EXERCISE"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Compilation successful.${NC}"
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE" # Use the newly compiled executable
|
||||
else
|
||||
echo -e "${RED}✗ Compilation failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Executable and source file for '$EXERCISE' not found.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${YELLOW}Using executable at: $EXE_PATH${NC}"
|
||||
# --- End of Logic ---
|
||||
|
||||
# Test 1: Program executes and terminates properly
|
||||
echo -n "Test 1: Basic execution... "
|
||||
OUTPUT=$(timeout 10 "$EXE_PATH" 2>&1)
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Program failed to execute (exit code: $EXIT_CODE)"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 2: Creates 5 child processes
|
||||
echo -n "Test 2: Creates 5 child processes... "
|
||||
CHILD_COUNT=$(echo "$OUTPUT" | grep -oE "Child [0-9]+" | sort -u | wc -l)
|
||||
if [ "$CHILD_COUNT" -eq 5 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected 5 unique children, found: $CHILD_COUNT"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 3: Detects when a child dies
|
||||
echo -n "Test 3: Detects child death... "
|
||||
if echo "$OUTPUT" | grep -q "has died"; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Parent didn't detect child death"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 4: Parent terminates remaining children
|
||||
echo -n "Test 4: Terminates remaining children... "
|
||||
TERMINATED_COUNT=$(echo "$OUTPUT" | grep -c "terminated")
|
||||
if [ "$TERMINATED_COUNT" -ge 4 ]; then # At least 4 children should be terminated
|
||||
echo -e "${GREEN}✓ PASSED${NC} ($TERMINATED_COUNT children terminated)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected at least 4 children to be terminated, got: $TERMINATED_COUNT"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 5: Proper cleanup message
|
||||
echo -n "Test 5: Cleanup message... "
|
||||
if echo "$OUTPUT" | grep -q "All processes cleaned up"; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Missing cleanup confirmation message"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 6: Program duration (should terminate after first child dies, 2-5 seconds)
|
||||
echo -n "Test 6: Terminates after first death... "
|
||||
START_TIME=$(date +%s)
|
||||
timeout 10 "$EXE_PATH" > /dev/null 2>&1
|
||||
END_TIME=$(date +%s)
|
||||
DURATION=$((END_TIME - START_TIME))
|
||||
|
||||
if [ $DURATION -ge 2 ] && [ $DURATION -le 7 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC} (ran for ${DURATION}s)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠ PARTIAL${NC} (ran for ${DURATION}s, expected 2-5s)"
|
||||
echo "This may be acceptable depending on random death times"
|
||||
((PASSED++))
|
||||
fi
|
||||
|
||||
# Test 7: No zombie processes remain after execution
|
||||
echo -n "Test 7: No zombie processes... "
|
||||
"$EXE_PATH" > /dev/null 2>&1 &
|
||||
PARENT_PID=$!
|
||||
sleep 2
|
||||
ZOMBIE_COUNT=$(ps aux | grep "$PARENT_PID" | grep -c "defunct" || echo "0")
|
||||
wait $PARENT_PID 2>/dev/null
|
||||
sleep 1
|
||||
ZOMBIE_COUNT_AFTER=$(ps aux | grep "defunct" | grep -c "$EXERCISE" || echo "0")
|
||||
if [ "$ZOMBIE_COUNT" -eq 0 ] && [ "$ZOMBIE_COUNT_AFTER" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Found zombie processes (during: $ZOMBIE_COUNT, after: $ZOMBIE_COUNT_AFTER)"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 8: No orphaned processes
|
||||
echo -n "Test 8: No orphaned processes... "
|
||||
"$EXE_PATH" > /dev/null 2>&1
|
||||
sleep 1
|
||||
# Check if there are any processes from this exercise still running
|
||||
ORPHAN_COUNT=$(ps aux | grep "$EXERCISE" | grep -v "grep" | grep -v "test_" | wc -l)
|
||||
if [ "$ORPHAN_COUNT" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Found $ORPHAN_COUNT orphaned processes"
|
||||
# Clean up orphans
|
||||
pkill -f "$EXERCISE" 2>/dev/null
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 9: Check for memory leaks with valgrind
|
||||
if command -v valgrind &> /dev/null; then
|
||||
echo -n "Test 9: Memory leak check... "
|
||||
VALGRIND_OUTPUT=$(valgrind --leak-check=full --error-exitcode=42 --trace-children=yes "$EXE_PATH" 2>&1)
|
||||
if [ $? -ne 42 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Memory leaks detected!"
|
||||
echo "$VALGRIND_OUTPUT" | grep -A 5 "LEAK SUMMARY"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⊘ Test 9: Valgrind not installed, skipping memory test${NC}"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo "========================================"
|
||||
echo -e "Results: ${GREEN}$PASSED passed${NC}, ${RED}$FAILED failed${NC}"
|
||||
echo "========================================"
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
240
testers/test_16_process_philosophers.sh
Executable file
240
testers/test_16_process_philosophers.sh
Executable file
@ -0,0 +1,240 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Tester for process_philosophers exercise
|
||||
# Tests complete philosophers implementation with processes and semaphores
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
EXERCISE="process_philosophers"
|
||||
EXE_PATH=""
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
# Determine the project root directory, which is one level up from the 'testers' directory.
|
||||
PROJECT_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
echo "========================================"
|
||||
echo "Testing: $EXERCISE"
|
||||
echo "========================================"
|
||||
|
||||
# --- Compilation and Executable Finding Logic ---
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE"
|
||||
RENDU_EXE_PATH="$PROJECT_ROOT/rendu/$EXERCISE"
|
||||
SOURCE_FILE="$PROJECT_ROOT/${EXERCISE}.c"
|
||||
RENDU_SOURCE_FILE="$PROJECT_ROOT/rendu/${EXERCISE}.c"
|
||||
|
||||
# Prefer executable in 'rendu' directory
|
||||
if [ -f "$RENDU_EXE_PATH" ]; then
|
||||
EXE_PATH="$RENDU_EXE_PATH"
|
||||
# Fallback to root directory executable
|
||||
elif [ -f "$EXE_PATH" ]; then
|
||||
: # EXE_PATH is already set correctly
|
||||
# If no executable, try to compile from source
|
||||
else
|
||||
COMPILE_CANDIDATE=""
|
||||
if [ -f "$RENDU_SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$RENDU_SOURCE_FILE"
|
||||
elif [ -f "$SOURCE_FILE" ]; then
|
||||
COMPILE_CANDIDATE="$SOURCE_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "$COMPILE_CANDIDATE" ]; then
|
||||
echo -e "${YELLOW}Executable not found, attempting to compile from $COMPILE_CANDIDATE...${NC}"
|
||||
gcc -Wall -Wextra -Werror -pthread "$COMPILE_CANDIDATE" -o "$PROJECT_ROOT/$EXERCISE"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Compilation successful.${NC}"
|
||||
EXE_PATH="$PROJECT_ROOT/$EXERCISE" # Use the newly compiled executable
|
||||
else
|
||||
echo -e "${RED}✗ Compilation failed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ Executable and source file for '$EXERCISE' not found.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo -e "${YELLOW}Using executable at: $EXE_PATH${NC}"
|
||||
# --- End of Logic ---
|
||||
|
||||
# Test 1: Basic execution with valid arguments (5 800 200 200 7)
|
||||
echo -n "Test 1: Basic execution (5 philos, should complete)... "
|
||||
OUTPUT=$(timeout 15 "$EXE_PATH" 5 800 200 200 7 2>&1)
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Program failed or timed out (exit code: $EXIT_CODE)"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 2: Correct number of philosophers
|
||||
echo -n "Test 2: Correct number of philosophers... "
|
||||
PHILO_COUNT=$(echo "$OUTPUT" | grep -oE "Philosopher [0-9]+" | sort -u | wc -l)
|
||||
if [ "$PHILO_COUNT" -eq 5 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected 5 philosophers, found: $PHILO_COUNT"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 3: All actions present (fork, eating, sleeping, thinking)
|
||||
echo -n "Test 3: All actions present... "
|
||||
HAS_FORK=$(echo "$OUTPUT" | grep -c "taken a fork")
|
||||
HAS_EAT=$(echo "$OUTPUT" | grep -c "is eating")
|
||||
HAS_SLEEP=$(echo "$OUTPUT" | grep -c "is sleeping")
|
||||
HAS_THINK=$(echo "$OUTPUT" | grep -c "is thinking")
|
||||
|
||||
if [ "$HAS_FORK" -gt 0 ] && [ "$HAS_EAT" -gt 0 ] && [ "$HAS_SLEEP" -gt 0 ] && [ "$HAS_THINK" -gt 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Missing actions - fork:$HAS_FORK eat:$HAS_EAT sleep:$HAS_SLEEP think:$HAS_THINK"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 4: Each philosopher eats required number of times
|
||||
echo -n "Test 4: Each philosopher eats 7 times... "
|
||||
ALL_ATE_ENOUGH=true
|
||||
for i in 1 2 3 4 5; do
|
||||
EAT_COUNT=$(echo "$OUTPUT" | grep "Philosopher $i" | grep -c "is eating")
|
||||
if [ "$EAT_COUNT" -lt 7 ]; then
|
||||
ALL_ATE_ENOUGH=false
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Philosopher $i ate only $EAT_COUNT times (expected 7)"
|
||||
((FAILED++))
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$ALL_ATE_ENOUGH" = true ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
fi
|
||||
|
||||
# Test 5: Timestamps are present and increasing
|
||||
echo -n "Test 5: Timestamps present and valid... "
|
||||
if echo "$OUTPUT" | grep -qE "\[[0-9]+\]"; then
|
||||
# Extract first and last timestamp
|
||||
FIRST_TS=$(echo "$OUTPUT" | grep -oE "\[[0-9]+\]" | head -1 | tr -d '[]')
|
||||
LAST_TS=$(echo "$OUTPUT" | grep -oE "\[[0-9]+\]" | tail -1 | tr -d '[]')
|
||||
if [ "$LAST_TS" -gt "$FIRST_TS" ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC} (${FIRST_TS}ms -> ${LAST_TS}ms)"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Timestamps not increasing properly"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "No timestamps found in output"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 6: No philosopher should die (with 5 800 200 200 7)
|
||||
echo -n "Test 6: No philosopher dies (5 800 200 200 7)... "
|
||||
if echo "$OUTPUT" | grep -q "died"; then
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "A philosopher died when none should have"
|
||||
((FAILED++))
|
||||
else
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
fi
|
||||
|
||||
# Test 7: Death detection (4 310 200 100 should die)
|
||||
echo -n "Test 7: Death detection (4 310 200 100)... "
|
||||
OUTPUT_DEATH=$(timeout 5 "$EXE_PATH" 4 310 200 100 2>&1)
|
||||
if echo "$OUTPUT_DEATH" | grep -q "died"; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Expected a philosopher to die with tight timing"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 8: Single philosopher case (should not eat - only one fork)
|
||||
echo -n "Test 8: Single philosopher (1 800 200 200)... "
|
||||
OUTPUT_SINGLE=$(timeout 3 "$EXE_PATH" 1 800 200 200 2>&1)
|
||||
SINGLE_EXIT=$?
|
||||
# Single philosopher should die (can't eat with one fork)
|
||||
if echo "$OUTPUT_SINGLE" | grep -q "died"; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠ PARTIAL${NC}"
|
||||
echo "Single philosopher handling may vary"
|
||||
((PASSED++))
|
||||
fi
|
||||
|
||||
# Test 9: Invalid arguments handling
|
||||
echo -n "Test 9: Invalid arguments handling... "
|
||||
timeout 2 "$EXE_PATH" 0 800 200 200 2>&1 > /dev/null
|
||||
EXIT1=$?
|
||||
timeout 2 "$EXE_PATH" -5 800 200 200 2>&1 > /dev/null
|
||||
EXIT2=$?
|
||||
timeout 2 "$EXE_PATH" abc 800 200 200 2>&1 > /dev/null
|
||||
EXIT3=$?
|
||||
|
||||
if [ $EXIT1 -ne 0 ] && [ $EXIT2 -ne 0 ] && [ $EXIT3 -ne 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Program should reject invalid arguments"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 10: No zombie processes
|
||||
echo -n "Test 10: No zombie processes... "
|
||||
"$EXE_PATH" 3 800 200 200 5 > /dev/null 2>&1 &
|
||||
PARENT_PID=$!
|
||||
sleep 2
|
||||
ZOMBIE_COUNT=$(ps aux | grep "$PARENT_PID" | grep -c "defunct" || echo "0")
|
||||
wait $PARENT_PID 2>/dev/null
|
||||
sleep 1
|
||||
ZOMBIE_COUNT_AFTER=$(ps aux | grep "defunct" | grep -c "$EXERCISE" || echo "0")
|
||||
if [ "$ZOMBIE_COUNT" -eq 0 ] && [ "$ZOMBIE_COUNT_AFTER" -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Found zombie processes"
|
||||
((FAILED++))
|
||||
fi
|
||||
|
||||
# Test 11: Check for memory leaks with valgrind (short run)
|
||||
if command -v valgrind &> /dev/null; then
|
||||
echo -n "Test 11: Memory leak check... "
|
||||
VALGRIND_OUTPUT=$(valgrind --leak-check=full --error-exitcode=42 --trace-children=yes "$EXE_PATH" 3 800 200 200 2 2>&1)
|
||||
if [ $? -ne 42 ]; then
|
||||
echo -e "${GREEN}✓ PASSED${NC}"
|
||||
((PASSED++))
|
||||
else
|
||||
echo -e "${RED}✗ FAILED${NC}"
|
||||
echo "Memory leaks detected!"
|
||||
echo "$VALGRIND_OUTPUT" | grep -A 5 "LEAK SUMMARY"
|
||||
((FAILED++))
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⊘ Test 11: Valgrind not installed, skipping memory test${NC}"
|
||||
fi
|
||||
|
||||
# Summary
|
||||
echo "========================================"
|
||||
echo -e "Results: ${GREEN}$PASSED passed${NC}, ${RED}$FAILED failed${NC}"
|
||||
echo "========================================"
|
||||
|
||||
if [ $FAILED -eq 0 ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Reference in New Issue
Block a user