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