OSDN Git Service

83f3322ee7d42321a134f678d0db6ee9d6a9d594
[pf3gnuchains/gcc-fork.git] / contrib / patch_tester.sh
1 #!/bin/sh
2
3 # Tests a set of patches from a directory.
4 # Copyright (C) 2007, 2008  Free Software Foundation, Inc.
5 # Contributed by Sebastian Pop <sebastian.pop@amd.com>
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
21 cat <<EOF
22
23 WARNING: This script should only be fed with patches from known
24          authorized and trusted sources.  Don't even think about
25          hooking it up to a raw feed from the gcc-patches list or
26          you'll regret it.
27
28 EOF
29
30 args=$@
31
32 svnpath=svn://gcc.gnu.org/svn/gcc
33 dashj=
34 default_standby=1
35 standby=$default_standby
36 default_watermark=0.60
37 watermark=$default_watermark
38 savecompilers=false
39 nogpg=false
40 stop=false
41
42 usage() {
43     cat <<EOF
44 patch_tester.sh [-j<N>] [-standby N] [-watermark N] [-savecompilers] [-nogpg]
45                 [-svnpath URL] [-stop]
46                 <source_dir> [patches_dir [state_dir [build_dir]]]
47
48     J is the flag passed to make.  Default is empty string.
49
50     STANDBY is the number of minutes between checks for new patches in
51     PATCHES_DIR.  Default is ${default_standby} minutes.
52
53     WATERMARK is the 5 minute average system charge under which a new
54     compile can start.  Default is ${default_watermark}.
55
56     SAVECOMPILERS copies the compilers in the same directory as the
57     test results for the non patched version.  Default is not copy.
58
59     NOGPG can be used to avoid checking the GPG signature of patches.
60
61     URL is the location of the GCC SVN repository.  The default is
62     ${svnpath}.
63
64     STOP exits when PATCHES_DIR is empty.
65
66     SOURCE_DIR is the directory containing GCC's toplevel configure.
67
68     PATCHES_DIR is the directory containing the patches to be tested.
69     Default is SOURCE_DIR/patches.
70
71     STATE_DIR is where the tester maintains its internal state.
72     Default is SOURCE_DIR/state.
73
74     BUILD_DIR is the build tree, a temporary directory that this
75     script will delete and recreate.  Default is SOURCE_DIR/obj.
76
77 EOF
78     exit 1
79 }
80
81 makedir () {
82     DIRNAME=$1
83     mkdir -p $DIRNAME
84     if [ $? -ne 0 ]; then
85         echo "ERROR: could not make directory $DIRNAME"
86         exit 1
87     fi
88 }
89
90 while [ $# -ne 0 ]; do
91     case $1 in
92         -j*)
93             dashj=$1; shift
94             ;;
95         -standby)
96             [[ $# > 2 ]] || usage
97             standby=$2; shift; shift
98             ;;
99         -watermark)
100             [[ $# > 2 ]] || usage
101             watermark=$2; shift; shift
102             ;;
103         -savecompilers)
104             savecompilers=true; shift
105             ;;
106         -nogpg)
107             nogpg=true; shift
108             ;;
109         -stop)
110             stop=true; shift
111             ;;
112         -svnpath)
113             svnpath=$2; shift; shift
114             ;;
115         -*) 
116             echo "Invalid option: $1"
117             usage
118             ;;
119         *)
120             break
121             ;;
122     esac
123 done
124
125 test $# -eq 0 && usage
126
127 SOURCE=$1
128 PATCHES=
129 STATE=
130 BUILD=
131
132 if [[ $# < 2 ]]; then
133     PATCHES=$SOURCE/patches
134 else
135     PATCHES=$2
136 fi
137 if [[ $# < 3 ]]; then
138     STATE=$SOURCE/state
139 else
140     STATE=$3
141 fi
142 if [[ $# < 4 ]]; then
143     BUILD=$SOURCE/obj
144 else
145     BUILD=$4
146 fi
147
148 [ -d $PATCHES ] || makedir $PATCHES
149 [ -d $STATE ] || makedir $STATE
150 [ -d $STATE/patched ] || makedir $STATE/patched
151 [ -d $SOURCE ] || makedir $SOURCE
152 [ -f $SOURCE/config.guess ] || {
153     cd $SOURCE
154     svn -q co $svnpath/trunk .
155     if [ $? -ne 0 ]; then
156         echo "ERROR: initial svn checkout failed"
157         exit 1
158     fi
159 }
160
161 # This can contain required local settings:
162 #  default_config  configure options, always passed
163 #  default_make    make bootstrap options, always passed
164 #  default_check   make check options, always passed
165 [ -f $STATE/defaults ] && . $STATE/defaults
166
167 VERSION=`svn info $SOURCE | grep "^Revision:" | sed -e "s/^Revision://g" -e "s/ //g"`
168
169 exec >> $STATE/tester.log 2>&1 || exit 1
170 set -x
171
172 TESTING=$STATE/testing
173 REPORT=$TESTING/report
174 PRISTINE=$TESTING/pristine
175 PATCHED=$TESTING/patched
176 PATCH=
177 TARGET=`$SOURCE/config.guess || exit 1` 
178 TESTLOGS="gcc/testsuite/gcc/gcc.sum
179 gcc/testsuite/gfortran/gfortran.sum
180 gcc/testsuite/g++/g++.sum
181 gcc/testsuite/objc/objc.sum
182 $TARGET/libstdc++-v3/testsuite/libstdc++.sum
183 $TARGET/libffi/testsuite/libffi.sum
184 $TARGET/libjava/testsuite/libjava.sum
185 $TARGET/libgomp/testsuite/libgomp.sum
186 $TARGET/libmudflap/testsuite/libmudflap.sum"
187 COMPILERS="gcc/cc1
188 gcc/cc1obj
189 gcc/cc1plus
190 gcc/f951
191 gcc/jc1
192 gcc/gnat1
193 gcc/tree1"
194
195 now () {
196     echo `TZ=UTC date +"%Y_%m_%d_%H_%M_%S"`
197 }
198
199 report () {
200     echo "$@" >> $REPORT
201 }
202
203 freport () {
204     if [ -s $1 ]; then
205         report "(cat $1"
206         cat $1 >> $REPORT
207         report "tac)"
208     fi
209 }
210
211 cleanup () {
212     cd $SOURCE
213     svn cleanup && svn revert -R . && svn st | cut -d' ' -f5- | xargs rm -v
214 }
215
216 selfexec () {
217     exec ${CONFIG_SHELL-/bin/sh} $0 $args
218 }
219
220 update () {
221     svn_branch=`grep "^branch:" $PATCH | sed -e "s/^branch://g" -e "s/ //g"`
222     if [ x$svn_branch = x ]; then
223         svn_branch=trunk
224     fi
225
226     svn_revision=`grep "^revision:" $PATCH | sed -e "s/^revision://g" -e "s/ //g"`
227     if [ x$svn_revision = x ]; then
228         svn_revision=HEAD
229     fi
230
231     cleanup
232     cd $SOURCE
233     case $svn_branch in
234         trunk)
235             if ! svn switch -r $svn_revision $svnpath/trunk &> $TESTING/svn ; then
236                 report "failed to update svn sources with"
237                 report "svn switch -r $svn_revision $svnpath/trunk"
238                 freport $TESTING/svn
239                 return 1
240             fi
241             ;;
242
243         ${svnpath}*)
244             if ! svn switch -r $svn_revision $svn_branch &> $TESTING/svn ; then
245                 report "failed to update svn sources with"
246                 report "svn switch -r $svn_revision $svn_branch"
247                 freport $TESTING/svn
248                 return 1
249             fi
250             ;;
251
252         *)
253             if ! svn switch -r $svn_revision $svnpath/branches/$svn_branch &> $TESTING/svn ; then
254                 report "failed to update svn sources with"
255                 report "svn switch -r $svn_revision $svnpath/branches/$svn_branch"
256                 freport $TESTING/svn
257                 return 1
258             fi
259             ;;
260     esac
261     contrib/gcc_update --touch
262
263     current_version=`svn info $SOURCE | grep "^Revision:" | sed -e "s/^Revision://g" -e "s/ //g"`
264     if [[ $VERSION < $current_version ]]; then
265         if [ -f $SOURCE/contrib/patch_tester.sh ]; then
266             selfexec
267         fi
268     fi
269
270     return 0
271 }
272
273 apply_patch () {
274     if [ $nogpg = false ]; then
275         if ! gpg --batch --verify $PATCH &> $TESTING/gpgverify ; then
276             report "your patch failed to verify:"
277             freport $TESTING/gpgverify
278             return 1
279         fi
280     fi
281
282     cd $SOURCE
283     if ! patch -p0 < $PATCH &> $TESTING/patching ; then
284         report "your patch failed to apply:"
285         report "(check that the patch was created at the top level)"
286         freport $TESTING/patching
287         return 1
288     fi
289
290     # Just assume indexes for now -- not really great, but svn always
291     # makes them.
292     grep "^Index: " $PATCH | sed -e 's/Index: //' | while read file; do
293         # If the patch resulted in an empty file, delete it.
294         # This is how svn reports deletions.
295         if [ ! -s $file ]; then
296             rm -f $file
297             report "Deleting empty file $file"
298         fi
299     done
300 }
301
302 save_compilers () {
303     for COMPILER in $COMPILERS ; do
304         if [ -f $BUILD/$COMPILER ]; then
305             cp $BUILD/$COMPILER $PRISTINE
306         fi
307     done
308 }
309
310 bootntest () {
311     rm -rf $BUILD
312     mkdir $BUILD
313     cd $BUILD
314
315     CONFIG_OPTIONS=`grep "^configure:" $PATCH | sed -e "s/^configure://g"`
316     CONFIG_OPTIONS="$default_config $CONFIG_OPTIONS"
317     if ! eval $SOURCE/configure $CONFIG_OPTIONS &> $1/configure ; then
318         report "configure with `basename $1` version failed with:"
319         freport $1/configure
320         return 1
321     fi
322
323     MAKE_ARGS=`grep "^make:" $PATCH | sed -e "s/^make://g"`
324     MAKE_ARGS="$default_make $MAKE_ARGS"
325     if ! eval make $dashj $MAKE_ARGS &> $1/bootstrap ; then
326         report "bootstrap with `basename $1` version failed with last lines:"
327         tail -30 $1/bootstrap > $1/last_bootstrap
328         freport $1/last_bootstrap
329         report "grep --context=20 Error bootstrap:"
330         grep --context=20 Error $1/bootstrap > $1/bootstrap_error
331         freport $1/bootstrap_error
332         return 1
333     fi
334
335     CHECK_OPTIONS=`grep "^check:" $PATCH | sed -e "s/^check://g"`
336     CHECK_OPTIONS="$default_check $CHECK_OPTIONS"
337     eval make $dashj $CHECK_OPTIONS -k check &> $1/check
338
339     SUITESRUN="`grep 'Summary ===' $1/check | cut -d' ' -f 2 | sort`"
340     if [ x$SUITESRUN = x ]; then
341         report "check with `basename $1` version failed, no testsuites were run"
342         return 1
343     fi
344
345     for LOG in $TESTLOGS ; do
346         if [ -f $BUILD/$LOG ]; then
347             mv $BUILD/$LOG $1
348             mv `echo "$BUILD/$LOG" | sed -e "s/\.sum/\.log/g"` $1
349         fi
350     done
351
352     return 0
353 }
354
355 bootntest_patched () {
356     cleanup
357     mkdir -p $PATCHED
358     apply_patch && bootntest $PATCHED
359     return $?
360 }
361
362 # Build the pristine tree with exactly the same options as the patch under test.
363 bootntest_pristine () {
364     cleanup
365     current_branch=`svn info $SOURCE | grep "^URL:" | sed -e "s/URL: //g" -e "s,${svnpath},,g"`
366     current_version=`svn info $SOURCE | grep "^Revision:" | sed -e "s/^Revision://g" -e "s/ //g"`
367     PRISTINE=$STATE/$current_branch/$current_version
368
369     if [ -d $PRISTINE ]; then
370         ln -s $PRISTINE $TESTING/pristine
371         return 0
372     else
373         mkdir -p $PRISTINE
374         ln -s $PRISTINE $TESTING/pristine
375         bootntest $PRISTINE
376         RETVAL=$?
377         if [ $RETVAL = 0 -a $savecompilers = true ]; then
378             save_compilers
379         fi
380         return $RETVAL
381     fi
382 }
383
384 regtest () {
385     touch $1/report
386     touch $1/passes
387     touch $1/failed
388     touch $1/regress
389
390     for LOG in $TESTLOGS ; do
391         NLOG=`basename $LOG`
392         if [ -f $1/$NLOG ]; then
393             awk '/^FAIL: / { print "'$NLOG'",$2; }' $1/$NLOG
394         fi
395     done | sort | uniq > $1/failed
396
397     comm -12 $1/failed $1/passes >> $1/regress
398     NUMREGRESS=`wc -l < $1/regress | tr -d ' '`
399
400     if [ $NUMREGRESS -eq 0 ] ; then
401         for LOG in $TESTLOGS ; do
402             NLOG=`basename $LOG`
403             if [ -f $1/$NLOG ] ; then
404                 awk '/^PASS: / { print "'$NLOG'",$2; }' $1/$NLOG
405             fi
406         done | sort | uniq | comm -23 - $1/failed > $1/passes
407         echo "there are no regressions with your patch." >> $1/report
408     else
409         echo "with your patch there are $NUMREGRESS regressions." >> $1/report
410         echo "list of regressions with your patch:" >> $1/report
411         cat $1/regress >> $1/report
412     fi
413 }
414
415 contrib_compare_tests () {
416     report "comparing logs with contrib/compare_tests:"
417     for LOG in $TESTLOGS ; do
418         NLOG=`basename $LOG`
419         if [ -f $PRISTINE/$NLOG -a -f $PATCHED/$NLOG ]; then
420             $SOURCE/contrib/compare_tests $PRISTINE/$NLOG $PATCHED/$NLOG > $TESTING/compare_$NLOG
421             freport $TESTING/compare_$NLOG
422         fi
423     done
424 }
425
426 compare_passes () {
427     regtest $PRISTINE
428     cp $PRISTINE/passes $PATCHED
429     regtest $PATCHED
430     freport $PATCHED/report
431     report "FAILs with patched version:"
432     freport $PATCHED/failed
433     report "FAILs with pristine version:"
434     freport $PRISTINE/failed
435
436     # contrib_compare_tests
437 }
438
439 write_report () {
440     backup_patched=$STATE/patched/`now`
441     report "The files used for the validation of your patch are stored in $backup_patched on the tester machine."
442
443     EMAIL=`grep "^email:" $PATCH | sed -e "s/^email://g" -e "s/ //g"`
444     if [ x$EMAIL != x ]; then
445         mutt -s "[regtest] Results for `basename $PATCH` on $TARGET" -i $REPORT -a $PATCH $EMAIL
446     fi
447
448     mv $TESTING $backup_patched
449 }
450
451 announce () {
452     EMAIL=`grep "^email:" $PATCH | sed -e "s/^email://g" -e "s/ //g"`
453     if [ x$EMAIL != x ]; then
454
455         START_REPORT=$TESTING/start_report
456         echo "Hi, " >> $START_REPORT
457         echo "I'm the automatic tester running on $TARGET." >> $START_REPORT
458         echo "I just started to look at your patch `basename $PATCH`." >> $START_REPORT
459         echo "Bye, your automatic tester." >> $START_REPORT
460         mutt -s "[regtest] Starting bootstrap for `basename $PATCH` on $TARGET" -i $START_REPORT $EMAIL
461     fi
462 }
463
464 # After selfexec, $TESTING is already set up.  
465 if [ -d $TESTING ]; then
466     # The only file in $TESTING is the patch.
467     PATCH=`ls -rt -1 $TESTING | head -1`
468     PATCH=$TESTING/$PATCH
469     if [ -f $PATCH ]; then
470         bootntest_patched && bootntest_pristine && compare_passes
471         write_report
472     fi
473 fi
474
475 firstpatch=true
476 while true; do
477     PATCH=`ls -rt -1 $PATCHES | head -1`
478     if [ x$PATCH = x ]; then
479         if [ $stop = true ]; then
480             if [ $firstpatch = true ]; then
481                 echo "No patches ready to test, quitting."
482                 exit 1
483             else
484                 echo "No more patches to test."
485                 exit 0
486             fi
487         fi
488         sleep ${standby}m
489     else
490         firstpatch=false
491         sysload=`uptime | cut -d, -f 5`
492         if [[ $sysload > $watermark ]]; then
493             # Wait a bit when system load is too high.
494             sleep ${standby}m
495         else
496             mkdir -p $TESTING
497             mv $PATCHES/$PATCH $TESTING/
498             PATCH=$TESTING/$PATCH
499
500             announce
501             update && bootntest_patched && bootntest_pristine && compare_passes
502             write_report
503         fi
504     fi
505 done