1 ;; Common definitions for writing tests.
3 ;; Copyright (C) 2016 g10 Code GmbH
5 ;; This file is part of GnuPG.
7 ;; GnuPG 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.
12 ;; GnuPG 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.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ;; Trace displays and returns the given value. A debugging aid.
27 (define (stringify expression)
28 (let ((p (open-output-string)))
30 (get-output-string p)))
34 (for-each (lambda (x) (display x) (display " ")) msg)
49 (define (make-counter)
56 (define *progress-nesting* 0)
58 (define (call-with-progress msg what)
59 (set! *progress-nesting* (+ 1 *progress-nesting*))
60 (if (= 1 *progress-nesting*)
71 (what (lambda (item) (display ".") (flush-stdio)))
74 (set! *progress-nesting* (- *progress-nesting* 1)))
76 (define (for-each-p msg proc lst)
77 (for-each-p' msg proc (lambda (x) x) lst))
79 (define (for-each-p' msg proc fmt lst)
88 ;; Process management.
90 (define (call-with-fds what infd outfd errfd)
91 (wait-process (stringify what) (spawn-process-fd what infd outfd errfd) #t))
95 (if (< *verbose* 0) STDOUT_FILENO CLOSED_FD)
96 (if (< *verbose* 0) STDERR_FILENO CLOSED_FD)))
97 (define (call-check what)
98 (if (not (= 0 (call what)))
99 (throw (list what "failed"))))
101 ;; Accessor functions for the results of 'spawn-process'.
103 (define :stdout cadr)
104 (define :stderr caddr)
107 (define (call-with-io what in)
108 (let ((h (spawn-process what 0)))
109 (es-write (:stdin h) in)
110 (es-fclose (:stdin h))
111 (let* ((out (es-read-all (:stdout h)))
112 (err (es-read-all (:stderr h)))
113 (result (wait-process (car what) (:pid h) #t)))
114 (es-fclose (:stdout h))
115 (es-fclose (:stderr h))
116 (list result out err))))
118 ;; Accessor function for the results of 'call-with-io'. ':stdout' and
119 ;; ':stderr' can also be used.
120 (define :retcode car)
122 (define (call-popen command input-string)
123 (let ((result (call-with-io command input-string)))
124 (if (= 0 (:retcode result))
126 (throw (:stderr result)))))
132 (define (es-read-all stream)
137 (loop (string-append acc (es-read stream 4096))))))
142 (define (file-exists? name)
143 (call-with-input-file name (lambda (port) #t)))
148 (define (text-file=? a b)
151 (define (file-copy from to)
152 (catch '() (unlink to))
153 (letfd ((source (open from (logior O_RDONLY O_BINARY)))
154 (sink (open to (logior O_WRONLY O_CREAT O_BINARY) #o600)))
155 (splice source sink)))
157 (define (text-file-copy from to)
158 (catch '() (unlink to))
159 (letfd ((source (open from O_RDONLY))
160 (sink (open to (logior O_WRONLY O_CREAT) #o600)))
161 (splice source sink)))
163 (define (canonical-path path)
164 (if (char=? #\/ (string-ref path 0))
166 (string-append (getcwd) "/" path)))
168 (define (in-srcdir what)
169 (canonical-path (string-append (getenv "srcdir") "/" what)))
171 (define (with-path name)
172 (let loop ((path (string-split (getenv "GPGSCM_PATH") #\:)))
175 (let* ((qualified-name (string-append (car path) "/" name))
176 (file-exists (call-with-input-file qualified-name
180 (loop (cdr path)))))))
182 (define (basename path)
183 (let ((i (string-index path #\/)))
186 (basename (substring path (+ 1 i) (string-length path))))))
188 (define (basename-suffix path suffix)
190 (if (string-suffix? path suffix)
191 (substring path 0 (- (string-length path) (string-length suffix)))
194 ;; Helper for (pipe).
195 (define :read-end car)
196 (define :write-end cadr)
198 ;; let-like macro that manages file descriptors.
200 ;; (letfd <bindings> <body>)
202 ;; Bind all variables given in <bindings> and initialize each of them
203 ;; to the given initial value, and close them after evaluting <body>.
205 (let ((result-sym (gensym)))
206 `((lambda (,(caaadr form))
208 ,(if (= 1 (length (cadr form)))
209 `(begin ,@(cddr form))
210 `(letfd ,(cdadr form) ,@(cddr form)))))
211 (close ,(caaadr form))
212 ,result-sym)) ,@(cdaadr form))))
214 (macro (with-working-directory form)
215 (let ((result-sym (gensym)) (cwd-sym (gensym)))
216 `(let* ((,cwd-sym (getcwd))
217 (_ (if ,(cadr form) (chdir ,(cadr form))))
218 (,result-sym (begin ,@(cddr form))))
222 (macro (with-temporary-working-directory form)
223 (let ((result-sym (gensym)) (cwd-sym (gensym)) (tmp-sym (gensym)))
224 `(let* ((,cwd-sym (getcwd))
225 (,tmp-sym (mkdtemp "gpgscm-XXXXXX"))
227 (,result-sym (begin ,@(cdr form))))
229 (unlink-recursively ,tmp-sym)
232 (define (make-temporary-file . args)
233 (canonical-path (string-append (mkdtemp "gpgscm-XXXXXX")
235 (if (null? args) "a" (car args)))))
237 (define (remove-temporary-file filename)
240 (let ((dirname (substring filename 0 (string-rindex filename #\/))))
241 (catch (echo "removing temporary directory" dirname "failed")
244 ;; let-like macro that manages temporary files.
246 ;; (lettmp <bindings> <body>)
248 ;; Bind all variables given in <bindings>, initialize each of them to
249 ;; a string representing an unique path in the filesystem, and delete
250 ;; them after evaluting <body>.
252 (let ((result-sym (gensym)))
253 `((lambda (,(caadr form))
255 ,(if (= 1 (length (cadr form)))
256 `(begin ,@(cddr form))
257 `(lettmp ,(cdadr form) ,@(cddr form)))))
258 (remove-temporary-file ,(caadr form))
259 ,result-sym)) (make-temporary-file ,(symbol->string (caadr form))))))
261 (define (check-execution source transformer)
263 (transformer source sink)))
265 (define (check-identity source transformer)
267 (transformer source sink)
268 (if (not (file=? source sink))
269 (error "mismatch"))))
272 ;; Monadic pipe support.
277 (define (new procs source sink producer)
280 (write (list procs source sink producer))
282 (define (add-proc command pid)
283 (new (cons (list command pid) procs) source sink producer))
288 (define (set-source source')
289 (new procs source' sink producer))
290 (define (set-sink sink')
291 (new procs source sink' producer))
292 (define (set-producer producer')
294 (throw "producer already set"))
295 (new procs source sink producer'))))))
298 (define (pipe:do . commands)
299 (let loop ((M (pipeM::new '() CLOSED_FD CLOSED_FD #f)) (cmds commands))
302 (if M::producer (M::producer))
303 (if (not (null? M::procs))
304 (let* ((retcodes (wait-processes (map stringify (M::commands))
306 (results (map (lambda (p r) (append p (list r)))
308 (failed (filter (lambda (x) (not (= 0 (caddr x))))
310 (if (not (null? failed))
311 (throw failed))))) ; xxx nicer reporting
312 (if (and (= 2 (length cmds)) (number? (cadr cmds)))
313 ;; hack: if it's an fd, use it as sink
314 (let ((M' ((car cmds) (M::set-sink (cadr cmds)))))
315 (if (> M::source 2) (close M::source))
316 (if (> (cadr cmds) 2) (close (cadr cmds)))
318 (let ((M' ((car cmds) M)))
319 (if (> M::source 2) (close M::source))
320 (loop M' (cdr cmds)))))))
322 (define (pipe:open pathname flags)
324 (M::set-source (open pathname flags))))
326 (define (pipe:defer producer)
328 (let* ((p (outbound-pipe))
329 (M' (M::set-source (:read-end p))))
330 (M'::set-producer (lambda ()
331 (producer (:write-end p))
332 (close (:write-end p)))))))
333 (define (pipe:echo data)
334 (pipe:defer (lambda (sink) (display data (fdopen sink "wb")))))
336 (define (pipe:spawn command)
338 (define (do-spawn M new-source)
339 (let ((pid (spawn-process-fd command M::source M::sink
341 STDERR_FILENO CLOSED_FD)))
342 (M' (M::set-source new-source)))
343 (M'::add-proc command pid)))
344 (if (= CLOSED_FD M::sink)
346 (M' (do-spawn (M::set-sink (:write-end p)) (:read-end p))))
347 (close (:write-end p))
348 (M'::set-sink CLOSED_FD))
349 (do-spawn M CLOSED_FD))))
351 (define (pipe:splice sink)
353 (splice M::source sink)
354 (M::set-source CLOSED_FD)))
356 (define (pipe:write-to pathname flags mode)
357 (open pathname flags mode))
360 ;; Monadic transformer support.
363 (define (tr:do . commands)
364 (let loop ((tmpfiles '()) (source #f) (cmds commands))
366 (for-each remove-temporary-file tmpfiles)
367 (let* ((v ((car cmds) tmpfiles source))
373 (for-each remove-temporary-file tmpfiles')
375 (loop tmpfiles' sink (cdr cmds))))))
377 (define (tr:open pathname)
378 (lambda (tmpfiles source)
379 (list tmpfiles pathname #f)))
381 (define (tr:spawn input command)
382 (lambda (tmpfiles source)
383 (if (and (member '**in** command) (not source))
384 (error (string-append (stringify cmd) " needs an input")))
385 (let* ((t (make-temporary-file))
386 (cmd (map (lambda (x)
388 ((equal? '**in** x) source)
389 ((equal? '**out** x) t)
390 (else x))) command)))
391 (catch (list (cons t tmpfiles) t *error*)
392 (call-popen cmd input)
393 (if (and (member '**out** command) (not (file-exists? t)))
394 (error (string-append (stringify cmd)
395 " did not produce '" t "'.")))
396 (list (cons t tmpfiles) t #f)))))
398 (define (tr:write-to pathname)
399 (lambda (tmpfiles source)
400 (rename source pathname)
401 (list tmpfiles pathname #f)))
403 (define (tr:pipe-do . commands)
404 (lambda (tmpfiles source)
405 (let ((t (make-temporary-file)))
407 `(,@(if source `(,(pipe:open source (logior O_RDONLY O_BINARY))) '())
409 ,(pipe:write-to t (logior O_WRONLY O_BINARY O_CREAT) #o600)))
410 (list (cons t tmpfiles) t #f))))
412 (define (tr:assert-identity reference)
413 (lambda (tmpfiles source)
414 (if (not (file=? source reference))
416 (list tmpfiles source #f)))
418 (define (tr:assert-weak-identity reference)
419 (lambda (tmpfiles source)
420 (if (not (text-file=? source reference))
422 (list tmpfiles source #f)))
424 (define (tr:call-with-content function . args)
425 (lambda (tmpfiles source)
426 (catch (list tmpfiles source *error*)
427 (apply function `(,(call-with-input-file source read-all) ,@args)))
428 (list tmpfiles source #f)))