浏览代码

gcc: Add test suite framework to check for compilation warnings, errors, etc.

* configure.ac: Check for Guile.  Output `gcc-plugin/tests/run-test' and
  make it executable.

* gcc-plugin/tests/Makefile.am (gcc_tests): New variable.
  (check_PROGRAMS): Remove.
  (CLEANFILES): Add executables.
  (TESTS_ENVIRONMENT)[HAVE_GUILE]: New variable.
  (check-hook)[!HAVE_GUILE]: New target.

* gcc-plugin/tests/run-test.in: New file.

* gcc-plugin/tests/pointer-tasks.c, gcc-plugin/tests/scalar-tasks.c: Add
  `instructions' directive.

* gcc-plugin/tests/register.c (main): Add `note' directive.
Ludovic Courtès 14 年之前
父节点
当前提交
586c428011

+ 1 - 0
.gitignore

@@ -178,3 +178,4 @@ starpu.log
 /gcc-plugin/tests/register
 /tests/datawizard/acquire_cb_insert
 /tools/starpu_perfmodel_plot
+/gcc-plugin/tests/run-test

+ 18 - 2
configure.ac

@@ -1088,11 +1088,21 @@ if test "x$enable_gcc_plugin" = "xyes"; then
    fi
 
    build_gcc_plugin="yes"
+
+   # GNU Guile 1.8/2.0 is used to run the test suite.
+   AC_PATH_PROG([GUILE], [guile])
+   if test "x$GUILE" != "x"; then
+      run_gcc_plugin_test_suite="yes"
+   else
+      run_gcc_plugin_test_suite="no"
+   fi
 else
    build_gcc_plugin="no"
+   run_gcc_plugin_test_suite="no"
 fi
 
 AM_CONDITIONAL([BUILD_GCC_PLUGIN], [test "x$build_gcc_plugin" = "xyes"])
+AM_CONDITIONAL([HAVE_GUILE], [test "x$GUILE" != "x"])
 
 ###############################################################################
 #                                                                             #
@@ -1314,7 +1324,11 @@ AC_MSG_RESULT($want_optional_tests)
 AM_CONDITIONAL([COND_OPT], [test "$want_optional_tests" = yes])
 
 # File configuration
-AC_CONFIG_COMMANDS([executable-scripts], [chmod +x ]tests/regression/regression.sh)
+AC_CONFIG_COMMANDS([executable-scripts], [
+  chmod +x tests/regression/regression.sh
+  chmod +x gcc-plugin/tests/run-test
+])
+
 AC_CONFIG_FILES(tests/regression/regression.sh tests/regression/profiles tests/regression/profiles.build.only)
 AC_CONFIG_HEADER(src/common/config.h include/starpu_config.h)
 
@@ -1327,6 +1341,7 @@ AC_OUTPUT([
 	gcc-plugin/Makefile
 	gcc-plugin/src/Makefile
 	gcc-plugin/tests/Makefile
+	gcc-plugin/tests/run-test
 	libstarpu.pc
 	examples/Makefile
         examples/opt/Makefile
@@ -1346,7 +1361,8 @@ AC_MSG_NOTICE([
 	OpenCL enabled: $enable_opencl
 	Cell   enabled: $enable_gordon
 
-	GCC plugin: $build_gcc_plugin
+	GCC plug-in: $build_gcc_plugin
+	GCC plug-in test suite: $run_gcc_plugin_test_suite
 
 	Compile-time limits
 	(change these with --enable-maxcpus, --enable-maxcudadev,

+ 2 - 0
gcc-plugin/README

@@ -6,3 +6,5 @@ language extensions to make it easier to define and invoke StarPU
 tasks.
 
 Plug-ins are supported starting from GCC 4.5.
+
+To run the test suite, GNU Guile 1.8.x or 2.0.x is needed.

+ 20 - 12
gcc-plugin/tests/Makefile.am

@@ -14,26 +14,34 @@
 # See the GNU Lesser General Public License in COPYING.LGPL for more details.
 
 
-TESTS = $(check_PROGRAMS)
+gcc_tests =					\
+  base.c					\
+  pointers.c					\
+  register.c					\
+  scalar-tasks.c				\
+  pointer-tasks.c
 
-check_PROGRAMS =				\
+dist_noinst_HEADERS = lib.h
+
+CLEANFILES = *.gimple				\
   base						\
   pointers					\
   register					\
   scalar-tasks					\
   pointer-tasks
 
-scalar_tasks_LDADD = $(top_builddir)/src/libstarpu.la
-pointer_tasks_LDADD = $(top_builddir)/src/libstarpu.la
+EXTRA_DIST = ./run-test.in
 
-dist_noinst_HEADERS = lib.h
+if HAVE_GUILE
+
+TESTS = $(gcc_tests)
+TESTS_ENVIRONMENT = ./run-test
+
+else !HAVE_GUILE
 
-AM_CPPFLAGS = -I$(builddir)
+EXTRA_DIST += $(gcc_tests)
 
-# Unfortunately `libtool --mode=execute' doesn't help here, so
-# hard-code the real file name.
-AM_CFLAGS =					\
-  -fplugin="$(builddir)/../src/.libs/starpu.so"	\
-  -dH -fdump-tree-gimple -Wall
+check-hook:
+	-@echo "GNU Guile not available, test suite not run."
 
-CLEANFILES = *.gimple
+endif !HAVE_GUILE

+ 2 - 0
gcc-plugin/tests/pointer-tasks.c

@@ -14,6 +14,8 @@
    You should have received a copy of the GNU General Public License
    along with GCC-StarPU.  If not, see <http://www.gnu.org/licenses/>.  */
 
+/* (instructions run (ldflags "-lstarpu")) */
+
 #undef NDEBUG
 
 #include <stdlib.h>

+ 1 - 1
gcc-plugin/tests/register.c

@@ -33,7 +33,7 @@ main (int argc, char *argv[])
   expected_register_arguments.pointer = x;
   expected_register_arguments.elements = 123;
   expected_register_arguments.element_size = sizeof x[0];
-#pragma starpu register x 123
+#pragma starpu register x 123 /* (note "can be omitted") */
 
   expected_register_arguments.pointer = y;
   expected_register_arguments.elements = 234;

+ 396 - 0
gcc-plugin/tests/run-test.in

@@ -0,0 +1,396 @@
+#!/bin/sh
+# -*- mode: scheme; coding: utf-8; -*-
+GUILE_AUTO_COMPILE=0
+export GUILE_AUTO_COMPILE
+main='(@ (run-test) build/run)'
+exec "${GUILE-@GUILE@}" -l "$0"    \
+         -c "(apply $main (cdr (command-line)))" "$@"
+!#
+;;; GCC-StarPU
+;;; Copyright (C) 2011 Institut National de Recherche en Informatique et Automatique
+;;;
+;;; GCC-StarPU is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation, either version 3 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; GCC-StarPU is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GCC-StarPU.  If not, see <http://www.gnu.org/licenses/>.
+
+;;;
+;;; Written by Ludovic Courtès <ludovic.courtes@inria.fr>.
+;;;
+
+(define-module (run-test)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-13)
+  #:use-module (srfi srfi-14)
+  #:use-module (srfi srfi-26)
+  #:export (build/run))
+
+;;; Commentary:
+;;;
+;;; Test machinery similary to the DejaGNU-based test framework used in GCC.
+;;; In a nutshell, this program compiles code with GCC and makes sure
+;;; warnings and errors are as appear in source code comments.
+;;;
+;;; This module should work with both Guile 1.8 and Guile 2.0.
+;;;
+;;; Code:
+
+;; Make sure the reader gets position information.
+(read-enable 'positions)
+
+(define %log-port
+  ;; Output port for debugging messages.
+  (current-output-port))
+
+(define (log fmt . args)
+  "Write an informational message."
+  (apply format %log-port (string-append fmt "\n") args))
+
+
+;;;
+;;; Compiling code.
+;;;
+
+(define %srcdir "@srcdir@")
+(define %builddir "@builddir@")
+(define %gcc "@CC@")
+
+(define %default-cflags
+  `("-I" ,%srcdir
+    "-I" ,(string-append %srcdir "/../../include")
+    "-I" ,(string-append %builddir "/../../include")
+    "-I" ,(string-append %builddir "/../..")
+
+    ;; Unfortunately `libtool --mode=execute' doesn't help here, so hard-code
+    ;; the real file name.
+    ,(string-append "-fplugin=" %builddir "/../src/.libs/starpu.so")
+
+    "-dH" "-fdump-tree-gimple" "-Wall"))
+
+(define %default-ldflags
+  `(,(string-append "-L" %srcdir "/../../src")))
+
+(define %libtool
+  (string-append %builddir "/../../libtool"))
+
+
+(define (compile-starpu-code file cc cflags ldflags)
+  "Compile and link FILE with CC, using CFLAGS and LDFLAGS.  Return the
+compiler status and the list of lines printed on stdout/stderr."
+  (let* ((compile? (member "-c" cflags))
+         (ldflags  (if compile?
+                       (remove (cut string-prefix? "-L" <>) ldflags)
+                       ldflags))
+         (mode     (if compile?
+                       "compile"
+                       "link"))
+         (command  (format #f "LANG=C ~a --mode=~a ~a ~{~a ~} \"~a\" ~{~a ~} 2>&1"
+                           %libtool mode cc cflags file ldflags))
+         (pipe     (begin
+                     (log "running `~a'" command)
+                     (open-input-pipe command))))
+    (let loop ((line    (read-line pipe))
+               (result '()))
+      (if (eof-object? line)
+          (values (close-pipe pipe) (reverse result))
+          (loop (read-line pipe)
+                (cons line result))))))
+
+(define (run-starpu-code executable)
+  "Run EXECUTABLE using Libtool; return its exit status."
+  (let* ((exe     (if (string-index executable #\/)
+                      executable
+                      (string-append (getcwd) "/" executable)))
+         (command (string-append %libtool " --mode=execute "
+                                 exe)))
+    (log "running `~a'" command)
+    (system command)))
+
+
+;;;
+;;; GCC diagnostics.
+;;;
+
+(define-record-type <location>
+  (make-location file line column)
+  location?
+  (file     location-file)
+  (line     location-line)
+  (column   location-column))
+
+(define (location=? loc1 loc2)
+  "Return #t if LOC1 and LOC2 refer roughly to the same file and line
+number."
+  (and (location-file loc1) (location-file loc2)
+       (string=? (basename (location-file loc1))
+                 (basename (location-file loc2)))
+       (= (location-line loc1) (location-line loc2))))
+
+(define-record-type <diagnostic>
+  (make-diagnostic location kind message)
+  diagnostic?
+  (location diagnostic-location)
+  (kind     diagnostic-kind)
+  (message  diagnostic-message))
+
+(define %diagnostic-with-location-rx
+  ;; "FILE:LINE:COL: KIND: MESSAGE..."
+  (make-regexp "^(.+):([[:digit:]]+):([[:digit:]]+): ([^:]+): (.*)$"))
+
+(define (string->diagnostic str)
+  "Parse STR and return the corresponding `diagnostic' object."
+  (cond ((regexp-exec %diagnostic-with-location-rx str)
+         =>
+         (lambda (m)
+           (let ((loc  (make-location (match:substring m 1)
+                                      (string->number (match:substring m 2))
+                                      (string->number (match:substring m 3))))
+                 (kind (string->symbol (match:substring m 4))))
+            (make-diagnostic loc kind (match:substring m 5)))))
+        (else
+         (make-diagnostic #f #f str))))
+
+
+;;;
+;;; Reading test directives.
+;;;
+
+(define (read-test-directives port)
+  "Read test directives from PORT.  Return a list of location/directive
+pairs."
+  (define (consume-whitespace p)
+    (let loop ((chr (peek-char p)))
+      (cond ((char-set-contains? char-set:whitespace chr)
+             (read-char p) ;; consume CHR
+             (loop (peek-char p)))
+            (else chr))))
+
+  (define (read-until-*/ p)
+    (let loop ((chr (read-char p)))
+      (cond ((eof-object? chr)
+             (error "unterminated C comment"))
+            ((eq? chr #\*)
+             (let ((next (peek-char p)))
+               (if (eq? next #\/)
+                   (read-char p) ;; consume CHR
+                   (loop (read-char p)))))
+            (else
+             (loop (read-char p))))))
+
+  (let loop ((chr        (read-char port))
+             (directives '()))
+    (cond ((eof-object? chr)
+           (reverse directives))
+          ((eq? chr #\/)
+           (let ((chr (read-char port)))
+             (if (eq? chr #\*)
+                 (let ((chr (consume-whitespace port)))
+                   (if (eq? chr #\()
+                       (let ((loc  (make-location (port-filename port)
+                                                  (1+ (port-line port))
+                                                  (port-column port)))
+                             (sexp (read port)))
+                         (read-until-*/ port)
+                         (loop (peek-char port)
+                               (cons (cons loc sexp)
+                                     directives)))
+                       (begin
+                         (read-until-*/ port)
+                         (loop (peek-char port) directives))))
+                 (loop chr directives))))
+          (else
+           (loop (read-char port) directives)))))
+
+(define (diagnostic-matches-directive? diagnostic directive location)
+  "Return #t if DIAGNOSTIC matches DIRECTIVE, which is at LOCATION."
+  (and (location? (diagnostic-location diagnostic))
+       (location=? (diagnostic-location diagnostic) location)
+       (match directive
+         ((kind message)
+          (and (eq? kind (diagnostic-kind diagnostic))
+               (string-match message (diagnostic-message diagnostic)))))))
+
+
+;;;
+;;; Compiling and matching diagnostics against directives.
+;;;
+
+(define (compile/match* file directives cc cflags ldflags)
+  "Compile FILE and check whether GCC's diagnostics match DIRECTIVES.  Return
+3 values: the compiler's status code, the unmatched diagnostics, and the
+unsatisfied directives."
+  (let-values (((status diagnostics)
+                (compile-starpu-code file cc cflags ldflags)))
+    (let loop ((diagnostics (map string->diagnostic diagnostics))
+               (directives  directives)
+               (unsatisfied '()))
+      (if (null? directives)
+          (values status diagnostics unsatisfied)
+          (let* ((dir  (car directives))
+                 (diag (find (cute diagnostic-matches-directive?
+                                   <> (cdr dir) (car dir))
+                             diagnostics)))
+            (if diag
+                (loop (delq diag diagnostics)
+                      (cdr directives)
+                      unsatisfied)
+                (loop diagnostics
+                      (cdr directives)
+                      (cons dir unsatisfied))))))))
+
+(define (executable-file source)
+  "Return the name of the executable file corresponding to SOURCE."
+  (let ((dot (string-rindex source #\.)))
+    (if dot
+        (substring source 0 dot)
+        (string-append source ".exe"))))
+
+(define (compile/match file cc cflags ldflags)
+  "Read directives from FILE, and compiler/link/run it.  Make sure directives
+are matched, and report any errors otherwise.  Return #t on success and #f
+otherwise."
+  (define directives
+    (call-with-input-file file read-test-directives))
+
+  (define exe
+    (executable-file file))
+
+  (log "~a directives found in `~a'" (length directives) file)
+
+  (let*-values (((error-expected?)
+                 (find (lambda (l+d)
+                         (match l+d
+                           (((? location?) 'error _)
+                            #t)
+                           (_ #f)))
+                       directives))
+                ((instructions)
+                 (or (any (lambda (l+d)
+                            (match l+d
+                              (((? location?) 'instructions x ...)
+                               x)
+                              (_ #f)))
+                          directives)
+                     '(run)))
+                ((goal)
+                 (if error-expected?
+                     'compile
+                     (car instructions)))
+                ((cflags)
+                 `(,@cflags
+                   ,@(or (and=> (assq-ref (cdr instructions) 'cflags) cadr)
+                         '())
+                   ,@(if (memq goal '(link run))
+                         `("-o" ,exe)
+                         '("-c"))))
+                ((ldflags)
+                 `(,@ldflags
+                   ,@(or (assq-ref (cdr instructions) 'ldflags)
+                         '())))
+                ((directives)
+                 (remove (lambda (l+d)
+                           (match l+d
+                             (((? location?) 'instructions _ ...)
+                              #t)
+                             (_ #f)))
+                         directives))
+                ((status diagnostics unsatisfied)
+                 (compile/match* file directives cc cflags ldflags))
+                ((unmatched)
+                 ;; Consider unmatched only diagnostics that have a kind, to
+                 ;; avoid taking into account messages like "In file included
+                 ;; from", "In function 'main'", etc.
+                 (filter diagnostic-kind diagnostics)))
+
+    (or (null? unmatched)
+        (begin
+          (format (current-error-port)
+                  "error: ~a unmatched GCC diagnostics:~%"
+                  (length unmatched))
+          (for-each (lambda (d)
+                      (format (current-error-port)
+                              "  ~a:~a:~a: ~a: ~a~%"
+                              (and=> (diagnostic-location d)
+                                     location-file)
+                              (and=> (diagnostic-location d)
+                                     location-line)
+                              (and=> (diagnostic-location d)
+                                     location-column)
+                              (diagnostic-kind d)
+                              (diagnostic-message d)))
+                    unmatched)
+          #f))
+
+    (if (null? unsatisfied)
+        (or (null? directives)
+            (log "~a directives satisfied" (length directives)))
+        (begin
+          (format (current-error-port) "error: ~a unsatisfied directives:~%"
+                  (length unsatisfied))
+          (for-each (lambda (l+d)
+                      (let ((loc (car l+d))
+                            (dir (cdr l+d)))
+                        (format (current-error-port)
+                                "  ~a:~a:~a: ~a: ~s~%"
+                                (location-file loc)
+                                (location-line loc)
+                                (location-column loc)
+                                (car dir)
+                                (cadr dir))))
+                    unsatisfied)
+          #f))
+
+    (if error-expected?
+        (if (= 0 status)
+            (format (current-error-port) "error: compilation succeeded~%"))
+        (if (= 0 status)
+            (or (eq? goal 'compile)
+                (file-exists? exe)
+                (begin
+                  (format (current-error-port)
+                          "error: executable file `~a' not found~%" exe)
+                  #f))
+            (format (current-error-port)
+                    "error: compilation failed (compiler exit code ~a)~%~{  ~a~%~}"
+                    status
+                    (map diagnostic-message diagnostics))))
+
+    (and (null? unmatched)
+         (null? unsatisfied)
+         (if error-expected?
+             (not (= 0 status))
+             (and (= 0 status)
+                  (or (eq? goal 'compile) (file-exists? exe))
+                  (or (not (eq? goal 'run))
+                      (let ((status (run-starpu-code exe)))
+                        (or (= 0 status)
+                            (begin
+                              (format (current-error-port)
+                                      "error: program `~a' failed (exit code ~a)~%"
+                                      exe status)
+                              #f)))))))))
+
+
+;;;
+;;; Entry point.
+;;;
+
+(define (build/run . file)
+  (exit (every (cut compile/match <> %gcc %default-cflags %default-ldflags) file)))
+
+;;; run-test.in ends here

+ 2 - 0
gcc-plugin/tests/scalar-tasks.c

@@ -14,6 +14,8 @@
    You should have received a copy of the GNU General Public License
    along with GCC-StarPU.  If not, see <http://www.gnu.org/licenses/>.  */
 
+/* (instructions run (ldflags "-lstarpu")) */
+
 #undef NDEBUG
 
 #include <stdlib.h>