#!/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, 2012 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 . ;;; ;;; Written by Ludovic Courtès . ;;; (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 similar 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 %cuda-cppflags (string-tokenize "@STARPU_CUDA_CPPFLAGS@")) (define %default-cflags `("-I" ,%srcdir "-I" ,(string-append %srcdir "/../../src") ; for "-I" ,(string-append %srcdir "/../../include") "-I" ,(string-append %builddir "/../../include") "-I" ,(string-append %builddir "/../..") ,@%cuda-cppflags ;; Unfortunately `libtool --mode=execute' doesn't help here, so hard-code ;; the real file name. ,(string-append "-fplugin=" %builddir "/../src/.libs/starpu.so") ;; Use the non-installed headers. ,(string-append "-fplugin-arg-starpu-include-dir=@top_srcdir@/include") "-g" "-fdump-tree-gimple" "-Wall")) (define %default-ldflags `(,(string-append "-L" %builddir "/../../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 "LC_ALL=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 (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 (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 #\.)) (exe (if dot (substring source 0 dot) (string-append source ".exe"))) ) (if (string-prefix? %srcdir exe) (string-append %builddir (substring exe (string-length %srcdir))) 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)) (define (c->o c-file) (string-append (substring c-file 0 (- (string-length c-file) 2)) ".lo")) (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))) ((options) (match instructions ((_ options ...) options) (_ '()))) ((dependencies) (or (assq-ref options 'dependencies) '()))) (or (null? dependencies) (format (current-output-port) "~s has ~a dependencies: ~{~s ~}~%" file (length dependencies) dependencies)) (and (every (cut compile/match <> cc cflags ldflags) (map (cut string-append %srcdir "/" <>) dependencies)) (let*-values (((goal) (if error-expected? 'compile (car instructions))) ((cflags) `(,@cflags ,@(or (assq-ref options 'cflags) '()) ,@(if (memq goal '(link run)) `("-o" ,exe) '("-c")))) ((ldflags) `(,@(map c->o dependencies) ,@ldflags ,@(or (assq-ref options '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 (lambda (file) ;; For each file, check that everything works both with and ;; without optimizations. (every (cut compile/match file %gcc <> %default-ldflags) `((,"-O0" ,@%default-cflags) (,"-O2" ,@%default-cflags)))) file))) ;;; run-test.in ends here