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:
Rui Ribeiro 2025-10-14 16:37:24 +01:00
parent f8f51f8cc2
commit 2128325237
14 changed files with 1137 additions and 4 deletions

View File

@ -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 */
/* */
/* ************************************************************************** */

View 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.

View 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.

View 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.

View 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.

View 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.

View File

@ -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
View 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

View 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

View 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

View 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

View 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