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