1 # The Evaluator -- Shell Function Library
2 # (c) 2001--2008 Martin Mares <mj@ucw.cz>
8 # File handles used: fd1=log, fd2=progress
33 # Report an internal error
35 [ -n "$HAVE_LOG" ] && echo "Fatal error: $@"
41 # Report a fatal error in the program being tested
43 [ -n "$HAVE_LOG" ] && echo "Fatal error: $@"
49 ln $1 $2 2>/dev/null || cp $1 $2
52 # Expand occurrences of `$var' in a given variable
59 # Given a <prefix>, override each variable <x> by <prefix>_<x>
61 function override-vars
65 # `${!${1}_@}' does not work, so we have to use eval
66 OR=($(eval echo '${!'$1'_@}'))
67 for V in "${OR[@]}" ; do
77 pstart "Preparing sandbox... "
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}
84 if [ -z "$TEST_USER" -o "$TEST_USER" == "$EVAL_USER" ] ; then
85 pcont "running locally (INSECURE), "
86 TEST_USER="$EVAL_USER"
91 pcont "used account $TEST_USER, "
93 BOXCMD=$HDIR/bin/box-$TEST_USER
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"
105 [ -n "$BOXCMD" ] || die "box-init not called"
109 # Initialization of testing directories
113 pstart "Initializing... "
114 [ -z "$HDIR" ] && HDIR=.
115 PDIR=$HDIR/problems/$PROBLEM
116 SDIR=$HDIR/solutions/$CONTESTANT/$PROBLEM
117 TDIR=$HDIR/testing/$CONTESTANT/$PROBLEM
120 [ -d $PDIR ] || die "Problem $PROBLEM not known"
121 [ -d $SDIR ] || fatal "Solution of $PROBLEM not found"
122 mkdir -p $TDIR $TMPDIR
124 mkdir -p $TDIR $TMPDIR
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
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.
141 function locate-source
143 pstart "Finding source... "
145 if [ -n "$1" ] ; then
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
153 [ -f "$SDIR/$SRCN" ] || die "Cannot find source file $SDIR/$SRCN"
155 for a in $EXTENSIONS ; do
156 if [ $a == $SRCEXT ] ; then
158 echo "Explicitly set source file: $SDIR/$SRCN"
162 die "Unknown extension .$SRCEXT"
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."
174 [ -n "$SRCN" ] || fatal "NOT FOUND"
176 echo "Found source file: $SDIR/$SRCN"
179 # Compilation (compile SDIR/SRCN with PDIR/COMP_EXTRAS to EXE=TDIR/PROBLEM)
183 pstart "Compiling... "
185 a="ALIAS_EXT_$SRCEXT"
186 if [ -n "${!a}" ] ; then
188 echo "Normalized file extension: $SRCEXT"
190 override-vars "EXT_$SRCEXT"
192 # Beware, the original SRCN can be a strange user-supplied name
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
201 for a in $SRC $COMP_EXTRAS ; do cp $TDIR/$a $BOXDIR/ ; done
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
212 echo "Compiler input files:"
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"
221 cat $TDIR/compile.out
223 if [ -n "$POST_COMPILE_HOOK" ] ; then
224 echo "Post-compile hook: $POST_COMPILE_HOOK"
225 eval $POST_COMPILE_HOOK
227 echo "Compiler output files:"
229 if [ ! -f $BOXDIR/$PROBLEM ] ; then
230 pend "FAILED: Missing executable file"
231 echo "Missing executable file"
235 cp -a $BOXDIR/$PROBLEM $EXE
236 echo "Compiled OK, result copied to $EXE"
240 # Running of test program according to current task type (returns exit code and TEST_MSG)
244 [ -f $PDIR/$TEST.config ] && . $PDIR/$TEST.config
245 override-vars "TEST_$TEST"
257 if [ -s $TDIR/$TEST.pts ] ; then
258 P=`cat $TDIR/$TEST.pts`
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)"
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"
288 echo "Executable file: $TDIR/$PROBLEM"
289 if [ ! -x $TDIR/$PROBLEM ] ; then
290 test-result 0 "Compile error"
292 cp $TDIR/$PROBLEM $BOXDIR/
293 IN_TYPE=${IN_TYPE:-$IO_TYPE}
294 OUT_TYPE=${OUT_TYPE:-$IO_TYPE}
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"
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"
306 none) echo "Input file: <none>"
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
315 BOX_EXTRAS="$BOX_EXTRAS -i.stdin"
317 *) die "Unknown IN_TYPE $IN_TYPE"
320 if [ -n "$EV_PEDANT" -a $IN_TYPE != none ] ; then
322 if [ "$EV_PEDANT" = 1 ] ; then
325 bin/pedant <$TDIR/$TEST.in >$TDIR/$TEST.pedant $EV_PEDANT
326 if [ -s $TDIR/$TEST.pedant ] ; then
328 sed 's/^/\t/' <$TDIR/$TEST.pedant >&2
333 file) echo "Output file: $PROBLEM.out"
334 [ $TASK_TYPE == interactive ] || BOX_EXTRAS="$BOX_EXTRAS -o/dev/null"
336 stdio) echo "Output file: <stdout>"
337 BOX_EXTRAS="$BOX_EXTRAS -o.stdout"
339 none) echo "Output file: <none>"
341 *) die "Unknown OUT_TYPE $OUT_TYPE"
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"
350 echo "Sandbox contents before start:"
356 if [ -n "$POST_RUN_HOOK" ] ; then
357 echo "Post-run hook: $POST_RUN_HOOK"
360 echo "Sandbox contents after exit:"
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
366 stdio) [ -f $BOXDIR/.stdout ] || test-result 0 "No output file"
367 cp $BOXDIR/.stdout $TDIR/$TEST.out
371 if [ -n "$OUTPUT_FILTER" -a "$OUT_TYPE" != none -a -z "$EV_NOFILTER" ] ; then
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
378 MSG=`tail -1 $TMPDIR/exec.out`
379 if [ -z "$MSG" ] ; then MSG="Filter failed" ; fi
386 # Running of test program with file input/output
388 function test-run-file
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
399 MSG=`tail -1 $TMPDIR/exec.out`
406 # Running of interactive test programs
408 function test-run-interactive
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
421 MSG="`head -1 $TMPDIR/exec.out`"
428 # "Running" of open-data problems
430 function test-run-open-data
432 [ -f $SDIR/$TEST.out ] || test-result 0 "No solution"
433 try-ln $SDIR/$TEST.out $TDIR/$TEST.out
438 function syntax-check
440 [ -n "$SYNTAX_CHECK" ] || return 0
441 [ -z "$EV_NOCHECK" ] || return 0
443 SCHECK=$(expand-var SYNTAX_CHECK)
444 echo "Syntax check command: $SCHECK"
445 if ! eval $SCHECK 2>$TMPDIR/exec.out ; then
447 MSG=`tail -1 $TMPDIR/exec.out`
448 if [ -z "$MSG" ] ; then MSG="Wrong syntax" ; fi
456 function output-check
459 if [ -n "$OUTPUT_CHECK" -a "$OUT_TYPE" != none -a -z "$EV_NOCHECK" ] ; then
461 [ -f $PDIR/$TEST.out ] && try-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
466 MSG=`tail -1 $TMPDIR/exec.out`
467 if [ -z "$MSG" ] ; then MSG="Wrong answer" ; fi
471 MSG=`tail -1 $TMPDIR/exec.out`
473 if [ -z "$MSG" ] ; then MSG="OK" ; fi
474 test-result $POINTS_PER_TEST "$MSG"
477 # Setup of public commands
479 function public-setup
482 PDIR=$MO_ROOT/problems/$PROBLEM
486 [ -d $PDIR ] || die "Unknown problem $PROBLEM"
488 pstart "Initializing... "
494 BOXCMD="$MO_ROOT/bin/box -c$BOXDIR"
496 pend "OK (see 'check-log' for details)"
499 # Locate output of open data problem, test case TEST
500 # Beware, SDIR and SRCN can contain spaces and other strange user-supplied characters.
504 [ -f $PDIR/$TEST.in ] || die "Unknown test $TEST"
505 if [ -n "$1" ] ; then
509 SRCN=$SDIR/$PROBLEM$TEST.out
511 [ -f "$SDIR/$SRCN" ] || fatal "Output file $SRCN not found"
514 # Translation of runtime error codes for various compilers
516 function fpc-exit-code
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" ;;
529 # A helper function for parsing of command-line overrides of variables
531 function parse-cmdline
533 if [ "${1#*=}" != "$1" ] ; then