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