├── .gitignore ├── no-die.txt ├── yes-die.txt ├── PhilosophersChecker.py ├── README.md └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /no-die.txt: -------------------------------------------------------------------------------- 1 | 5 800 200 200 2 | no one should die 3 | 5 600 150 150 4 | no one should die 5 | 4 410 200 200 6 | no one should die 7 | 100 800 200 200 8 | no one should die 9 | 105 800 200 200 10 | no one should die 11 | 200 800 200 200 12 | no one should die -------------------------------------------------------------------------------- /yes-die.txt: -------------------------------------------------------------------------------- 1 | 1 800 200 200 2 | a philo should die 3 | 4 310 200 100 4 | a philo should die 5 | 4 200 205 200 6 | a philo should die 7 | 5 800 200 200 7 8 | no one should die, simulation should stop after 7 eats 9 | 4 410 200 200 10 10 | no one should die, simulation should stop after 10 eats 11 | -5 600 200 200 12 | should error and not run (no crashing) 13 | 4 -5 200 200 14 | should error and not run (no crashing) 15 | 4 600 -5 200 16 | should error and not run (no crashing) 17 | 4 600 200 -5 18 | should error and not run (no crashing) 19 | 4 600 200 200 -5 20 | should error and not run (no crashing) -------------------------------------------------------------------------------- /PhilosophersChecker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import subprocess 4 | import fcntl 5 | import time 6 | import os 7 | import signal 8 | import sys 9 | 10 | if len(sys.argv) < 2: 11 | print("Please give command uWu") 12 | exit(1) 13 | 14 | # === CHANGE THIS === 15 | command = sys.argv[1] 16 | timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 10 17 | # === NO TOUCHY === 18 | 19 | # Vars 20 | pipe = None 21 | pid = 0 22 | 23 | # Create datastream from demodulator 24 | pipe = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) 25 | 26 | # Make subprocess non-blocking 27 | fcntl.fcntl(pipe.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) 28 | 29 | time.sleep(1) 30 | 31 | # If cannot open: 32 | if pipe.poll() != None: 33 | print("Never started...") 34 | exit(1) 35 | else: 36 | pid = pipe.pid 37 | print("PID: %s" % (pid)) 38 | 39 | # Do some counting 40 | for x in range(0, timeout): 41 | print(timeout - x) 42 | if pipe.poll() != None: 43 | print("Died :(") 44 | exit(1) 45 | time.sleep(1) 46 | 47 | # Kill dem! 48 | if pipe != None: 49 | os.system("killall philo SIGTERM") 50 | pipe.kill() 51 | pipe = None 52 | 53 | # STONKS! 54 | print("Worked! :D") 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lazy Philosophers Tester 2 | A semi-automatic tester for checking 42's philosophers project (2021 curriculum). 3 | It allows you to test 2 scenarios: 4 | 1. when your program should stop, either due to death, enough eaten, or bad input 5 | - to be checked manually by the user, based on the expected result listed in yes-die.txt. 6 | 2. when no philosophers should die 7 | - this is checked automatically if the program runs for x seconds (default 10) without death. 8 | 9 | More tests can be added by editing the appropriate ```.txt``` files with the input and expected outcome. 10 | 11 | ![screenshot of tester being run](https://i.imgur.com/iAfsRWM.png) 12 | 13 | ## Installation 14 | Clone the tester into the root directory of your philosophers project. 15 | ```bash 16 | https://github.com/MichelleJiam/LazyPhilosophersTester.git 17 | ``` 18 | 19 | ## Usage 20 | 21 | If you haven't already, run ```make``` in your philo directory to create your ```./philo``` executable. 22 | Then from within the LazyPhilosophersTester directory, run ```./test.sh``` to start the tester. 23 | Tester takes an optional 2nd argument of the path to your ```philo``` executable. 24 | 25 | Example: 26 | ```bash 27 | ./test.sh ../philo 28 | ``` 29 | If not provided, the tester assumes the path is ```../philo``` - i.e. in same directory as tester directory. 30 | 31 | ### Adding tests 32 | If you wish to add your own tests, open either: 33 | 1. ```no-die.txt``` for tests where philos aren't supposed to die, or 34 | 2. ```yes-die.txt``` for tests where the program should stop (death, eaten enough, error). 35 | 36 | The 1st line should be your test case, separated by spaces. This is what is inputted into your program. 37 | The 2nd line should be the expected outcome. This is outputted during the tester for checking purposes. 38 | 39 | Example: 40 | ```text 41 | 4 310 200 100 42 | a philo should die 43 | 5 800 200 200 7 44 | no one should die, simulation should stop after 7 eats 45 | ``` 46 | 47 | ## Credits 48 | Timed-checker Python script ```PhilosophersChecker.py``` [(link)](https://gist.github.com/jkctech/367fad4aa01c820ffb1b8d29d1ecaa4d) was written by [JKCTech](https://gist.github.com/jkctech) and modified slightly by me to take an optional timer duration. 49 | [Progress bar function](https://stackoverflow.com/a/52581824) written by Vagiz Duseev, found on StackOverflow. 50 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Colours 4 | BOLD="\033[0;1m" 5 | RED="\033[0;31m" 6 | CYAN="\033[0;36m" 7 | PURP="\033[0;35m" 8 | GREEN="\033[0;32m" 9 | BLUE="\033[0;34m" 10 | BLUEBG="\033[44m" 11 | WHITE="\033[1;97m" 12 | RESET="\033[0m" 13 | 14 | # Counters 15 | PASS=0 16 | FAIL=0 17 | TESTS=0 18 | 19 | # checks params. If none given, assumes philo executable is in same directory as tester directory. 20 | if [ "$#" -gt 1 ]; then 21 | printf "Too many parameters. Please only provide path to philo executable.\n" 22 | exit 23 | elif [ "$#" -lt 1 ]; then 24 | set $1 ../philo 25 | fi 26 | 27 | # checks if given executable path and file is valid. 28 | if [ ! -f "$1" ]; then 29 | printf "$1 not found or invalid file. Please (re)make philo executable first.\n" 30 | exit 31 | fi 32 | 33 | PROGRESS_BAR_WIDTH=50 # progress bar length in characters 34 | 35 | draw_progress_bar() { 36 | # Arguments: current value, max value, unit of measurement (optional) 37 | local __value=$1 38 | local __max=$2 39 | local __unit=${3:-""} # if unit is not supplied, do not display it 40 | 41 | # Calculate percentage 42 | if (( $__max < 1 )); then __max=1; fi # anti zero division protection 43 | local __percentage=$(( 100 - ($__max*100 - $__value*100) / $__max )) 44 | 45 | # Rescale the bar according to the progress bar width 46 | local __num_bar=$(( $__percentage * $PROGRESS_BAR_WIDTH / 100 )) 47 | 48 | # Draw progress bar 49 | printf "[" 50 | for b in $(seq 1 $__num_bar); do printf "#"; done 51 | for s in $(seq 1 $(( $PROGRESS_BAR_WIDTH - $__num_bar ))); do printf " "; done 52 | printf "] $__percentage%% ($__value / $__max $__unit)\r" 53 | } 54 | 55 | die_test () { 56 | printf "\n${CYAN}=== Starting tests where program should end with death or enough eaten ===\n${RESET}" 57 | while IFS="" read -r -u 3 input || [ -n "$input" ] # read input from fd 3 58 | do 59 | read -r -u 3 result # read desired result description from input.txt 60 | printf "\nTest: ${BLUEBG}${WHITE}[$input]${RESET} | ${PURP}$result${RESET}\n\n" 61 | read -rs -n 1 -p $'Press ENTER to start test, press any other key to exit tester...\n' key # read from stdin, accepting only 1 char 62 | if [[ $key == "" ]] ; then 63 | printf "\n" 64 | $1 $input # run ./philo with test case input 65 | else 66 | exit 0 67 | fi 68 | done 3< ./yes-die.txt # open file is assigned fd 3 69 | exec 3<&- # close fd 3 70 | } 71 | 72 | no_die_test () { 73 | printf "\n${CYAN}=== Starting tests where a philosopher should NOT die ===\n${RESET}" 74 | read -r -p $'\nPlease enter desired timer for tests or press ENTER to use default 10 seconds: ' timeout 75 | printf "\n" 76 | if [[ $timeout != *[[:digit:]]* ]]; then 77 | timeout=10 78 | fi 79 | while IFS="" read -r -u 3 input || [ -n "$input" ] # read input from fd 3 80 | do 81 | read -r -u 3 result # read desired result description from input.txt 82 | printf "\nTest: ${BLUEBG}${WHITE}[$input]${RESET} | ${PURP}$result${RESET}\n\n" 83 | read -rs -n 1 -p $'Press ENTER to start test, press any other key to exit tester...\n' key # read from stdin, accepting only 1 char 84 | if [[ $key == "" ]] ; then 85 | printf "\n" 86 | ./PhilosophersChecker.py "$1 $input" $timeout > /dev/null & pid=$! # silence checker output and run in bg 87 | local elapsed=0 88 | while ps -p $pid &>/dev/null; do # check if checker script still running 89 | draw_progress_bar $elapsed $timeout "seconds" # TODO: fix extra space at end of progress bar, extra ) 90 | if [[ $elapsed == $timeout ]]; then 91 | printf "\n\n${GREEN}OK${RESET}\n" 92 | (( PASS++ )) 93 | break; 94 | fi 95 | (( elapsed++ )) 96 | sleep 1 97 | done 98 | wait $pid 99 | status=$? 100 | if [[ $status != 0 ]]; then 101 | printf "\n\n${RED}KO${RESET} - program terminated prematurely\n" 102 | (( FAIL++ )) 103 | fi 104 | else 105 | printf "\n${GREEN}PASSED${RESET}: $PASS/$TESTS | ${RED}FAILED${RESET}: $FAIL/$TESTS\n" 106 | exit 0 107 | fi 108 | (( TESTS++ )) 109 | done 3< ./no-die.txt # open file is assigned fd 3 110 | printf "\nNo-Die Tests: ${GREEN}PASSED${RESET}: $PASS/$TESTS | ${RED}FAILED${RESET}: $FAIL/$TESTS\n" 111 | exec 3<&- # close fd 3 112 | } 113 | 114 | choose_test() { 115 | read -rn1 -p $'\nChoose test to run:\n\t[0] all tests\n\t[1] die tests\n\t[2] no-die tests (can take a while)\n\t[ESC] exit tester\n\n' choice 116 | printf "\n" 117 | case $choice in 118 | 0) die_test "$1" && no_die_test "$1" ;; 119 | 1) die_test "$1" ;; 120 | 2) no_die_test "$1" ;; 121 | $'\e') exit 0 ;; 122 | *) printf "${RED}Invalid choice\n${RESET}"; choose_test "$1" ;; 123 | esac 124 | } 125 | 126 | printf "${BOLD}\n💭 The Lazy Philosophers Tester 💭\n${RESET}" 127 | printf "\nThis tester allows you to test:\n\n" 128 | printf "\t1. when your program should stop on death or when all philos have eaten enough\n" 129 | printf "\t- to be checked manually by the user, based on the expected result listed in yes-die.txt.\n\n" 130 | printf "\t2. when no philosophers should die\n" 131 | printf "\t- this is checked automatically if the program runs for x seconds (default 10) without death.\n" 132 | choose_test "$1" 133 | --------------------------------------------------------------------------------