summary refs log tree commit diff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/gexp.scm4
-rw-r--r--tests/guix-build.sh4
-rw-r--r--tests/guix-pack.sh30
-rw-r--r--tests/guix-package.sh15
-rw-r--r--tests/guix-system.sh4
-rw-r--r--tests/inferior.scm123
-rw-r--r--tests/pack.scm75
-rw-r--r--tests/pypi.scm2
-rw-r--r--tests/services.scm30
-rw-r--r--tests/status.scm183
-rw-r--r--tests/store.scm63
11 files changed, 468 insertions, 65 deletions
diff --git a/tests/gexp.scm b/tests/gexp.scm
index 380b83509a..bc83a8de8c 100644
--- a/tests/gexp.scm
+++ b/tests/gexp.scm
@@ -615,6 +615,8 @@
                                                 `(("graph" ,two))
                                                 #:modules
                                                 '((guix build store-copy)
+                                                  (guix progress)
+                                                  (guix records)
                                                   (guix sets)
                                                   (guix build utils))))
                          (ok? (built-derivations (list drv)))
@@ -817,6 +819,8 @@
        (two (gexp->derivation "two"
                               #~(symlink #$one #$output:chbouib)))
        (build -> (with-imported-modules '((guix build store-copy)
+                                          (guix progress)
+                                          (guix records)
                                           (guix sets)
                                           (guix build utils))
                    #~(begin
diff --git a/tests/guix-build.sh b/tests/guix-build.sh
index 92e7299321..7842ce87c6 100644
--- a/tests/guix-build.sh
+++ b/tests/guix-build.sh
@@ -221,6 +221,10 @@ guix build -e "(begin
 guix build -e '#~(mkdir #$output)' -d
 guix build -e '#~(mkdir #$output)' -d | grep 'gexp\.drv'
 
+# Same with a file-like object.
+guix build -e '(computed-file "foo" #~(mkdir #$output))' -d
+guix build -e '(computed-file "foo" #~(mkdir #$output))' -d | grep 'foo\.drv'
+
 # Building from a package file.
 cat > "$module_dir/package.scm"<<EOF
 (use-modules (gnu))
diff --git a/tests/guix-pack.sh b/tests/guix-pack.sh
index bf367fa429..cd721a60e9 100644
--- a/tests/guix-pack.sh
+++ b/tests/guix-pack.sh
@@ -1,5 +1,6 @@
 # GNU Guix --- Functional package management for GNU
 # Copyright © 2018 Chris Marusich <cmmarusich@gmail.com>
+# Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
 #
 # This file is part of GNU Guix.
 #
@@ -28,26 +29,33 @@ fi
 
 guix pack --version
 
-# FIXME: Starting from commit 66e9944e078cbb9e0d618377dd6df6e639640efa,
-# '--bootstrap' is mostly ineffective since 'guix pack' produces derivations
-# that refer to guile-sqlite3 and libgcrypt.  For now we just skip the test.
-exit 77
+# Starting from commit 66e9944e078cbb9e0d618377dd6df6e639640efa, 'guix pack'
+# produces derivations that refer to guile-sqlite3 and libgcrypt.  To make
+# that relatively inexpensive, run the test in the user's global store if
+# possible, on the grounds that binaries may already be there or can be built
+# or downloaded inexpensively.
 
-# Use --no-substitutes because we need to verify we can do this ourselves.
-GUIX_BUILD_OPTIONS="--no-substitutes"
-export GUIX_BUILD_OPTIONS
+NIX_STORE_DIR="`guile -c '(use-modules (guix config))(display %storedir)'`"
+localstatedir="`guile -c '(use-modules (guix config))(display %localstatedir)'`"
+GUIX_DAEMON_SOCKET="$localstatedir/guix/daemon-socket/socket"
+export NIX_STORE_DIR GUIX_DAEMON_SOCKET
+
+if ! guile -c '(use-modules (guix)) (exit (false-if-exception (open-connection)))'
+then
+    exit 77
+fi
 
 # Build a tarball with no compression.
-guix pack --compression=none --bootstrap guile-bootstrap
+guix pack --compression=none guile-bootstrap
 
 # Build a tarball (with compression).  Check that '-e' works as well.
-out1="`guix pack --bootstrap guile-bootstrap`"
-out2="`guix pack --bootstrap -e '(@ (gnu packages bootstrap) %bootstrap-guile)'`"
+out1="`guix pack guile-bootstrap`"
+out2="`guix pack -e '(@ (gnu packages bootstrap) %bootstrap-guile)'`"
 test -n "$out1"
 test "$out1" = "$out2"
 
 # Build a tarball with a symlink.
-the_pack="`guix pack --bootstrap -S /opt/gnu/bin=bin guile-bootstrap`"
+the_pack="`guix pack -S /opt/gnu/bin=bin guile-bootstrap`"
 
 # Try to extract it.  Note: we cannot test whether /opt/gnu/bin/guile itself
 # exists because /opt/gnu/bin may be an absolute symlink to a store item that
diff --git a/tests/guix-package.sh b/tests/guix-package.sh
index cef3b3452e..f7dfbfad00 100644
--- a/tests/guix-package.sh
+++ b/tests/guix-package.sh
@@ -358,6 +358,21 @@ EOF
 guix package --bootstrap -m "$module_dir/manifest.scm"
 guix package -I | grep guile
 test `guix package -I | wc -l` -eq 1
+guix package --rollback --bootstrap
+
+# Applying a manifest file with inferior packages.
+cat > "$module_dir/manifest.scm"<<EOF
+(use-modules (guix inferior))
+
+(define i
+  (open-inferior "$abs_top_srcdir" #:command "scripts/guix"))
+
+(let ((guile (car (lookup-inferior-packages i "guile-bootstrap"))))
+  (packages->manifest (list guile)))
+EOF
+guix package --bootstrap -m "$module_dir/manifest.scm"
+guix package -I | grep guile
+test `guix package -I | wc -l` -eq 1
 
 # Error reporting.
 cat > "$module_dir/manifest.scm"<<EOF
diff --git a/tests/guix-system.sh b/tests/guix-system.sh
index a129efdfcb..23d2da4903 100644
--- a/tests/guix-system.sh
+++ b/tests/guix-system.sh
@@ -153,8 +153,8 @@ cat > "$tmpfile" <<EOF
 
 (operating-system
   $OS_BASE
-  (services (cons* (dhcp-client-service)
-                   (dhcp-client-service) ;twice!
+  (services (cons* (service dhcp-client-service-type)
+                   (service dhcp-client-service-type) ;twice!
                    %base-services)))
 EOF
 
diff --git a/tests/inferior.scm b/tests/inferior.scm
index ff5cad4210..d1d5c00a77 100644
--- a/tests/inferior.scm
+++ b/tests/inferior.scm
@@ -17,11 +17,18 @@
 ;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
 
 (define-module (test-inferior)
+  #:use-module (guix tests)
   #:use-module (guix inferior)
   #:use-module (guix packages)
+  #:use-module (guix store)
+  #:use-module (guix profiles)
+  #:use-module (guix derivations)
   #:use-module (gnu packages)
+  #:use-module (gnu packages bootstrap)
+  #:use-module (gnu packages guile)
   #:use-module (srfi srfi-1)
-  #:use-module (srfi srfi-64))
+  #:use-module (srfi srfi-64)
+  #:use-module (ice-9 match))
 
 (define %top-srcdir
   (dirname (search-path %load-path "guix.scm")))
@@ -29,6 +36,16 @@
 (define %top-builddir
   (dirname (search-path %load-compiled-path "guix.go")))
 
+(define %store
+  (open-connection-for-tests))
+
+(define (manifest-entry->list entry)
+  (list (manifest-entry-name entry)
+        (manifest-entry-version entry)
+        (manifest-entry-output entry)
+        (manifest-entry-search-paths entry)
+        (map manifest-entry->list (manifest-entry-dependencies entry))))
+
 
 (test-begin "inferior")
 
@@ -72,4 +89,108 @@
            (close-inferior inferior)
            result))))
 
+(test-equal "lookup-inferior-packages"
+  (let ((->list (lambda (package)
+                  (list (package-name package)
+                        (package-version package)
+                        (package-location package)))))
+    (list (map ->list (find-packages-by-name "guile" #f))
+          (map ->list (find-packages-by-name "guile" "2.2"))))
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (->list   (lambda (package)
+                     (list (inferior-package-name package)
+                           (inferior-package-version package)
+                           (inferior-package-location package))))
+         (lst1     (map ->list
+                        (lookup-inferior-packages inferior "guile")))
+         (lst2     (map ->list
+                        (lookup-inferior-packages inferior
+                                                  "guile" "2.2"))))
+    (close-inferior inferior)
+    (list lst1 lst2)))
+
+(test-assert "lookup-inferior-packages and eq?-ness"
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (lst1     (lookup-inferior-packages inferior "guile"))
+         (lst2     (lookup-inferior-packages inferior "guile")))
+    (close-inferior inferior)
+    (every eq? lst1 lst2)))
+
+(test-equal "inferior-package-inputs"
+  (let ((->list (match-lambda
+                  ((label (? package? package) . rest)
+                   `(,label
+                     (package ,(package-name package)
+                              ,(package-version package)
+                              ,(package-location package))
+                     ,@rest)))))
+    (list (map ->list (package-inputs guile-2.2))
+          (map ->list (package-native-inputs guile-2.2))
+          (map ->list (package-propagated-inputs guile-2.2))))
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (guile    (first (lookup-inferior-packages inferior "guile")))
+         (->list   (match-lambda
+                     ((label (? inferior-package? package) . rest)
+                      `(,label
+                        (package ,(inferior-package-name package)
+                                 ,(inferior-package-version package)
+                                 ,(inferior-package-location package))
+                        ,@rest))))
+         (result   (list (map ->list (inferior-package-inputs guile))
+                         (map ->list
+                              (inferior-package-native-inputs guile))
+                         (map ->list
+                              (inferior-package-propagated-inputs
+                               guile)))))
+    (close-inferior inferior)
+    result))
+
+(test-equal "inferior-package-search-paths"
+  (package-native-search-paths guile-2.2)
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (guile    (first (lookup-inferior-packages inferior "guile")))
+         (result   (inferior-package-native-search-paths guile)))
+    (close-inferior inferior)
+    result))
+
+(test-equal "inferior-package-derivation"
+  (map derivation-file-name
+       (list (package-derivation %store %bootstrap-guile "x86_64-linux")
+             (package-derivation %store %bootstrap-guile "armhf-linux")))
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (packages (inferior-packages inferior))
+         (guile    (find (lambda (package)
+                           (string=? (package-name %bootstrap-guile)
+                                     (inferior-package-name package)))
+                         packages)))
+    (map derivation-file-name
+         (list (inferior-package-derivation %store guile "x86_64-linux")
+               (inferior-package-derivation %store guile "armhf-linux")))))
+
+(test-equal "inferior-package->manifest-entry"
+  (manifest-entry->list (package->manifest-entry
+                         (first (find-best-packages-by-name "guile" #f))))
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (guile    (first (lookup-inferior-packages inferior "guile")))
+         (entry    (inferior-package->manifest-entry guile)))
+    (close-inferior inferior)
+    (manifest-entry->list entry)))
+
+(test-equal "packages->manifest"
+  (map manifest-entry->list
+       (manifest-entries (packages->manifest
+                          (find-best-packages-by-name "guile" #f))))
+  (let* ((inferior (open-inferior %top-builddir
+                                  #:command "scripts/guix"))
+         (guile    (first (lookup-inferior-packages inferior "guile")))
+         (manifest (packages->manifest (list guile))))
+    (close-inferior inferior)
+    (map manifest-entry->list (manifest-entries manifest))))
+
 (test-end "inferior")
diff --git a/tests/pack.scm b/tests/pack.scm
index c57c6848ff..7f867894c2 100644
--- a/tests/pack.scm
+++ b/tests/pack.scm
@@ -29,15 +29,12 @@
   #:use-module (gnu packages bootstrap)
   #:use-module (srfi srfi-64))
 
-(define %store
-  (open-connection-for-tests))
-
 ;; Globally disable grafts because they can trigger early builds.
 (%graft? #f)
 
-(define-syntax-rule (test-assertm name exp)
+(define-syntax-rule (test-assertm name store exp)
   (test-assert name
-    (run-with-store %store exp
+    (run-with-store store exp
                     #:guile-for-build (%guile-for-build))))
 
 (define %gzip-compressor
@@ -51,37 +48,43 @@
 
 (test-begin "pack")
 
-;; FIXME: The following test would rebuild the world (and likely fail) as a
-;; consequence of commit c45477d2a1a651485feede20fe0f3d15aec48b39 (and related
-;; changes) that made guile-sqlite3 a dependency of the derivation.
-;; See <https://bugs.gnu.org/32184>.
-(test-skip 1)
+;; The following test needs guile-sqlite3, libgcrypt, etc. as a consequence of
+;; commit c45477d2a1a651485feede20fe0f3d15aec48b39 and related changes.  Thus,
+;; run it on the user's store, if it's available, on the grounds that these
+;; dependencies may be already there, or we can get substitutes or build them
+;; quite inexpensively; see <https://bugs.gnu.org/32184>.
 
-(test-assertm "self-contained-tarball"
-  (mlet* %store-monad
-      ((profile (profile-derivation (packages->manifest
-                                     (list %bootstrap-guile))
-                                    #:hooks '()
-                                    #:locales? #f))
-       (tarball (self-contained-tarball "pack" profile
-                                        #:symlinks '(("/bin/Guile"
-                                                      -> "bin/guile"))
-                                        #:compressor %gzip-compressor
-                                        #:archiver %tar-bootstrap))
-       (check   (gexp->derivation
-                 "check-tarball"
-                 #~(let ((bin (string-append "." #$profile "/bin")))
-                     (setenv "PATH"
-                             (string-append #$%tar-bootstrap "/bin"))
-                     (system* "tar" "xvf" #$tarball)
-                     (mkdir #$output)
-                     (exit
-                      (and (file-exists? (string-append bin "/guile"))
-                           (string=? (string-append #$%bootstrap-guile "/bin")
-                                     (readlink bin))
-                           (string=? (string-append ".." #$profile
-                                                    "/bin/guile")
-                                     (readlink "bin/Guile"))))))))
-    (built-derivations (list check))))
+(with-external-store store
+  (unless store (tests-skip 1))
+  (test-assertm "self-contained-tarball" store
+    (mlet* %store-monad
+        ((profile (profile-derivation (packages->manifest
+                                       (list %bootstrap-guile))
+                                      #:hooks '()
+                                      #:locales? #f))
+         (tarball (self-contained-tarball "pack" profile
+                                          #:symlinks '(("/bin/Guile"
+                                                        -> "bin/guile"))
+                                          #:compressor %gzip-compressor
+                                          #:archiver %tar-bootstrap))
+         (check   (gexp->derivation
+                   "check-tarball"
+                   #~(let ((bin (string-append "." #$profile "/bin")))
+                       (setenv "PATH"
+                               (string-append #$%tar-bootstrap "/bin"))
+                       (system* "tar" "xvf" #$tarball)
+                       (mkdir #$output)
+                       (exit
+                        (and (file-exists? (string-append bin "/guile"))
+                             (string=? (string-append #$%bootstrap-guile "/bin")
+                                       (readlink bin))
+                             (string=? (string-append ".." #$profile
+                                                      "/bin/guile")
+                                       (readlink "bin/Guile"))))))))
+      (built-derivations (list check)))))
 
 (test-end)
+
+;; Local Variables:
+;; eval: (put 'test-assertm 'scheme-indent-function 2)
+;; End:
diff --git a/tests/pypi.scm b/tests/pypi.scm
index 616ec191f5..6daa44a6e7 100644
--- a/tests/pypi.scm
+++ b/tests/pypi.scm
@@ -81,7 +81,7 @@ baz > 13.37")
    (dummy-package "foo"
                   (source (dummy-origin
                            (uri
-                            "https://pypi.io/packages/source/p/psutil/psutil-4.3.0.tar.gz"))))))
+                            "https://pypi.org/packages/source/p/psutil/psutil-4.3.0.tar.gz"))))))
 
 (test-equal "guix-package->pypi-name, new URL style"
   "certbot"
diff --git a/tests/services.scm b/tests/services.scm
index 1ad577e601..5827dee80d 100644
--- a/tests/services.scm
+++ b/tests/services.scm
@@ -207,13 +207,14 @@
     list))
 
 (test-equal "shepherd-service-upgrade: one unchanged, one upgraded, one new"
-  '(((bar))                                       ;unload
-    ((bar) (baz)))                                ;load
+  '(()                                            ;unload
+    ((foo)))                                      ;restart
   (call-with-values
       (lambda ()
-        ;; Here 'foo' is not upgraded because it is still running, whereas
-        ;; 'bar' is upgraded because it is not currently running.  'baz' is
-        ;; loaded because it's a new service.
+        ;; Here 'foo' is replaced and must be explicitly restarted later
+        ;; because it is still running, whereas 'bar' is upgraded right away
+        ;; because it is not currently running.  'baz' is loaded because it's
+        ;; a new service.
         (shepherd-service-upgrade
          (list (live-service '(foo) '() #t)
                (live-service '(bar) '() #f)
@@ -224,30 +225,31 @@
                                  (start #t))
                (shepherd-service (provision '(baz))
                                  (start #t)))))
-    (lambda (unload load)
+    (lambda (unload restart)
       (list (map live-service-provision unload)
-            (map shepherd-service-provision load)))))
+            (map shepherd-service-provision restart)))))
 
 (test-equal "shepherd-service-upgrade: service depended on is not unloaded"
   '(((baz))                                       ;unload
-    ())                                           ;load
+    ((foo)))                                      ;restart
   (call-with-values
       (lambda ()
         ;; Service 'bar' is not among the target services; yet, it must not be
-        ;; unloaded because 'foo' depends on it.
+        ;; unloaded because 'foo' depends on it.  'foo' gets replaced but it
+        ;; must be restarted manually.
         (shepherd-service-upgrade
          (list (live-service '(foo) '(bar) #t)
                (live-service '(bar) '() #t)       ;still used!
                (live-service '(baz) '() #t))
          (list (shepherd-service (provision '(foo))
                                  (start #t)))))
-    (lambda (unload load)
+    (lambda (unload restart)
       (list (map live-service-provision unload)
-            (map shepherd-service-provision load)))))
+            (map shepherd-service-provision restart)))))
 
 (test-equal "shepherd-service-upgrade: obsolete services that depend on each other"
   '(((foo) (bar) (baz))                           ;unload
-    ((qux)))                                      ;load
+    ())                                           ;restart
   (call-with-values
       (lambda ()
         ;; 'foo', 'bar', and 'baz' depend on each other, but all of them are
@@ -258,9 +260,9 @@
                (live-service '(baz) '() #t))      ;obsolete
          (list (shepherd-service (provision '(qux))
                                  (start #t)))))
-    (lambda (unload load)
+    (lambda (unload restart)
       (list (map live-service-provision unload)
-            (map shepherd-service-provision load)))))
+            (map shepherd-service-provision restart)))))
 
 (test-eq "lookup-service-types"
   system-service-type
diff --git a/tests/status.scm b/tests/status.scm
new file mode 100644
index 0000000000..99abb41c8b
--- /dev/null
+++ b/tests/status.scm
@@ -0,0 +1,183 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2018 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix 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.
+;;;
+;;; GNU Guix 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 GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (test-status)
+  #:use-module (guix status)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-11)
+  #:use-module (srfi srfi-64)
+  #:use-module (rnrs bytevectors)
+  #:use-module (rnrs io ports)
+  #:use-module (ice-9 match))
+
+(test-begin "status")
+
+(test-equal "compute-status, no-op"
+  (build-status)
+  (let-values (((port get-status)
+                (build-event-output-port compute-status)))
+    (display "foo\nbar\n\baz\n" port)
+    (get-status)))
+
+(test-equal "compute-status, builds + substitutes"
+  (list (build-status
+         (building '("foo.drv"))
+         (downloading (list (download "bar" "http://example.org/bar"
+                                      #:size 500
+                                      #:start 'now))))
+        (build-status
+         (building '("foo.drv"))
+         (downloading (list (download "bar" "http://example.org/bar"
+                                      #:size 500
+                                      #:transferred 42
+                                      #:start 'now))))
+        (build-status
+         (builds-completed '("foo.drv"))
+         (downloads-completed (list (download "bar" "http://example.org/bar"
+                                              #:size 500
+                                              #:transferred 500
+                                              #:start 'now
+                                              #:end 'now)))))
+  (let-values (((port get-status)
+                (build-event-output-port (lambda (event status)
+                                           (compute-status event status
+                                                           #:current-time
+                                                           (const 'now))))))
+    (display "@ build-started foo.drv\n" port)
+    (display "@ substituter-started bar\n" port)
+    (display "@ download-started bar http://example.org/bar 500\n" port)
+    (display "various\nthings\nget\nwritten\n" port)
+    (let ((first (get-status)))
+      (display "@ download-progress bar http://example.org/bar 500 42\n"
+               port)
+      (let ((second (get-status)))
+        (display "@ download-progress bar http://example.org/bar 500 84\n"
+                 port)
+        (display "@ build-succeeded foo.drv\n" port)
+        (display "@ download-succeeded bar http://example.org/bar 500\n" port)
+        (display "Almost done!\n" port)
+        (display "@ substituter-succeeded bar\n" port)
+        (list first second (get-status))))))
+
+(test-equal "compute-status, missing events"
+  (list (build-status
+         (building '("foo.drv"))
+         (downloading (list (download "baz" "http://example.org/baz"
+                                      #:size 500
+                                      #:transferred 42
+                                      #:start 'now)
+                            (download "bar" "http://example.org/bar"
+                                      #:size 999
+                                      #:transferred 0
+                                      #:start 'now))))
+        (build-status
+         (builds-completed '("foo.drv"))
+         (downloads-completed (list (download "baz" "http://example.org/baz"
+                                              #:size 500
+                                              #:transferred 500
+                                              #:start 'now
+                                              #:end 'now)
+                                    (download "bar" "http://example.org/bar"
+                                              #:size 999
+                                              #:transferred 999
+                                              #:start 'now
+                                              #:end 'now)))))
+  ;; Below we omit 'substituter-started' events and the like.
+  (let-values (((port get-status)
+                (build-event-output-port (lambda (event status)
+                                           (compute-status event status
+                                                           #:current-time
+                                                           (const 'now))))))
+    (display "@ build-started foo.drv\n" port)
+    (display "@ download-started bar http://example.org/bar 999\n" port)
+    (display "various\nthings\nget\nwritten\n" port)
+    (display "@ download-progress baz http://example.org/baz 500 42\n"
+             port)
+    (let ((first (get-status)))
+      (display "@ build-succeeded foo.drv\n" port)
+      (display "@ download-succeeded bar http://example.org/bar 999\n" port)
+      (display "Almost done!\n" port)
+      (display "@ substituter-succeeded baz\n" port)
+      (list first (get-status)))))
+
+(test-equal "build-output-port, UTF-8"
+  '((build-log #f "lambda is λ!\n"))
+  (let-values (((port get-status) (build-event-output-port cons '()))
+               ((bv)              (string->utf8 "lambda is λ!\n")))
+    (put-bytevector port bv)
+    (force-output port)
+    (get-status)))
+
+(test-equal "current-build-output-port, UTF-8 + garbage"
+  ;; What about a mixture of UTF-8 + garbage?
+  (let ((replacement (cond-expand
+                      ((and guile-2 (not guile-2.2)) "?")
+                      (else "�"))))
+    `((build-log #f ,(string-append "garbage: " replacement "lambda: λ\n"))))
+  (let-values (((port get-status) (build-event-output-port cons '())))
+    (display "garbage: " port)
+    (put-bytevector port #vu8(128))
+    (put-bytevector port (string->utf8 "lambda: λ\n"))
+    (force-output port)
+    (get-status)))
+
+(test-equal "compute-status, multiplexed build output"
+  (list (build-status
+         (building '("foo.drv"))
+         (downloading (list (download "bar" "http://example.org/bar"
+                                      #:size 999
+                                      #:start 'now))))
+        (build-status
+         (building '("foo.drv"))
+         (downloading (list (download "bar" "http://example.org/bar"
+                                      #:size 999
+                                      #:transferred 42
+                                      #:start 'now))))
+        (build-status
+         ;; XXX: Should "bar.drv" be present twice?
+         (builds-completed '("bar.drv" "foo.drv"))
+         (downloads-completed (list (download "bar" "http://example.org/bar"
+                                              #:size 999
+                                              #:transferred 999
+                                              #:start 'now
+                                              #:end 'now)))))
+  (let-values (((port get-status)
+                (build-event-output-port (lambda (event status)
+                                           (compute-status event status
+                                                           #:current-time
+                                                           (const 'now)
+                                                           #:derivation-path->output-path
+                                                           (match-lambda
+                                                             ("bar.drv" "bar")))))))
+    (display "@ build-started foo.drv 121\n" port)
+    (display "@ build-started bar.drv 144\n" port)
+    (display "@ build-log 121 6\nHello!" port)
+    (display "@ build-log 144 50
+@ download-started bar http://example.org/bar 999\n" port)
+    (let ((first (get-status)))
+      (display "@ build-log 121 30\n@ build-started FAKE!.drv 555\n")
+      (display "@ build-log 144 54
+@ download-progress bar http://example.org/bar 999 42\n"
+               port)
+      (let ((second (get-status)))
+        (display "@ download-succeeded bar http://example.org/bar 999\n" port)
+        (display "@ build-succeeded foo.drv\n" port)
+        (display "@ build-succeeded bar.drv\n" port)
+        (list first second (get-status))))))
+
+(test-end "status")
diff --git a/tests/store.scm b/tests/store.scm
index 2858369706..3ff526cdcf 100644
--- a/tests/store.scm
+++ b/tests/store.scm
@@ -31,6 +31,7 @@
   #:use-module (gnu packages)
   #:use-module (gnu packages bootstrap)
   #:use-module (ice-9 match)
+  #:use-module (ice-9 regex)
   #:use-module (rnrs bytevectors)
   #:use-module (rnrs io ports)
   #:use-module (web uri)
@@ -1021,4 +1022,66 @@
                  (call-with-input-file (derivation->output-path drv2)
                    read))))))
 
+(test-equal "multiplexed-build-output"
+  '("Hello from first." "Hello from second.")
+  (with-store store
+    (let* ((build  (add-text-to-store store "build.sh"
+                                      "echo Hello from $NAME.; echo > $out"))
+           (bash   (add-to-store store "bash" #t "sha256"
+                                 (search-bootstrap-binary "bash"
+                                                          (%current-system))))
+           (drv1   (derivation store "one" bash
+                               `("-e" ,build)
+                               #:inputs `((,bash) (,build))
+                               #:env-vars `(("NAME" . "first")
+                                            ("x" . ,(random-text)))))
+           (drv2   (derivation store "two" bash
+                               `("-e" ,build)
+                               #:inputs `((,bash) (,build))
+                               #:env-vars `(("NAME" . "second")
+                                            ("x" . ,(random-text))))))
+      (set-build-options store
+                         #:print-build-trace #t
+                         #:multiplexed-build-output? #t
+                         #:max-build-jobs 10)
+      (let ((port (open-output-string)))
+        ;; Send the build log to PORT.
+        (parameterize ((current-build-output-port port))
+          (build-derivations store (list drv1 drv2)))
+
+        ;; Retrieve the build log; make sure it contains valid "@ build-log"
+        ;; traces that allow us to retrieve each builder's output (we assume
+        ;; there's exactly one "build-output" trace for each builder, which is
+        ;; reasonable.)
+        (let* ((log     (get-output-string port))
+               (started (fold-matches
+                         (make-regexp "@ build-started ([^ ]+) - ([^ ]+) ([^ ]+) ([0-9]+)")
+                         log '() cons))
+               (done    (fold-matches
+                         (make-regexp "@ build-succeeded (.*) - (.*) (.*) (.*)")
+                         log '() cons))
+               (output  (fold-matches
+                         (make-regexp "@ build-log ([[:digit:]]+) ([[:digit:]]+)\n([A-Za-z .*]+)\n")
+                         log '() cons))
+               (drv-pid (lambda (name)
+                          (lambda (m)
+                            (let ((drv (match:substring m 1))
+                                  (pid (string->number
+                                        (match:substring m 4))))
+                              (and (string-suffix? name drv) pid)))))
+               (pid-log (lambda (pid)
+                          (lambda (m)
+                            (let ((n   (string->number
+                                        (match:substring m 1)))
+                                  (len (string->number
+                                        (match:substring m 2)))
+                                  (str (match:substring m 3)))
+                              (and (= pid n)
+                                   (= (string-length str) (- len 1))
+                                   str)))))
+               (pid1    (any (drv-pid "one.drv") started))
+               (pid2    (any (drv-pid "two.drv") started)))
+          (list (any (pid-log pid1) output)
+                (any (pid-log pid2) output)))))))
+
 (test-end "store")