OSDN Git Service

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