]> mj.ucw.cz Git - eval.git/blob - eval/libeval.sh
852bca86efbe5176451e231762e40ef5339f4df0
[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         HDIR=.
115         PDIR=problems/$PROBLEM
116         SDIR=solutions/$CONTESTANT/$PROBLEM
117         TDIR=testing/$CONTESTANT/$PROBLEM
118         TMPDIR=tmp
119         [ -d $PDIR ] || die "Problem $PROBLEM not known"
120         [ -d $SDIR ] || fatal "Solution of $PROBLEM not found"
121         mkdir -p $TDIR $TMPDIR
122         rm -rf $TDIR $TMPDIR
123         mkdir -p $TDIR $TMPDIR
124         cat >$TDIR/log <<EOF
125 Testing solution of $PROBLEM by $CONTESTANT
126 Test started at `date`
127 Contestant's solution directory: $SDIR
128 Problem directory: $PDIR
129 Testing directory: $TDIR
130 EOF
131         pend "OK"
132 }
133
134 # Locate source file.
135 # If no parameter is given, locate it in SDIR and return name as SRCN and extension as SRCEXT
136 # Or a file name can be given and then SDIR, SRCN and SRCEXT are set.
137 # Beware, SDIR and SRCN can contain spaces and other strange user-supplied characters.
138
139 function locate-source
140 {
141         pstart "Finding source... "
142         local SBASE
143         if [ -n "$1" ] ; then
144                 SDIR=`dirname "$1"`
145                 local S=`basename "$1"`
146                 SBASE=$(echo "$S" | sed 's/\.\([^.]\+\)//')
147                 SRCEXT=$(echo "$S" | sed '/\./!d; s/.*\.\([^.]\+\)/\1/')
148                 if [ -n "$SRCEXT" ] ; then
149                         # Full name given, so just check the extension and existence
150                         SRCN="$S"
151                         [ -f "$SDIR/$SRCN" ] || die "Cannot find source file $SDIR/$SRCN"
152                         SRCEXT_OK=
153                         for a in $EXTENSIONS ; do
154                                 if [ $a == $SRCEXT ] ; then
155                                         pend $SDIR/$SRCN
156                                         echo "Explicitly set source file: $SDIR/$SRCN"
157                                         return 0
158                                 fi
159                         done
160                         die "Unknown extension .$SRCEXT"
161                 fi
162         else
163                 SBASE=$PROBLEM
164         fi
165         for a in $EXTENSIONS ; do
166                 if [ -f "$SDIR/$SBASE.$a" ] ; then
167                         [ -z "$SRCN" ] || die "Multiple source files found: $SDIR/$PROBLEM.$a and $SDIR/$SRCN. Please fix."
168                         SRCN="$SBASE.$a"
169                         SRCEXT=$a
170                 fi
171         done
172         [ -n "$SRCN" ] || fatal "NOT FOUND"
173         pend $SRCN
174         echo "Found source file: $SDIR/$SRCN"
175 }
176
177 # Compilation (compile SDIR/SRCN with PDIR/COMP_EXTRAS to EXE=TDIR/PROBLEM)
178
179 function compile
180 {
181         pstart "Compiling... "
182
183         a="ALIAS_EXT_$SRCEXT"
184         if [ -n "${!a}" ] ; then
185                 SRCEXT="${!a}"
186                 echo "Normalized file extension: $SRCEXT"
187         fi
188         override-vars "EXT_$SRCEXT"
189
190         # Beware, the original SRCN can be a strange user-supplied name
191         SRC=$PROBLEM.$SRCEXT
192         cp "$SDIR/$SRCN" $TDIR/$SRC
193         if [ -n "$COMP_EXTRAS" ] ; then
194                 echo "Extras: $COMP_EXTRAS"
195                 for a in $COMP_EXTRAS ; do cp $PDIR/$a $TDIR/ ; done
196         fi
197
198         box-clean
199         for a in $SRC $COMP_EXTRAS ; do cp $TDIR/$a $BOXDIR/ ; done
200         EXE=$PROBLEM
201         CCMD=$(expand-var COMP)
202         COMP_SANDBOX_OPTS=$(expand-var COMP_SANDBOX_OPTS)
203         echo "Compiler command: $CCMD"
204         echo "Compiler sandbox options: $COMP_SANDBOX_OPTS"
205         if [ -n "$PRE_COMPILE_HOOK" ] ; then
206                 echo "Pre-compile hook: $PRE_COMPILE_HOOK"
207                 eval $PRE_COMPILE_HOOK
208         fi
209
210         echo "Compiler input files:"
211         ls -Al $BOXDIR
212         echo "Compiler output:"
213         if ! $BOXCMD $COMP_SANDBOX_OPTS -- $CCMD 2>$TDIR/compile.out ; then
214                 COMPILE_MSG="`cat $TDIR/compile.out`"
215                 pend "FAILED: $COMPILE_MSG"
216                 echo "$COMPILE_MSG"
217                 return 1
218         fi
219         cat $TDIR/compile.out
220         rm $TDIR/compile.out
221         if [ -n "$POST_COMPILE_HOOK" ] ; then
222                 echo "Post-compile hook: $POST_COMPILE_HOOK"
223                 eval $POST_COMPILE_HOOK
224         fi
225         echo "Compiler output files:"
226         ls -Al $BOXDIR
227         if [ ! -f $BOXDIR/$PROBLEM ] ; then
228                 pend "FAILED: Missing executable file"
229                 echo "Missing executable file"
230                 return 1
231         fi
232         EXE=$TDIR/$PROBLEM
233         cp -a $BOXDIR/$PROBLEM $EXE
234         echo "Compiled OK, result copied to $EXE"
235         pend "OK"
236 }
237
238 # Running of test program according to current task type (returns exit code and TEST_MSG)
239
240 function test-config
241 {
242         [ -f $PDIR/$TEST.config ] && . $PDIR/$TEST.config
243         override-vars "TEST_$TEST"
244 }
245
246 function test-run
247 {
248         test-run-$TASK_TYPE
249 }
250
251 function test-result
252 {
253         P=$1
254         M=$2
255         if [ -s $TDIR/$TEST.pts ] ; then
256                 P=`cat $TDIR/$TEST.pts`
257                 rm $TDIR/$TEST.pts
258         fi
259
260         # Translate signal numbers to readable strings
261         SG=${M#Caught fatal signal }
262         SG=${SG#Committed suicide by signal }
263         if [ "$SG" != "$M" ] ; then
264                 SG=`perl -MConfig -e '@s=split / /,$Config{sig_name}; print $s[$ARGV[0]]' $SG`
265                 [ -z "$SG" ] || M="$M (SIG$SG)"
266         fi
267
268         # Translate runtime errors to readable strings
269         RE=${M#Exited with error status }
270         if [ -n "$EXIT_CODE_HOOK" -a "$RE" != "$M" ] ; then
271                 NEWMSG=`$EXIT_CODE_HOOK $RE`
272                 if [ -n "$NEWMSG" ] ; then
273                         M="Runtime error $RE: $NEWMSG"
274                 fi
275         fi
276
277         echo "Verdict: $M"
278         echo "Points: $P"
279         test-verdict $P "$M"
280 }
281
282 function test-prolog
283 {
284         pcont "<init> "
285         box-clean
286         echo "Executable file: $TDIR/$PROBLEM"
287         if [ ! -x $TDIR/$PROBLEM ] ; then
288                 test-result 0 "Compile error"
289         fi
290         cp $TDIR/$PROBLEM $BOXDIR/
291         IN_TYPE=${IN_TYPE:-$IO_TYPE}
292         OUT_TYPE=${OUT_TYPE:-$IO_TYPE}
293         case $IN_TYPE in
294                 file)   echo "Input file: $PROBLEM.in (from $PDIR/$TEST.in)"
295                         try-ln $PDIR/$TEST.in $TDIR/$TEST.in
296                         cp $PDIR/$TEST.in $BOXDIR/$PROBLEM.in
297                         [ $TASK_TYPE == interactive ] || BOX_EXTRAS="$BOX_EXTRAS -i/dev/null"
298                         ;;
299                 stdio)  echo "Input file: <stdin> (from $PDIR/$TEST.in)"
300                         try-ln $PDIR/$TEST.in $TDIR/$TEST.in
301                         cp $PDIR/$TEST.in $BOXDIR/.stdin
302                         BOX_EXTRAS="$BOX_EXTRAS -i.stdin"
303                         ;;
304                 none)   echo "Input file: <none>"
305                         ;;
306                 *)      die "Unknown IN_TYPE $IN_TYPE"
307                         ;;
308         esac
309         if [ -n "$EV_PEDANT" -a $IN_TYPE != none ] ; then
310                 pcont "<pedant> "
311                 if [ "$EV_PEDANT" = 1 ] ; then
312                         EV_PEDANT=" "
313                 fi
314                 bin/pedant <$TDIR/$TEST.in >$TDIR/$TEST.pedant $EV_PEDANT
315                 if [ -s $TDIR/$TEST.pedant ] ; then
316                         pend
317                         sed 's/^/\t/' <$TDIR/$TEST.pedant >&2
318                         pstart -e '\t'
319                 fi
320         fi
321         case $OUT_TYPE in
322                 file)   echo "Output file: $PROBLEM.out"
323                         [ $TASK_TYPE == interactive ] || BOX_EXTRAS="$BOX_EXTRAS -o/dev/null"
324                         ;;
325                 stdio)  echo "Output file: <stdout>"
326                         BOX_EXTRAS="$BOX_EXTRAS -o.stdout"
327                         ;;
328                 none)   echo "Output file: <none>"
329                         ;;
330                 *)      die "Unknown OUT_TYPE $OUT_TYPE"
331                         ;;
332         esac
333         echo "Timeout: $TIME_LIMIT s"
334         echo "Memory: $MEM_LIMIT KB"
335         if [ -n "$PRE_RUN_HOOK" ] ; then
336                 echo "Pre-run hook: $PRE_RUN_HOOK"
337                 eval $PRE_RUN_HOOK
338         fi
339         echo "Sandbox contents before start:"
340         ls -Al $BOXDIR
341 }
342
343 function test-epilog
344 {
345         if [ -n "$POST_RUN_HOOK" ] ; then
346                 echo "Post-run hook: $POST_RUN_HOOK"
347                 eval $POST_RUN_HOOK
348         fi
349         echo "Sandbox contents after exit:"
350         ls -Al $BOXDIR
351         case ${OUT_TYPE:-$IO_TYPE} in
352                 file)   [ -f $BOXDIR/$PROBLEM.out ] || test-result 0 "No output file"
353                         cp $BOXDIR/$PROBLEM.out $TDIR/$TEST.out
354                         ;;
355                 stdio)  [ -f $BOXDIR/.stdout ] || test-result 0 "No output file"
356                         cp $BOXDIR/.stdout $TDIR/$TEST.out
357                         ;;
358         esac
359
360         if [ -n "$OUTPUT_FILTER" -a "$OUT_TYPE" != none -a -z "$EV_NOFILTER" ] ; then
361                 pcont "<filter> "
362                 FILTER=$(expand-var OUTPUT_FILTER)
363                 echo "Output filter command: $FILTER"
364                 mv $TDIR/$TEST.out $TDIR/$TEST.raw
365                 if ! eval $FILTER 2>$TMPDIR/exec.out ; then
366                         cat $TMPDIR/exec.out
367                         MSG=`tail -1 $TMPDIR/exec.out`
368                         if [ -z "$MSG" ] ; then MSG="Filter failed" ; fi
369                         test-result 0 "$MSG"
370                 fi
371                 cat $TMPDIR/exec.out
372         fi
373 }
374
375 # Running of test program with file input/output
376
377 function test-run-file
378 {
379         test-prolog
380         pcont "<run> "
381         BOXOPTS=$(expand-var TEST_SANDBOX_OPTS)
382         echo "Sandbox options: $BOXOPTS"
383         if ! $BOXCMD $BOXOPTS -- ./$PROBLEM 2>$TMPDIR/exec.out ; then
384                 cat $TMPDIR/exec.out
385                 MSG=`tail -1 $TMPDIR/exec.out`
386                 test-result 0 "$MSG"
387         fi
388         cat $TMPDIR/exec.out
389         test-epilog
390 }
391
392 # Running of interactive test programs
393
394 function test-run-interactive
395 {
396         test-prolog
397         pcont "<run> "
398         BOXOPTS=$(expand-var TEST_SANDBOX_OPTS)
399         echo "Sandbox options: $BOXOPTS"
400         ICCMD=$(expand-var IA_CHECK)
401         echo "Interactive checker: $ICCMD"
402         if ! $HDIR/bin/iwrapper $BOXCMD $BOXOPTS -- ./$PROBLEM @@ $ICCMD 2>$TMPDIR/exec.out ; then
403                 cat $TMPDIR/exec.out
404                 MSG="`head -1 $TMPDIR/exec.out`"
405                 test-result 0 "$MSG"
406         fi
407         cat $TMPDIR/exec.out
408         test-epilog
409 }
410
411 # "Running" of open-data problems
412
413 function test-run-open-data
414 {
415         [ -f $SDIR/$TEST.out ] || test-result 0 "No solution"
416         ln $SDIR/$TEST.out $TDIR/$TEST.out
417 }
418
419 # Syntax checks
420
421 function syntax-check
422 {
423         [ -n "$SYNTAX_CHECK" ] || return 0
424         [ -z "$EV_NOCHECK" ] || return 0
425         pcont "<syntax> "
426         SCHECK=$(expand-var SYNTAX_CHECK)
427         echo "Syntax check command: $SCHECK"
428         if ! eval $SCHECK 2>$TMPDIR/exec.out ; then
429                 cat $TMPDIR/exec.out
430                 MSG=`tail -1 $TMPDIR/exec.out`
431                 if [ -z "$MSG" ] ; then MSG="Wrong syntax" ; fi
432                 test-result 0 "$MSG"
433         fi
434         cat $TMPDIR/exec.out
435 }
436
437 # Output checks
438
439 function output-check
440 {
441         MSG=
442         if [ -n "$OUTPUT_CHECK" -a "$OUT_TYPE" != none -a -z "$EV_NOCHECK" ] ; then
443                 pcont "<check> "
444                 [ -f $PDIR/$TEST.out ] && ln $PDIR/$TEST.out $TDIR/$TEST.ok
445                 OCHECK=$(expand-var OUTPUT_CHECK)
446                 echo "Output check command: $OCHECK"
447                 if ! eval $OCHECK 2>$TMPDIR/exec.out ; then
448                         cat $TMPDIR/exec.out
449                         MSG=`tail -1 $TMPDIR/exec.out`
450                         if [ -z "$MSG" ] ; then MSG="Wrong answer" ; fi
451                         test-result 0 "$MSG"
452                 fi
453                 cat $TMPDIR/exec.out
454                 MSG=`tail -1 $TMPDIR/exec.out`
455         fi
456         if [ -z "$MSG" ] ; then MSG="OK" ; fi
457         test-result $POINTS_PER_TEST "$MSG"
458 }
459
460 # Setup of public commands
461
462 function public-setup
463 {
464         HDIR=$MO_ROOT
465         PDIR=$MO_ROOT/problems/$PROBLEM
466         SDIR=.
467         TDIR=~/.test
468         TMPDIR=~/.test
469         [ -d $PDIR ] || die "Unknown problem $PROBLEM"
470
471         pstart "Initializing... "
472         mkdir -p $TDIR
473         rm -rf $TDIR/*
474         BOXDIR=~/.box
475         mkdir -p $BOXDIR
476         rm -rf $BOXDIR/*
477         BOXCMD="$MO_ROOT/bin/box -c$BOXDIR"
478         exec >check-log
479         pend "OK  (see 'check-log' for details)"
480 }
481
482 # Locate output of open data problem, test case TEST
483 # Beware, SDIR and SRCN can contain spaces and other strange user-supplied characters.
484
485 function open-locate
486 {
487         [ -f $PDIR/$TEST.in ] || die "Unknown test $TEST"
488         if [ -n "$1" ] ; then
489                 SDIR=`dirname "$1"`
490                 SRCN=`basename "$1"`
491         else
492                 SRCN=$SDIR/$PROBLEM$TEST.out
493         fi
494         [ -f "$SDIR/$SRCN" ] || fatal "Output file $SRCN not found"
495 }
496
497 # Translation of runtime error codes for various compilers
498
499 function fpc-exit-code
500 {
501         case "$1" in
502                 200)    echo -n "Division by zero" ;;
503                 201)    echo -n "Range check error" ;;
504                 202)    echo -n "Stack overflow" ;;
505                 203)    echo -n "Heap overflow" ;;
506                 205)    echo -n "Floating point overflow" ;;
507                 215)    echo -n "Arithmetic overflow" ;;
508                 216)    echo -n "Segmentation fault" ;;
509         esac
510 }
511
512 # A helper function for parsing of command-line overrides of variables
513
514 function parse-cmdline
515 {
516         if [ "${1#*=}" != "$1" ] ; then
517                 local var=${1%%=*}
518                 local val=${1#*=}
519                 eval $var="'$val'"
520                 return 0
521         else
522                 return 1
523         fi
524 }