├── infiles ├── empty.txt └── basic.txt ├── last_err_log.txt ├── LICENSE ├── utils.sh ├── README.md ├── run.sh └── test.sh /infiles/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /last_err_log.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Michael Moser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /infiles/basic.txt: -------------------------------------------------------------------------------- 1 | Good draw knew bred ham busy his hour. Ask agreed answer rather joy nature admire wisdom. Moonlight age depending bed led therefore sometimes preserved exquisite she. An fail up so shot leaf wise in. Minuter highest his arrived for put and. Hopes lived by rooms oh in no death house. Contented direction september but end led excellent ourselves may. Ferrars few arrival his offered not charmed you. Offered anxious respect or he. On three thing chief years in money arise of. 2 | 3 | Did shy say mention enabled through elderly improve. As at so believe account evening behaved hearted is. House is tiled we aware. It ye greatest removing concerns an overcame appetite. Manner result square father boy behind its his. Their above spoke match ye mr right oh as first. Be my depending to believing perfectly concealed household. Point could to built no hours smile sense. 4 | 5 | Finished her are its honoured drawings nor. Pretty see mutual thrown all not edward ten. Particular an boisterous up he reasonably frequently. Several any had enjoyed shewing studied two. Up intention remainder sportsmen behaviour ye happiness. Few again any alone style added abode ask. Nay projecting unpleasing boisterous eat discovered solicitude. Own six moments produce elderly pasture far arrival. Hold our year they ten upon. Gentleman contained so intention sweetness in on resolving. 6 | -------------------------------------------------------------------------------- /utils.sh: -------------------------------------------------------------------------------- 1 | 2 | BOLD="\033[1m" 3 | BLACK="\033[30;1m" 4 | RED="\033[31;1m" 5 | GREEN="\033[32;1m" 6 | YELLOW="\033[33;1m" 7 | MAGENTA="\033[35;1m" 8 | RESET="\033[0m" 9 | 10 | DELIMITER="------------------------------------------------------------------------------------------------------------------------" 11 | 12 | set_flags () { 13 | if [ "$1" == --help ]; then print_help && exit 0; fi 14 | if [[ "$1" == "--show-valgrind" || "$2" == "--show-valgrind" ]]; then SHOW_VALGRIND=1; else SHOW_VALGRIND=0; fi 15 | if [[ "$1" == "--hide-err-log" || "$2" == "--hide-err-log" ]]; then HIDE_LOG=1; else HIDE_LOG=0; fi 16 | # check only one test 17 | if [[ "$1" == --test=* ]]; then 18 | TEST_NUM_ONLY="${1#--test=}" 19 | elif [[ "$2" == --test=* ]]; then 20 | TEST_NUM_ONLY="${2#--test=}" 21 | else 22 | TEST_NUM_ONLY=-1 23 | fi 24 | } 25 | 26 | print_help () { 27 | printf "${BOLD}%-20s %s${RESET}\n" "FLAG" "MEANING" 28 | printf "%-20s %s\n" "--hide-err-log" "hide detailed information on KOs" 29 | printf "%-20s %s\n" "--show-valgrind" "show detailed valgrind output" 30 | printf "%-20s %s\n" "--test=" "run only the test number " 31 | } 32 | 33 | print_arg_array() { 34 | local size=${#ARG_ARRAY[@]} 35 | 36 | for ((i=0; i<$size; i++)); do 37 | printf "\"%s\" " "${ARG_ARRAY[i]}" 38 | done 39 | } 40 | 41 | print_err_log () { 42 | if (( $(wc -c < "last_err_log.txt") != 0)); then 43 | printf "$RED$BOLD\nERRORS :($RESET\n" 44 | cat last_err_log.txt 45 | fi 46 | } 47 | 48 | print_test_case() { 49 | printf "${RED}%s\n" "$DELIMITER" 50 | printf "TEST %i:\n${RESET}" ${TEST_NUM} 51 | 52 | printf "${BOLD}./pipex " 53 | print_arg_array 54 | 55 | printf "\n%s\n\n${RESET}" "${ARG_STR}" 56 | } 57 | 58 | print_header() { 59 | printf "${YELLOW}\n%s\n${RESET}" "$1" 60 | } 61 | 62 | tester_setup() { 63 | rm pipex 64 | 65 | # compiling 66 | make -C ${PIPEX_DIR} all 67 | make -C ${PIPEX_DIR} bonus 68 | printf "\n" 69 | cp ${PIPEX_DIR}/pipex ./ 2> /dev/null 70 | if [ -f "${PIPEX_DIR}/pipex" ] && [ -x "${PIPEX_DIR}/pipex" ];then 71 | printf "%-20s$GREEN%-8s$RESET\n" "compiling" "[OK]" 72 | else 73 | printf "%-20s$RED%-8s \"pipex\" not found. Check that correct path is set in run.sh file$RESET\n\n" "compiling" "[KO]"; exit 1 74 | fi 75 | 76 | # norminette 77 | if type "norminette" > /dev/null 2>&1; then 78 | if norminette ${PIPEX_DIR} | grep 'Error!' > /dev/null; then 79 | printf "%-20s$RED%-8s$RESET\n" "norminette" "[KO]" 80 | else 81 | printf "%-20s$GREEN%-8s$RESET\n" "norminette" "[OK]" 82 | fi 83 | else 84 | printf "$RED$BOLD norminette not found $RESET" 85 | fi 86 | 87 | printf "\n\n$BOLD$MAGENTA%-90s%-8s%-8s%-8s%-8s\n$RESET" "TESTNAME" "OUT" "EXIT" "TIME" "LEAKS" 88 | 89 | exec 2> /dev/null 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 42 pipex tester 2 | This is a tester for the project `pipex` from the ecole 42 core curriculum. It is designed to facilitate the addition of your own custom test cases. 3 | 4 | > This tester is written **for linux**. There might be issues if you use a different OS :exclamation: 5 | 6 | > If the tester helps you, leave a star on github to make it more visible for others :star: 7 | 8 | > If you find a bug, send me a message on slack please (@mmoser) :email: 9 | 10 | ## Usage 11 | ### Download 12 | Clone the repository **into your pipex directory**: 13 | ``` 14 | git clone https://github.com/michmos/42_pipex_tester.git && cd 42_pipex_tester 15 | ``` 16 | Alternatively, you can clone it elsewhere and adjust the relative path inside `run.sh`. 17 | 18 | Ensure you have a Makefile in place and that your program compiles to `pipex` (also the bonus, in case you did it). 19 | 20 | ### Run 21 | Run the tester like this: 22 | ``` 23 | bash run.sh 24 | ``` 25 | 26 | The behaviour can be modified by adding the following flags: 27 | | Flag | Meaning | 28 | | ------------------- | -------------------------------------------------------------------- | 29 | | `--help` | display all flags and their usage | 30 | | `--hide-err-log` | hide error log | 31 | | `--show-valgrind` | show valgrind output for tests cases where valgrind found an error | 32 | | `--test=` | run only the test number | 33 | 34 | 35 | e.g.: 36 | ``` 37 | bash run.sh --show-valgrind --test=12 38 | ``` 39 | 40 | ## Layout 41 | ![visualization](https://github.com/michmos/42_pipex_tester/assets/141367977/290d866f-3c3e-4c7d-84c5-2392036d4a15) 42 | The tester compares your program with the original shell piping in terms of: 43 | * **output** to the specified file 44 | * **exit status** 45 | * **time** - differences here may indicate that your parent is not waiting for all children to terminate 46 | * **leaks** in the parent 47 | 48 | 49 | ## Adapt 50 | Test cases can easily be added to `run.sh` following the same structure as the existing ones. 51 | Two variables are available for customization: 52 | * `LEAKS_ONLY`: if set to 1, all subsequent test cases will only be checked for leaks and fatal errors, until it is reset to 0. This is especially useful for test cases that have no equivalent in bash 53 | * `HERE_DOC`: a string used as input for here_doc. Ensure it ends with a \n 54 | 55 | If a test case receives less than 4 arguments without setting LEAKS_ONLY to 0, an error message will be displayed as the subject doesn't clarify how to handle these cases - naturally, your program shouldn't crash. 56 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source test.sh 4 | source utils.sh 5 | 6 | set_flags "$@" 7 | trap 'kill 0;' SIGINT 8 | 9 | # -- SETUP ------------------------------------------------------------------------------# 10 | # ADJUST PATH TO PIPEX DIRECTORY IF NECESSARY (-> IF ITS NOT THE PARENT DIRCTORY) 11 | PIPEX_DIR=$(dirname "$0")/../ 12 | 13 | TIMEOUT=7 14 | rm -rf outfiles 15 | mkdir outfiles 16 | echo -n > last_err_log.txt 17 | 18 | tester_setup 19 | 20 | # -- TEST -------------------------------------------------------------------------------# 21 | print_header "BASIC CHECKS" 22 | test "infiles/basic.txt" "cat -e" "cat -e" "outfiles/outfile" 23 | test "infiles/basic.txt" "ls -la" "cat -e" "outfiles/outfile" 24 | test "infiles/basic.txt" "ls -l -a" "cat -e -n" "outfiles/outfile" 25 | test "infiles/basic.txt" "ls -l -a -f" "cat -e -n" "outfiles/outfile" 26 | test "infiles/basic.txt" "ls -laf" "cat -e -n" "outfiles/outfile" 27 | test "infiles/basic.txt" "grep -A5 is" "cat -e" "outfiles/nonexistingfile" 28 | test "infiles/basic.txt" "cat -e" "grep nonexistingword" "outfiles/nonexistingfile" 29 | test "infiles/empty.txt" "grep nonexistingword" "cat -e" "outfiles/outfile" 30 | test "infiles/basic.txt" "sleep 3" "ls" "outfiles/outfile" 31 | test "infiles/big_text.txt" "cat" "head -2" "outfiles/outfile" 32 | 33 | print_header "ERROR CHECKING" 34 | # invalid input file 35 | test "nonexistingfile" "cat -e" "ls" "outfiles/outfile" 36 | test "nonexistingfile" "cat" "sleep 3" "outfiles/outfile" 37 | touch infiles/infile_without_permissions 38 | chmod 000 infiles/infile_without_permissions 39 | test "infiles/infile_without_permissions" "cat -e" "cat -e" "outfiles/outfile" 40 | # ouput file without permissions 41 | touch outfiles/outfile_without_permissions 42 | chmod 000 outfiles/outfile_without_permissions 43 | test "infiles/basic.txt" "cat -e" "cat -e" "outfiles/outfile_without_permissions" 44 | test "infiles/basic.txt" "sleep 3" "cat -e" "outfiles/outfile_without_permissions" 45 | test "nonexistingfile" "cat -e" "cat -e" "outfiles/outfile_without_permissions" 46 | # wrong argument 47 | test "infiles/basic.txt" "nonexistingcommand" "cat -e" "outfiles/outfile" 48 | test "infiles/basic.txt" "cat -e" "nonexistingcommand" "outfiles/outfile" 49 | test "infiles/basic.txt" "cat -e" "cat -nonexistingflag" "outfiles/outfile" 50 | LEAKS_ONLY=1 # checks only for Leaks (FATAL ERRORS like segfaults are always checked) 51 | # not enough arguments 52 | test 53 | test "" 54 | test "infiles/basic.txt" "cat -e" "outfiles/outfile" 55 | # empty string 56 | test "" "cat -e" "cat -e" "outfiles/outfile" 57 | test "infiles/basic.txt" "" "cat -e" "outfiles/outfile" 58 | test "infiles/basic.txt" "cat -e" "" "outfiles/outfile" 59 | LEAKS_ONLY=0 60 | 61 | print_header "BONUS" 62 | test "infiles/basic.txt" "cat -e" "cat -e" "cat -e" "outfiles/outfile" 63 | # 100 times cat -e 64 | test "infiles/basic.txt" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "cat -e" "outfiles/outfile" 65 | HERE_DOC=$'Hello\nHello\nHello\nEOF\n' 66 | test "here_doc" "EOF" "cat -e" "cat -e" "outfiles/outfile" 67 | 68 | 69 | # -- ERROR_OUTPUT -----------------------------------------------------------------------# 70 | if (( ${HIDE_LOG} == 0 )); then print_err_log; fi 71 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | 2 | PIPEX_DIR="" 3 | 4 | TEST_NUM=0 5 | ERROR_FLAG=0 6 | LEAKS_ONLY=0 7 | 8 | HERE_DOC='' 9 | HERE_DOC_FLAG=0 10 | 11 | OUTPUTFILE="" 12 | OUTPUTFILE1="" 13 | 14 | ARG_ARRAY="" 15 | ARG_STR="" 16 | 17 | test () { 18 | TEST_NUM=$(( TEST_NUM + 1 )) 19 | if (( TEST_NUM_ONLY > 0 )) && (( TEST_NUM != TEST_NUM_ONLY )); then 20 | return 21 | fi 22 | ARG_ARRAY=("$@") 23 | OUTPUTFILE="${ARG_ARRAY[-1]}" 24 | OUTPUTFILE1="${OUTPUTFILE}_tester" 25 | 26 | ERROR_FLAG=0 27 | if [[ ${ARG_ARRAY[0]} == "here_doc" ]]; then 28 | HERE_DOC_FLAG=1 29 | else 30 | HERE_DOC_FLAG=0 31 | fi 32 | 33 | printf "#%2i: %-85.83s" "${TEST_NUM}" "$(print_arg_array)" 34 | if (( ${#ARG_ARRAY[@]} < 4 )) && (( LEAKS_ONLY == 0 )); then 35 | printf "${RED}UNVALID TESTCASE: NOT ENOUGH ARGS FOR COMPARISON. ACTIVATE LEAKS_ONLY OR ADD ARGS\n${RESET}"; return 36 | fi 37 | 38 | # construct command line for og piping 39 | if (( HERE_DOC_FLAG == 1 )); then 40 | ARG_STR="${ARG_ARRAY[2]} << ${ARG_ARRAY[1]}" 41 | for ((i=3; i<${#ARG_ARRAY[@]} - 1; i++));do 42 | ARG_STR+=" | "${ARG_ARRAY[i]}"" 43 | done 44 | ARG_STR+=" >> ${OUTPUTFILE1}" 45 | else 46 | ARG_STR="< ${ARG_ARRAY[0]} ${ARG_ARRAY[1]}" 47 | for (( i=2; i<${#ARG_ARRAY[@]} - 1; i++ ));do 48 | ARG_STR+=" | "${ARG_ARRAY[i]}"" 49 | done 50 | ARG_STR+=" > ${OUTPUTFILE1}" 51 | fi 52 | 53 | # make sure outputfiles are identical before testing and have necessary permissions 54 | if [ -f "$OUTPUTFILE1" ]; then 55 | rm -rf $OUTPUTFILE1 56 | fi 57 | if (( LEAKS_ONLY == 0 )) && [ -f "$OUTPUTFILE" ]; then 58 | # read permissions are necessary for cp and diff operations 59 | chmod u+r $OUTPUTFILE 60 | if [ -w "$OUTPUTFILE" ]; then 61 | echo -e "This is random text echoed into existing outfiles before \napplying pipex. This allows to verify whether your program\nand the original replace or append existing text" > $OUTPUTFILE 62 | fi 63 | cp $OUTPUTFILE $OUTPUTFILE1 64 | fi 65 | 66 | # execute mine and get time and exit status 67 | SECONDS=0 68 | timeout $TIMEOUT ./pipex "${ARG_ARRAY[@]}" < <(echo "${HERE_DOC}") > /dev/null 69 | local exit_status_my=$? 70 | local time_my=$SECONDS 71 | if (( exit_status_my == 124 )); then 72 | printf "${RED}---------- TIMEOUT ----------\n${RESET}" 73 | return 74 | fi 75 | 76 | # execute og and get time and exit status 77 | SECONDS=0 78 | eval "$ARG_STR 79 | ${HERE_DOC}" 2> /dev/null 80 | local exit_status_og=$? 81 | local time_og=$SECONDS 82 | 83 | # get and print results 84 | if (( exit_status_my > 128 && exit_status_my < 250 )); then printf "${RED}------- FATAL ERROR -------\n${RESET}"; return; fi 85 | if (( LEAKS_ONLY == 0 )); then 86 | if [[ ! -f $OUTPUTFILE ]]; then printf "${RED}-- DIDN'T CREATE OUTFILE --\n${RESET}"; return; fi 87 | result_output 88 | result_ex_stat "${exit_status_my}" "${exit_status_og}" 89 | result_time ${time_my} ${time_og} 90 | else 91 | printf "%24s" " " 92 | fi 93 | result_leaks 94 | } 95 | 96 | result_output() { 97 | local temp_file=$(mktemp) 98 | if diff -y "$OUTPUTFILE" "$OUTPUTFILE1" > ${temp_file}; then 99 | printf "${GREEN}%-8s${RESET}" "[OK]" 100 | else 101 | if (( ${ERROR_FLAG} == 0 )); then print_test_case >> last_err_log.txt; fi 102 | ERROR_FLAG=1 103 | printf "${RED}%-8s${RESET}\n" "Output:" >> last_err_log.txt 104 | printf "${RED}%-64s${GREEN}%s${RESET}\n" "${OUTPUTFILE}:" "${OUTPUTFILE1}:" >> last_err_log.txt 105 | cat ${temp_file} >> last_err_log.txt 106 | printf "\n" >> last_err_log.txt 107 | printf "${RED}%-8s${RESET}" "[KO]" 108 | fi 109 | rm "${temp_file}" 110 | } 111 | 112 | result_ex_stat() { 113 | if [ "$1" == "$2" ]; then 114 | printf "${GREEN}%-8s${RESET}" "[OK]" 115 | else 116 | if (( ${ERROR_FLAG} == 0 )); then print_test_case >> last_err_log.txt; fi 117 | ERROR_FLAG=1 118 | printf "${RED}%-8s${RESET}" "[KO]" 119 | printf "${RED}%-8s${RESET}\n" "Exit status:" >> last_err_log.txt 120 | printf "Your exit status: %s\n" $1 >> last_err_log.txt 121 | printf "Orig exit status: %s\n\n" $2 >> last_err_log.txt 122 | fi 123 | } 124 | 125 | result_time() { 126 | if (( $1 <= $2 + 1 )) && (( $1 >= $2 - 1 )); then 127 | printf "${GREEN}%-8s${RESET}" "[OK]" 128 | else 129 | if (( ${ERROR_FLAG} == 0 )); then print_test_case >> last_err_log.txt; fi 130 | ERROR_FLAG=1 131 | printf "${RED}%-8s${RESET}" "[KO]" 132 | printf "${RED}%-8s${RESET}\n" "Time:" >> last_err_log.txt 133 | printf "Your execution time: %s\n" $1 >> last_err_log.txt 134 | printf "Orig execution time: %s\n\n" $2 >> last_err_log.txt 135 | fi 136 | } 137 | 138 | result_leaks() { 139 | local temp_file=$(mktemp) 140 | local timeout=$(($TIMEOUT + 3)) 141 | timeout $TIMEOUT valgrind --log-file=${temp_file} --leak-check=full --errors-for-leak-kinds=all ./pipex "${ARG_ARRAY[@]}" < <(echo -n "${HERE_DOC}") > /dev/null 142 | local timeout=$? 143 | if grep -q "ERROR SUMMARY: [^0]" "${temp_file}"; then 144 | if (( ${ERROR_FLAG} == 0 )); then print_test_case >> last_err_log.txt; fi 145 | ERROR_FLAG=1 146 | printf "${RED}%-8s${RESET}\n" "[KO]" 147 | printf "${RED}%-8s${RESET}\n" "Leaks:" >> last_err_log.txt 148 | if ((timeout == 124)); then 149 | printf "Valgrind timeouts\n" >> last_err_log.txt 150 | else 151 | if (( ${SHOW_VALGRIND} == 1 )); then 152 | cat "${temp_file}" >> last_err_log.txt 153 | printf "\n" >> last_err_log 154 | else 155 | printf "Valgrind found an error. To get valgrind output, you have 2 options\na) run the tester like this: bash run.sh --show-valgrind\nb) run: valgrind --leak-check=full --errors-for-leak-kinds=all ./pipex " >> last_err_log.txt && print_arg_array >> last_err_log.txt 156 | printf "\n" >> last_err_log.txt 157 | fi 158 | fi 159 | else 160 | printf "${GREEN}%-8s${RESET}\n" "[OK]" 161 | fi 162 | rm "${temp_file}" 163 | } 164 | --------------------------------------------------------------------------------