]> mj.ucw.cz Git - eval.git/blob - eval/libeval.sh
Handle exit code translation failures gracefully.
[eval.git] / eval / libeval.sh
1 # The Evaluator -- Shell Function Library
2 # (c) 2001--2008 Martin Mares <mj@ucw.cz>
3
4 # General settings
5 shopt -s dotglob
6
7 # Logging functions.
8 # File handles used: fd1=log, fd2=progress
9
10 function log-init
11 {
12         exec >>$TDIR/log
13         HAVE_LOG=1
14 }
15
16 function pstart
17 {
18         echo >&2 -n "$@"
19 }
20
21 function pcont
22 {
23         echo >&2 -n "$@"
24 }
25
26 function pend
27 {
28         echo >&2 "$@"
29 }
30
31 function die
32 {
33         # Report an internal error
34         echo >&2 "$@"
35         [ -n "$HAVE_LOG" ] && echo "Fatal error: $@"
36         exit 2
37 }
38
39 function fatal
40 {
41         # Report a fatal error in the program being tested
42         echo >&2 "$@"
43         [ -n "$HAVE_LOG" ] && echo "Fatal error: $@"
44         exit 1
45 }
46
47 function try-ln
48 {
49         ln $1 $2 2>/dev/null || cp $1 $2
50 }
51
52 # Expand occurrences of `$var' in a given variable
53
54 function expand-var
55 {
56         eval echo \"${!1}\"
57 }
58
59 # Given a <prefix>, override each variable <x> by <prefix>_<x>
60
61 function override-vars
62 {
63         local OR V W
64         declare -a OR
65         # `${!${1}_@}' does not work, so we have to use eval
66         OR=($(eval echo '${!'$1'_@}'))
67         for V in "${OR[@]}" ; do
68                 W=${V##$1_}
69                 eval $W='"$'$V'"'
70         done
71 }
72
73 # Sandbox subroutines
74
75 function box-init
76 {
77         pstart "Preparing sandbox... "
78
79         # Default values for user/group
80         EVAL_USER=${EVAL_USER:-$USER}
81         EVAL_GROUP=${EVAL_GROUP:-$GROUP}
82         TEST_USER=${TEST_USER:-$EVAL_USER}
83
84         if [ -z "$TEST_USER" -o "$TEST_USER" == "$EVAL_USER" ] ; then
85                 pcont "running locally (INSECURE), "
86                 TEST_USER="$EVAL_USER"
87                 BOXDIR=$HDIR/box
88                 BOXCMD=$HDIR/bin/box
89                 mkdir -p $BOXDIR
90         else
91                 pcont "used account $TEST_USER, "
92                 BOXDIR=$HDIR/box
93                 BOXCMD=$HDIR/bin/box-$TEST_USER
94         fi
95         [ -d $BOXDIR -a -f $BOXCMD ] || die "Sandbox set up incorrectly"
96         BOXCMD="$BOXCMD -c$BOXDIR"
97         echo "Sandbox directory: $BOXDIR"
98         echo "Sandbox command: $BOXCMD"
99         box-clean
100         pend "OK"
101 }
102
103 function box-clean
104 {
105         [ -n "$BOXCMD" ] || die "box-init not called"
106         rm -rf $BOXDIR/*
107 }
108
109 # Initialization of testing directories
110
111 function dir-init
112 {
113         pstart "Initializing... "
114         [ -z "$HDIR" ] && HDIR=.
115         PDIR=$HDIR/problems/$PROBLEM
116         SDIR=$HDIR/solutions/$CONTESTANT/$PROBLEM
117         TDIR=$HDIR/testing/$CONTESTANT/$PROBLEM
118         TMPDIR=$HDIR/tmp/
119
120         [ -d $PDIR ] || die "Problem $PROBLEM not known"
121         [ -d $SDIR ] || fatal "Solution of $PROBLEM not found"
122         mkdir -p $TDIR $TMPDIR
123         rm -rf $TDIR $TMPDIR
124         mkdir -p $TDIR $TMPDIR
125         cat >$TDIR/log <<EOF
126 Testing solution of $PROBLEM by $CONTESTANT
127 Test started at `date`
128 Eval base directory: $HDIR
129 Contestant's solution directory: $SDIR
130 Problem directory: $PDIR
131 Testing directory: $TDIR
132 EOF
133         pend "OK"
134 }
135
136 # Locate source file.
137 # If no parameter is given, locate it in SDIR and return name as SRCN and extension as SRCEXT
138 # Or a file name can be given and then SDIR, SRCN and SRCEXT are set.
139 # Beware, SDIR and SRCN can contain spaces and other strange user-supplied characters.
140
141 function locate-source
142 {
143         pstart "Finding source... "
144         local SBASE
145         if [ -n "$1" ] ; then
146                 SDIR=`dirname "$1"`
147                 local S=`basename "$1"`
148                 SBASE=$(echo "$S" | sed 's/\.\([^.]\+\)//')
149                 SRCEXT=$(echo "$S" | sed '/\./!d; s/.*\.\([^.]\+\)/\1/')
150                 if [ -n "$SRCEXT" ] ; then
151                         # Full name given, so just check the extension and existence
152                         SRCN="$S"
153                         [ -f "$SDIR/$SRCN" ] || die "Cannot find source file $SDIR/$SRCN"
154                         SRCEXT_OK=
155                         for a in $EXTENSIONS ; do
156                                 if [ $a == $SRCEXT ] ; then
157                                         pend $SDIR/$SRCN
158                                         echo "Explicitly set source file: $SDIR/$SRCN"
159                                         return 0
160                                 fi
161                         done
162                         die "Unknown extension .$SRCEXT"
163                 fi
164         else
165                 SBASE=$PROBLEM
166         fi
167         for a in $EXTENSIONS ; do
168                 if [ -f "$SDIR/$SBASE.$a" ] ; then
169                         [ -z "$SRCN" ] || die "Multiple source files found: $SDIR/$PROBLEM.$a and $SDIR/$SRCN. Please fix."
170                         SRCN="$SBASE.$a"
171                         SRCEXT=$a
172                 fi
173         done
174         [ -n "$SRCN" ] || fatal "NOT FOUND"
175         pend $SRCN
176         echo "Found source file: $SDIR/$SRCN"
177 }
178
179 # Compilation (compile SDIR/SRCN with PDIR/COMP_EXTRAS to EXE=TDIR/PROBLEM)
180
181 function compile
182 {
183         pstart "Compiling... "
184
185         a="ALIAS_EXT_$SRCEXT"
186         if [ -n "${!a}" ] ; then
187                 SRCEXT="${!a}"
188                 echo "Normalized file extension: $SRCEXT"
189         fi
190         override-vars "EXT_$SRCEXT"
191
192         # Beware, the original SRCN can be a strange user-supplied name
193         SRC=$PROBLEM.$SRCEXT
194         cp "$SDIR/$SRCN" $TDIR/$SRC
195         if [ -n "$COMP_EXTRAS" ] ; then
196                 echo "Extras: $COMP_EXTRAS"
197                 for a in $COMP_EXTRAS ; do cp $PDIR/$a $TDIR/ ; done
198         fi
199
200         box-clean
201         for a in $SRC $COMP_EXTRAS ; do cp $TDIR/$a $BOXDIR/ ; done
202         EXE=$PROBLEM
203         CCMD=$(expand-var COMP)
204         COMP_SANDBOX_OPTS=$(expand-var COMP_SANDBOX_OPTS)
205         echo "Compiler command: $CCMD"
206         echo "Compiler sandbox options: $COMP_SANDBOX_OPTS"
207         if [ -n "$PRE_COMPILE_HOOK" ] ; then
208                 echo "Pre-compile hook: $PRE_COMPILE_HOOK"
209                 eval $PRE_COMPILE_HOOK
210         fi
211
212         echo "Compiler input files:"
213         ls -Al $BOXDIR
214         echo "Compiler output:"
215         if ! $BOXCMD $COMP_SANDBOX_OPTS -- $CCMD 2>$TDIR/compile.out ; then
216                 COMPILE_MSG="`cat $TDIR/compile.out`"
217                 pend "FAILED: $COMPILE_MSG"
218                 echo "$COMPILE_MSG"
219                 return 1
220         fi
221         cat $TDIR/compile.out
222         rm $TDIR/compile.out
223         if [ -n "$POST_COMPILE_HOOK" ] ; then
224                 echo "Post-compile hook: $POST_COMPILE_HOOK"
225                 eval $POST_COMPILE_HOOK
226         fi
227         echo "Compiler output files:"
228         ls -Al $BOXDIR
229         if [ ! -f $BOXDIR/$PROBLEM ] ; then
230                 pend "FAILED: Missing executable file"
231                 echo "Missing executable file"
232                 return 1
233         fi
234         EXE=$TDIR/$PROBLEM
235         cp -a $BOXDIR/$PROBLEM $EXE
236         echo "Compiled OK, result copied to $EXE"
237         pend "OK"
238 }
239
240 # Running of test program according to current task type (returns exit code and TEST_MSG)
241
242 function test-config
243 {
244         [ -f $PDIR/$TEST.config ] && . $PDIR/$TEST.config
245         override-vars "TEST_$TEST"
246 }
247
248 function test-run
249 {
250         test-run-$TASK_TYPE
251 }
252
253 function test-result
254 {
255         P=$1
256         M=$2
257         if [ -s $TDIR/$TEST.pts ] ; then
258                 P=`cat $TDIR/$TEST.pts`
259                 rm $TDIR/$TEST.pts
260         fi
261
262         # Translate signal numbers to readable strings
263         SG=${M#Caught fatal signal }
264         SG=${SG#Committed suicide by signal }
265         if [ "$SG" != "$M" ] ; then
266                 SG=`kill -l $SG 2>/dev/null` || SG=
267                 [ -z "$SG" ] || M="$M (SIG$SG)"
268         fi
269
270         # Translate runtime errors to readable strings
271         RE=${M#Exited with error status }
272         if [ -n "$EXIT_CODE_HOOK" -a "$RE" != "$M" ] ; then
273                 NEWMSG=`$EXIT_CODE_HOOK $RE` || NEWMSG=
274                 if [ -n "$NEWMSG" ] ; then
275                         M="Runtime error $RE: $NEWMSG"
276                 fi
277         fi
278
279         echo "Verdict: $M"
280         echo "Points: $P"
281         test-verdict $P "$M"
282 }
283
284 function test-prolog
285 {
286         pcont "<init> "
287         box-clean
288         echo "Executable file: $TDIR/$PROBLEM"
289         if [ ! -x $TDIR/$PROBLEM ] ; then
290                 test-result 0 "Compile error"
291         fi
292         cp $TDIR/$PROBLEM $BOXDIR/
293         IN_TYPE=${IN_TYPE:-$IO_TYPE}
294         OUT_TYPE=${OUT_TYPE:-$IO_TYPE}
295         case $IN_TYPE in
296                 file)   echo "Input file: $PROBLEM.in (from $PDIR/$TEST.in)"
297                         try-ln $PDIR/$TEST.in $TDIR/$TEST.in
298                         cp $PDIR/$TEST.in $BOXDIR/$PROBLEM.in
299                         [ $TASK_TYPE == interactive ] || BOX_EXTRAS="$BOX_EXTRAS -i/dev/null"
300                         ;;
301                 stdio)  echo "Input file: <stdin> (from $PDIR/$TEST.in)"
302                         try-ln $PDIR/$TEST.in $TDIR/$TEST.in
303                         cp $PDIR/$TEST.in $BOXDIR/.stdin
304                         BOX_EXTRAS="$BOX_EXTRAS -i.stdin"
305                         ;;
306                 none)   echo "Input file: <none>"
307                         ;;
308                 dir)    echo "Input file: files in directory $PDIR/$TEST.in/"
309                         [ -d $PDIR/$TEST.in ] || die "Not a directory: $PDIR/$TEST.in"
310                         # TODO: recursive ln to $TDIR
311                         cp -r $PDIR/$TEST.in $TDIR/$TEST.in
312                         cp -r $PDIR/$TEST.in/* $BOXDIR/
313                         # Can have .stdin, but empty by default
314                         touch $BOXDIR/.stdin
315                         BOX_EXTRAS="$BOX_EXTRAS -i.stdin"
316                         ;;
317                 *)      die "Unknown IN_TYPE $IN_TYPE"
318                         ;;
319         esac
320         if [ -n "$EV_PEDANT" -a $IN_TYPE != none ] ; then
321                 pcont "<pedant> "
322                 if [ "$EV_PEDANT" = 1 ] ; then
323                         EV_PEDANT=" "
324                 fi
325                 bin/pedant <$TDIR/$TEST.in >$TDIR/$TEST.pedant $EV_PEDANT
326                 if [ -s $TDIR/$TEST.pedant ] ; then
327                         pend
328                         sed 's/^/\t/' <$TDIR/$TEST.pedant >&2
329                         pstart -e '\t'
330                 fi
331         fi
332         case $OUT_TYPE in
333                 file)   echo "Output file: $PROBLEM.out"
334                         [ $TASK_TYPE == interactive ] || BOX_EXTRAS="$BOX_EXTRAS -o/dev/null"
335                         ;;
336                 stdio)  echo "Output file: <stdout>"
337                         BOX_EXTRAS="$BOX_EXTRAS -o.stdout"
338                         ;;
339                 none)   echo "Output file: <none>"
340                         ;;
341                 *)      die "Unknown OUT_TYPE $OUT_TYPE"
342                         ;;
343         esac
344         echo "Timeout: $TIME_LIMIT s"
345         echo "Memory: $MEM_LIMIT KB"
346         if [ -n "$PRE_RUN_HOOK" ] ; then
347                 echo "Pre-run hook: $PRE_RUN_HOOK"
348                 eval $PRE_RUN_HOOK
349         fi
350         echo "Sandbox contents before start:"
351         ls -Al $BOXDIR
352 }
353
354 function test-epilog
355 {
356         if [ -n "$POST_RUN_HOOK" ] ; then
357                 echo "Post-run hook: $POST_RUN_HOOK"
358                 eval $POST_RUN_HOOK
359         fi
360         echo "Sandbox contents after exit:"
361         ls -Al $BOXDIR
362         case ${OUT_TYPE:-$IO_TYPE} in
363                 file)   [ -f $BOXDIR/$PROBLEM.out ] || test-result 0 "No output file"
364                         cp $BOXDIR/$PROBLEM.out $TDIR/$TEST.out
365                         ;;
366                 stdio)  [ -f $BOXDIR/.stdout ] || test-result 0 "No output file"
367                         cp $BOXDIR/.stdout $TDIR/$TEST.out
368                         ;;
369         esac
370
371         if [ -n "$OUTPUT_FILTER" -a "$OUT_TYPE" != none -a -z "$EV_NOFILTER" ] ; then
372                 pcont "<filter> "
373                 FILTER=$(expand-var OUTPUT_FILTER)
374                 echo "Output filter command: $FILTER"
375                 mv $TDIR/$TEST.out $TDIR/$TEST.raw
376                 if ! eval $FILTER 2>$TMPDIR/exec.out ; then
377                         cat $TMPDIR/exec.out
378                         MSG=`tail -1 $TMPDIR/exec.out`
379                         if [ -z "$MSG" ] ; then MSG="Filter failed" ; fi
380                         test-result 0 "$MSG"
381                 fi
382                 cat $TMPDIR/exec.out
383         fi
384 }
385
386 # Running of test program with file input/output
387
388 function test-run-file
389 {
390         test-prolog
391         pcont "<run> "
392         BOXOPTS=$(expand-var TEST_SANDBOX_OPTS)
393         echo "Sandbox options: $BOXOPTS"
394         EXECMD=$(expand-var TEST_EXEC_CMD)
395         [ -z "$EXECMD" ] || echo "Exec command: $EXECMD" 
396         [ -z "$EXECMD" ] && EXECMD="./$PROBLEM" 
397         if ! $BOXCMD $BOXOPTS -- $EXECMD 2>$TMPDIR/exec.out ; then
398                 cat $TMPDIR/exec.out
399                 MSG=`tail -1 $TMPDIR/exec.out`
400                 test-result 0 "$MSG"
401         fi
402         cat $TMPDIR/exec.out
403         test-epilog
404 }
405
406 # Running of interactive test programs
407
408 function test-run-interactive
409 {
410         test-prolog
411         pcont "<run> "
412         BOXOPTS=$(expand-var TEST_SANDBOX_OPTS)
413         echo "Sandbox options: $BOXOPTS"
414         ICCMD=$(expand-var IA_CHECK)
415         echo "Interactive checker: $ICCMD"
416         EXECMD=$(expand-var TEST_EXEC_CMD)
417         [ -z "$EXECMD" ] || echo "Exec command: $EXECMD" 
418         [ -z "$EXECMD" ] && EXECMD="./$PROBLEM" 
419         if ! $HDIR/bin/iwrapper $BOXCMD $BOXOPTS -- $EXECMD @@ $ICCMD 2>$TMPDIR/exec.out ; then
420                 cat $TMPDIR/exec.out
421                 MSG="`head -1 $TMPDIR/exec.out`"
422                 test-result 0 "$MSG"
423         fi
424         cat $TMPDIR/exec.out
425         test-epilog
426 }
427
428 # "Running" of open-data problems
429
430 function test-run-open-data
431 {
432         [ -f $SDIR/$TEST.out ] || test-result 0 "No solution"
433         ln $SDIR/$TEST.out $TDIR/$TEST.out
434 }
435
436 # Syntax checks
437
438 function syntax-check
439 {
440         [ -n "$SYNTAX_CHECK" ] || return 0
441         [ -z "$EV_NOCHECK" ] || return 0
442         pcont "<syntax> "
443         SCHECK=$(expand-var SYNTAX_CHECK)
444         echo "Syntax check command: $SCHECK"
445         if ! eval $SCHECK 2>$TMPDIR/exec.out ; then
446                 cat $TMPDIR/exec.out
447                 MSG=`tail -1 $TMPDIR/exec.out`
448                 if [ -z "$MSG" ] ; then MSG="Wrong syntax" ; fi
449                 test-result 0 "$MSG"
450         fi
451         cat $TMPDIR/exec.out
452 }
453
454 # Output checks
455
456 function output-check
457 {
458         MSG=
459         if [ -n "$OUTPUT_CHECK" -a "$OUT_TYPE" != none -a -z "$EV_NOCHECK" ] ; then
460                 pcont "<check> "
461                 [ -f $PDIR/$TEST.out ] && ln $PDIR/$TEST.out $TDIR/$TEST.ok
462                 OCHECK=$(expand-var OUTPUT_CHECK)
463                 echo "Output check command: $OCHECK"
464                 if ! eval $OCHECK 2>$TMPDIR/exec.out ; then
465                         cat $TMPDIR/exec.out
466                         MSG=`tail -1 $TMPDIR/exec.out`
467                         if [ -z "$MSG" ] ; then MSG="Wrong answer" ; fi
468                         test-result 0 "$MSG"
469                 fi
470                 cat $TMPDIR/exec.out
471                 MSG=`tail -1 $TMPDIR/exec.out`
472         fi
473         if [ -z "$MSG" ] ; then MSG="OK" ; fi
474         test-result $POINTS_PER_TEST "$MSG"
475 }
476
477 # Setup of public commands
478
479 function public-setup
480 {
481         HDIR=$MO_ROOT
482         PDIR=$MO_ROOT/problems/$PROBLEM
483         SDIR=.
484         TDIR=~/.test
485         TMPDIR=~/.test
486         [ -d $PDIR ] || die "Unknown problem $PROBLEM"
487
488         pstart "Initializing... "
489         mkdir -p $TDIR
490         rm -rf $TDIR/*
491         BOXDIR=~/.box
492         mkdir -p $BOXDIR
493         rm -rf $BOXDIR/*
494         BOXCMD="$MO_ROOT/bin/box -c$BOXDIR"
495         exec >check-log
496         pend "OK  (see 'check-log' for details)"
497 }
498
499 # Locate output of open data problem, test case TEST
500 # Beware, SDIR and SRCN can contain spaces and other strange user-supplied characters.
501
502 function open-locate
503 {
504         [ -f $PDIR/$TEST.in ] || die "Unknown test $TEST"
505         if [ -n "$1" ] ; then
506                 SDIR=`dirname "$1"`
507                 SRCN=`basename "$1"`
508         else
509                 SRCN=$SDIR/$PROBLEM$TEST.out
510         fi
511         [ -f "$SDIR/$SRCN" ] || fatal "Output file $SRCN not found"
512 }
513
514 # Translation of runtime error codes for various compilers
515
516 function fpc-exit-code
517 {
518         case "$1" in
519                 200)    echo -n "Division by zero" ;;
520                 201)    echo -n "Range check error" ;;
521                 202)    echo -n "Stack overflow" ;;
522                 203)    echo -n "Heap overflow" ;;
523                 205)    echo -n "Floating point overflow" ;;
524                 215)    echo -n "Arithmetic overflow" ;;
525                 216)    echo -n "Segmentation fault" ;;
526         esac
527 }
528
529 # A helper function for parsing of command-line overrides of variables
530
531 function parse-cmdline
532 {
533         if [ "${1#*=}" != "$1" ] ; then
534                 local var=${1%%=*}
535                 local val=${1#*=}
536                 eval $var="'$val'"
537                 return 0
538         else
539                 return 1
540         fi
541 }