OSDN Git Service

9866b3d0d3e9ec26b9e8ab4b88749f5618889024
[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  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 2 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 dashj=
33 default_standby=1
34 standby=$default_standby
35 default_watermark=0.60
36 watermark=$default_watermark
37 savecompilers=false
38 nogpg=false
39
40 usage() {
41     cat <<EOF
42 patch_tester.sh [-j<N>] [-standby N] [-watermark N] [-savecompilers] [-nogpg]
43                 <source_dir> [patches_dir [state_dir [build_dir]]]
44
45     J is the flag passed to make.  Default is empty string.
46
47     STANDBY is the number of minutes between checks for new patches in
48     PATCHES_DIR.  Default is ${default_standby} minutes.
49
50     WATERMARK is the 5 minute average system charge under which a new
51     compile can start.  Default is ${default_watermark}.  Note that the comparison 
52     is done in lexicographical order, so don't forget the leading 0.
53
54     SAVECOMPILERS copies the compilers in the same directory as the
55     test results for the non patched version.  Default is not copy.
56
57     NOGPG can be used to avoid checking the GPG signature of patches.
58
59     SOURCE_DIR is the directory containing GCC's toplevel configure.
60
61     PATCHES_DIR is the directory containing the patches to be tested.
62     Default is SOURCE_DIR/patches.
63
64     STATE_DIR is where the tester maintains its internal state.
65     Default is SOURCE_DIR/state.
66
67     BUILD_DIR is the build tree, a temporary directory that this
68     script will delete and recreate.  Default is SOURCE_DIR/obj.
69
70 EOF
71     exit 1
72 }
73
74 while [ $# -ne 0 ]; do
75     case $1 in
76         -j*)
77             dashj=$1; shift
78             ;;
79         -standby)
80             [[ $# > 2 ]] || usage
81             standby=$2; shift; shift
82             ;;
83         -watermark)
84             [[ $# > 2 ]] || usage
85             watermark=$2; shift; shift
86             ;;
87         -savecompilers)
88             savecompilers=true; shift
89             ;;
90         -nogpg)
91             nogpg=true; shift
92             ;;
93         -*) 
94             echo "Invalid option: $1"
95             usage
96             ;;
97         *)
98             break
99             ;;
100     esac
101 done
102
103 test $# -eq 0 && usage
104
105 SOURCE=$1
106 PATCHES=
107 STATE=
108 BUILD=
109
110 if [[ $# < 2 ]]; then
111     PATCHES=$SOURCE/patches
112 else
113     PATCHES=$2
114 fi
115 if [[ $# < 3 ]]; then
116     STATE=$SOURCE/state
117 else
118     STATE=$3
119 fi
120 if [[ $# < 4 ]]; then
121     BUILD=$SOURCE/obj
122 else
123     BUILD=$4
124 fi
125
126 [ -d $PATCHES ] || mkdir -p $PATCHES
127 [ -d $STATE ] || mkdir -p $STATE
128 [ -d $STATE/patched ] || mkdir -p $STATE/patched
129 [ -d $SOURCE ] || mkdir -p $SOURCE
130 [ -f $SOURCE/config.guess ] || {
131     cd $SOURCE
132     svn -q co svn://gcc.gnu.org/svn/gcc/trunk .
133 }
134
135 VERSION=`svn info $SOURCE | grep "^Revision:" | sed -e "s/^Revision://g" -e "s/ //g"`
136
137 exec >> $STATE/tester.log 2>&1 || exit 1
138 set -x
139
140 TESTING=$STATE/testing
141 REPORT=$TESTING/report
142 PRISTINE=$TESTING/pristine
143 PATCHED=$TESTING/patched
144 PATCH=
145 TARGET=`$SOURCE/config.guess || exit 1` 
146 TESTLOGS="gcc/testsuite/gcc/gcc.sum
147 gcc/testsuite/gfortran/gfortran.sum
148 gcc/testsuite/g++/g++.sum
149 gcc/testsuite/objc/objc.sum
150 $TARGET/libstdc++-v3/testsuite/libstdc++.sum
151 $TARGET/libffi/testsuite/libffi.sum
152 $TARGET/libjava/testsuite/libjava.sum
153 $TARGET/libgomp/testsuite/libgomp.sum
154 $TARGET/libmudflap/testsuite/libmudflap.sum"
155 COMPILERS="gcc/cc1
156 gcc/cc1obj
157 gcc/cc1plus
158 gcc/f951
159 gcc/jc1
160 gcc/gnat1
161 gcc/tree1"
162
163 now () {
164     echo `TZ=UTC date +"%Y_%m_%d_%H_%M_%S"`
165 }
166
167 report () {
168     echo "Checker: (`now`): $@" >> $REPORT
169 }
170
171 freport () {
172     if [ -s $1 ]; then
173         report "(cat $1"
174         cat $1 >> $REPORT
175         report "tac)"
176     fi
177 }
178
179 cleanup () {
180     cd $SOURCE
181
182     # FORNOW: Until this script is not committed to trunk, save and restore it.
183     mv $SOURCE/contrib/patch_tester.sh $STATE
184     svn cleanup && svn revert -R . && svn st | cut -d' ' -f5- | xargs rm -v
185     mv $STATE/patch_tester.sh $SOURCE/contrib/
186 }
187
188 selfexec () {
189     exec ${CONFIG_SHELL-/bin/sh} $SOURCE/contrib/patch_tester.sh $args
190 }
191
192 update () {
193     svn_branch=`grep "^branch:" $PATCH | sed -e "s/^branch://g" -e "s/ //g"`
194     if [ x$svn_branch = x ]; then
195         svn_branch=trunk
196     fi
197
198     svn_revision=`grep "^revision:" $PATCH | sed -e "s/^revision://g" -e "s/ //g"`
199     if [ x$svn_revision = x ]; then
200         svn_revision=HEAD
201     fi
202
203     cleanup
204     cd $SOURCE
205     case $svn_branch in
206         trunk)
207             if ! svn switch -r $svn_revision svn://gcc.gnu.org/svn/gcc/trunk &> $TESTING/svn ; then
208                 report "failed to update svn sources with"
209                 report "svn switch -r $svn_revision svn://gcc.gnu.org/svn/gcc/trunk"
210                 freport $TESTING/svn
211                 return 1
212             fi
213             ;;
214
215         svn://gcc.gnu.org/svn/gcc/*)
216             if ! svn switch -r $svn_revision $svn_branch &> $TESTING/svn ; then
217                 report "failed to update svn sources with"
218                 report "svn switch -r $svn_revision $svn_branch"
219                 freport $TESTING/svn
220                 return 1
221             fi
222             ;;
223
224         *)
225             if ! svn switch -r $svn_revision svn://gcc.gnu.org/svn/gcc/branches/$svn_branch &> $TESTING/svn ; then
226                 report "failed to update svn sources with"
227                 report "svn switch -r $svn_revision svn://gcc.gnu.org/svn/gcc/branches/$svn_branch"
228                 freport $TESTING/svn
229                 return 1
230             fi
231             ;;
232     esac
233
234     current_version=`svn info $SOURCE | grep "^Revision:" | sed -e "s/^Revision://g" -e "s/ //g"`
235     if [[ $VERSION < $current_version ]]; then
236         if [ -f $SOURCE/contrib/patch_tester.sh ]; then
237             selfexec
238         fi
239     fi
240
241     return 0
242 }
243
244 apply_patch () {
245     if [ $nogpg = false ]; then
246         if ! gpg --batch --verify $PATCH &> $TESTING/gpgverify ; then
247             report "your patch failed to verify:"
248             freport $TESTING/gpgverify
249             return 1
250         fi
251     fi
252
253     # Detect if the patch was created in toplev GCC.
254     grep "^Index: " $PATCH | grep "gcc/"
255     if [ $? = 0 ]; then
256         cd $SOURCE
257         if ! patch -p0 < $PATCH &> $TESTING/patching ; then
258             report "your patch failed to apply:"
259             freport $TESTING/patching
260             return 1
261         fi
262     else
263         cd $SOURCE/gcc
264         if ! patch -p0 < $PATCH &> $TESTING/patching ; then
265             report "your patch failed to apply:"
266             freport $TESTING/patching
267             return 1
268         fi
269     fi
270 }
271
272 save_compilers () {
273     for COMPILER in $COMPILERS ; do
274         if [ -f $BUILD/$COMPILER ]; then
275             cp $BUILD/$COMPILER $PRISTINE
276         fi
277     done
278 }
279
280 bootntest () {
281     rm -rf $BUILD
282     mkdir $BUILD
283     cd $BUILD
284
285     CONFIG_OPTIONS=`grep "^configure:" $PATCH | sed -e "s/^configure://g"`
286     if ! $SOURCE/configure $CONFIG_OPTIONS &> $1/configure ; then
287         report "configure failed with:"
288         freport $1/configure
289         return 1
290     fi
291
292     if ! make $dashj `grep "^make:" $PATCH | sed -e "s/^make://g"` bootstrap &> $1/bootstrap ; then
293         report "bootstrap failed with last lines:"
294         tail -30 $1/bootstrap > $1/last_bootstrap
295         freport $1/last_bootstrap
296         report "grep --context=20 Error bootstrap:"
297         grep --context=20 Error $1/bootstrap > $1/bootstrap_error
298         freport $1/bootstrap_error
299         return 1
300     fi
301
302     CHECK_OPTIONS=`grep "^check:" $PATCH | sed -e "s/^check://g"`
303     make $dashj $CHECK_OPTIONS -k check &> $1/check
304
305     for LOG in $TESTLOGS ; do
306         if [ -f $BUILD/$LOG ]; then
307             mv $BUILD/$LOG $1
308             mv `echo "$BUILD/$LOG" | sed -e "s/\.sum/\.log/g"` $1
309         fi
310     done
311
312     return 0
313 }
314
315 bootntest_patched () {
316     cleanup
317     mkdir -p $PATCHED
318     apply_patch && bootntest $PATCHED
319     return $?
320 }
321
322 # Build the pristine tree with exactly the same options as the patch under test.
323 bootntest_pristine () {
324     cleanup
325     current_branch=`svn info $SOURCE | grep "^URL:" | sed -e "s/URL: //g" -e "s/svn:\/\/gcc.gnu.org\/svn\/gcc\///g"`
326     current_version=`svn info $SOURCE | grep "^Revision:" | sed -e "s/^Revision://g" -e "s/ //g"`
327     PRISTINE=$STATE/$current_branch/$current_version
328
329     if [ -d $PRISTINE ]; then
330         ln -s $PRISTINE $TESTING/pristine
331         return 0
332     else
333         mkdir -p $PRISTINE
334         ln -s $PRISTINE $TESTING/pristine
335         bootntest $PRISTINE
336         RETVAL=$?
337         if [ $RETVAL = 0 -a $savecompilers = true ]; then
338             save_compilers
339         fi
340         return $RETVAL
341     fi
342 }
343
344 regtest () {
345     touch $1/report
346     touch $1/passes
347     touch $1/failed
348     touch $1/regress
349
350     for LOG in $TESTLOGS ; do
351         NLOG=`basename $LOG`
352         if [ -f $1/$NLOG ]; then
353             awk '/^FAIL: / { print "'$NLOG'",$2; }' $1/$NLOG
354         fi
355     done | sort | uniq > $1/failed
356
357     comm -12 $1/failed $1/passes >> $1/regress
358     NUMREGRESS=`wc -l < $1/regress | tr -d ' '`
359
360     if [ $NUMREGRESS -eq 0 ] ; then
361         for LOG in $TESTLOGS ; do
362             NLOG=`basename $LOG`
363             if [ -f $1/$NLOG ] ; then
364                 awk '/^PASS: / { print "'$NLOG'",$2; }' $1/$NLOG
365             fi
366         done | sort | uniq | comm -23 - $1/failed > $1/passes
367         echo "there are no regressions with your patch." >> $1/report
368     else
369         echo "with your patch there are $NUMREGRESS regressions." >> $1/report
370         echo "list of regressions with your patch:" >> $1/report
371         cat $1/regress >> $1/report
372     fi
373 }
374
375 contrib_compare_tests () {
376     report "comparing logs with contrib/compare_tests:"
377     for LOG in $TESTLOGS ; do
378         NLOG=`basename $LOG`
379         if [ -f $PRISTINE/$NLOG -a -f $PATCHED/$NLOG ]; then
380             $SOURCE/contrib/compare_tests $PRISTINE/$NLOG $PATCHED/$NLOG > $TESTING/compare_$NLOG
381             freport $TESTING/compare_$NLOG
382         fi
383     done
384 }
385
386 compare_passes () {
387     regtest $PRISTINE
388     cp $PRISTINE/passes $PATCHED
389     regtest $PATCHED
390     freport $PATCHED/report
391     report "FAILs with patched version:"
392     freport $PATCHED/failed
393     report "FAILs with pristine version:"
394     freport $PRISTINE/failed
395
396     # contrib_compare_tests
397 }
398
399 write_report () {
400     backup_patched=$STATE/patched/`now`
401     report "The files used for the validation of your patch are stored in $backup_patched on the tester machine."
402
403     EMAIL=`grep "^email:" $PATCH | sed -e "s/^email://g" -e "s/ //g"`
404     if [ x$EMAIL != x ]; then
405         mutt -s "[regtest] Results for `basename $PATCH` on $TARGET" -i $REPORT -a $PATCH $EMAIL
406     fi
407
408     mv $TESTING $backup_patched
409 }
410
411 announce () {
412     EMAIL=`grep "^email:" $PATCH | sed -e "s/^email://g" -e "s/ //g"`
413     if [ x$EMAIL != x ]; then
414
415         START_REPORT=$TESTING/start_report
416         echo "Hi, " >> $START_REPORT
417         echo "I'm the automatic tester running on $TARGET." >> $START_REPORT
418         echo "I just started to look at your patch `basename $PATCH`." >> $START_REPORT
419         echo "Bye, your automatic tester." >> $START_REPORT
420         mutt -s "[regtest] Starting bootstrap for `basename $PATCH` on $TARGET" -i $START_REPORT $EMAIL
421     fi
422 }
423
424 # After selfexec, $TESTING is already set up.  
425 if [ -d $TESTING ]; then
426     # The only file in $TESTING is the patch.
427     PATCH=`ls -rt -1 $TESTING | head -1`
428     PATCH=$TESTING/$PATCH
429     if [ -f $PATCH ]; then
430         bootntest_patched && bootntest_pristine && compare_passes
431         write_report
432     fi
433 fi
434
435 while true; do
436     PATCH=`ls -rt -1 $PATCHES | head -1`
437     if [ x$PATCH = x ]; then
438         sleep ${standby}m
439     else
440         sysload=`uptime | cut -d, -f 5`
441         if [[ $sysload > $watermark ]]; then
442             # Wait a bit when system load is too high.
443             sleep ${standby}m
444         else
445             mkdir -p $TESTING
446             mv $PATCHES/$PATCH $TESTING/
447             PATCH=$TESTING/$PATCH
448
449             announce
450             update && bootntest_patched && bootntest_pristine && compare_passes
451             write_report
452         fi
453     fi
454 done