-
Notifications
You must be signed in to change notification settings - Fork 1
/
program-tester.sh
executable file
·194 lines (165 loc) · 5.36 KB
/
program-tester.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#!/usr/bin/bash
# -|-- ------------------------------------------------------------------ --|-
# -|-- Program Tester --|-
# -|-- ------------------------------------------------------------------ --|-
# Takes a filename as input, compiles it if needed, then runs test cases on it.
# These test cases need to be named 1.in, 2.in, 3.in... etc. The ground truths need to
# be named 1.ans, 2.ans, 3.ans... etc (alternatively .out is accepted as well).
# It will then show you whether your result matches these ground truths.
# Supports Python, Julia, C and C++ right now.
# Additionally implement a quick way to run it with your editor
# I use a vim mapping (add to your .vimrc):
# nnoremap <CR> :w<CR>:!/absolute/file/location/program-tester.sh %<CR>
# TODO:
#
# Improve code:
# * Use tmp files which are guaranteed to not exist instead of tmp{1-4}
#
# New features:
# * Better color support (e.g. highlight entire test case green when it succeeds)
# * Print time for worst test case (also show time in red for test cases running longer than 2 s)
#
# Flags:
# * Add a flag to only show bad tests
# * Flag to print only short test cases
# * Flag to write .ans files (e.g. when a correct solution is available)
# * Add check for flags if they make sense (e.g. if width is a number)
#
# Bugfixes:
# * Fix internationalization (use diff return codes)
# * Fix long lines getting cut off in diff
width=$(tput cols)
color=false
onlySummary=false
testsPattern="*.in"
# Skip required positional first argument in 'getopts'
[[ "$1" != "-h" ]] && OPTIND=2
usage="
Usage: program-tester.sh SOURCE-FILE [OPTIONS]
SOURCE-FILE (required):
Is the source file to be ran (e.g.: G.py, solution.cpp).
Currently C, C++ and Python are supported.
OPTIONS:
-s print only the summary
-c add color when printing
-p PATTERN print specific test cases matching the glob (e.g. '1.in', 'test*.in')
-w WIDTH overwrite width of columns in characters (by default maximum possible)
"
# Handle flags
while getopts 'hscp:w:' flag; do
case "${flag}" in
s) onlySummary=true;;
c) color=true;;
p) testsPattern="$OPTARG";;
w) width="$OPTARG";;
h) echo "$usage"; exit 0;;
esac
done
if $color; then
RESET="\033[0m"
BOLD="\033[1m"
BLACK="\033[30m"
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
PURPLE="\033[35m"
CYAN="\033[36m"
WHITE="\033[37m"
fi
noAnsFileMsg="No results file! Input instead!"
different=""
unknown=0
good=0
bad=0
total=0
extension="${1##*.}"
name="${1%.*}"
binaryName="$name.bin"
# Compile c++ and c if needed, add set-up runCommand to run each test
case "$extension" in
cpp)
g++ -std=c++2a -O3 -Wall -Wextra -Wshadow -o "$binaryName" "$1" 2>&1 || exit
runCommand="./$binaryName"
;;
c)
gcc "$1" -o "$binaryName"
runCommand="./$binaryName"
;;
py)
runCommand="python3 $1"
;;
jl)
runCommand="julia $1"
;;
java)
javac -cp ".;*" "$1"
runCommand="java $name"
;;
kt)
kotlinc -include-runtime "$1" -d "${name}.jar"
runCommand="java -jar ${name}.jar"
;;
rs)
rustc "$1"
runCommand="./${name}"
;;
esac
if [[ -z "$runCommand" ]]; then
echo "ERROR: Was not able to infer run command from given file $1!"
exit 1
fi
# Don't add quotes here, as otherwise the glob is not expanded
for file in $testsPattern; do
if [[ -e "$file" ]]; then
# Print dividing line
echo -ne "──┤$YELLOW$BOLD$file$RESET├"
for i in $(seq $(($width - 4 - ${#file}))); do
echo -n "─"
done
echo ""
runFile="${file%.in}.run"
{ time $runCommand < "$file" > "$runFile" ; } 2>tmp4
# Check if either .ans or .out file exists
testFile="${file%.in}.ans"
if ! [[ -e "$testFile" ]]; then
testFile="${file%.in}.out"
fi
# Depending if there is the results file use it as comparison file
lineNumberCommand="nl -s ' │ '"
if [[ -e "$testFile" ]]; then
eval "$lineNumberCommand" "$testFile" > tmp1
else
eval "$lineNumberCommand" "$file" > tmp1
echo -e "$noAnsFileMsg" >> "tmp1"
fi
# Show the difference between the two files
eval "$lineNumberCommand" "$runFile" > tmp2
# echo -e "$($lineNumberCommand $runFile)" > tmp2
diff --ignore-trailing-space --report-identical-files --side-by-side --width=$width --color=auto tmp1 tmp2 > tmp3
diffexit="$?"
if [[ "$diffexit" == 0 ]]; then
good=$(($good + 1))
elif ! [[ -e "$testFile" ]]; then
unknown=$(($unknown + 1))
else
bad=$(($bad + 1))
different="$different $file"
fi
if ! $onlySummary; then
cat tmp3
fi
cat tmp4
# Calculate totals
total=$(($total + 1))
# Delete tmp files
rm -f tmp1 tmp2 tmp3 tmp4
fi
done
printf '%.0s─' $(seq 1 $width)
echo -en "\n${GREEN}Good: $good/$total$RESET; "
echo -en "${RED}Bad: $bad/$total$RESET; "
echo -e "${BLUE}Unknown: $unknown/$total$RESET"
if [[ "$different" ]]; then
echo -e "Bad tests:$different"
fi