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