From 056d0b40341a1b28516a6c2e0fed820360be42b5 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 17 Nov 2016 23:14:58 +0100 Subject: tests: basic: Fix harmless thinko. * gnu/tests/base.scm (run-basic-test)["login on tty1"]: Move the file-waiting loop inside the 'marionette-eval' body. Before that, we were waiting for the file to appear on the host, which would never happen, meaning that we were always waiting for 15 seconds. --- gnu/tests/base.scm | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'gnu/tests') diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm index 5c2af5b6d4..9a265309c0 100644 --- a/gnu/tests/base.scm +++ b/gnu/tests/base.scm @@ -150,14 +150,20 @@ info --version") (marionette-type "root\n\nid -un > logged-in\n" marionette) ;; It can take a while before the shell commands are executed. - (let loop ((i 0)) - (unless (or (file-exists? "/root/logged-in") (> i 15)) - (sleep 1) - (loop (+ i 1)))) (marionette-eval '(use-modules (rnrs io ports)) marionette) - (marionette-eval '(call-with-input-file "/root/logged-in" - get-string-all) - marionette))) + (marionette-eval + '(let loop ((i 0)) + (catch 'system-error + (lambda () + (call-with-input-file "/root/logged-in" + get-string-all)) + (lambda args + (if (and (< i 15) (= ENOENT (system-error-errno args))) + (begin + (sleep 1) + (loop (+ i 1))) + (apply throw args))))) + marionette))) (test-assert "host name resolution" (match (marionette-eval -- cgit v1.2.3 From fe933833504c90eb40b0d2c71847675b31c142b4 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 23 Nov 2016 13:56:42 +0100 Subject: marionette: Add 'marionette-screen-text' using OCR. * gnu/build/marionette.scm (marionette-screen-text): New procedure. * gnu/tests/base.scm (run-basic-test)["screen text"]: New test. --- gnu/build/marionette.scm | 33 +++++++++++++++++++++++++++++++++ gnu/tests/base.scm | 16 ++++++++++++++++ 2 files changed, 49 insertions(+) (limited to 'gnu/tests') diff --git a/gnu/build/marionette.scm b/gnu/build/marionette.scm index 70b737fc57..8070b6b439 100644 --- a/gnu/build/marionette.scm +++ b/gnu/build/marionette.scm @@ -21,10 +21,12 @@ #:use-module (srfi srfi-26) #:use-module (rnrs io ports) #:use-module (ice-9 match) + #:use-module (ice-9 popen) #:export (marionette? make-marionette marionette-eval marionette-control + marionette-screen-text %qwerty-us-keystrokes marionette-type)) @@ -171,6 +173,37 @@ pcsys_monitor\")." (newline monitor) (wait-for-monitor-prompt monitor)))) +(define* (marionette-screen-text marionette + #:key + (ocrad "ocrad")) + "Take a screenshot of MARIONETTE, perform optical character +recognition (OCR), and return the text read from the screen as a string. Do +this by invoking OCRAD (file name for GNU Ocrad's command)" + (define (random-file-name) + (string-append "/tmp/marionette-screenshot-" + (number->string (random (expt 2 32)) 16) + ".ppm")) + + (let ((image (random-file-name))) + (dynamic-wind + (const #t) + (lambda () + (marionette-control (string-append "screendump " image) + marionette) + + ;; Tell Ocrad to invert the image colors (make it black on white) and + ;; to scale the image up, which significantly improves the quality of + ;; the result. In spite of this, be aware that OCR confuses "y" and + ;; "V" and sometimes erroneously introduces white space. + (let* ((pipe (open-pipe* OPEN_READ ocrad + "-i" "-s" "10" image)) + (text (get-string-all pipe))) + (unless (zero? (close-pipe pipe)) + (error "'ocrad' failed" ocrad)) + text)) + (lambda () + (false-if-exception (delete-file image)))))) + (define %qwerty-us-keystrokes ;; Maps "special" characters to their keystrokes. '((#\newline . "ret") diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm index 9a265309c0..3be1c55b41 100644 --- a/gnu/tests/base.scm +++ b/gnu/tests/base.scm @@ -31,6 +31,8 @@ #:use-module (gnu services mcron) #:use-module (gnu services shepherd) #:use-module (gnu services networking) + #:use-module (gnu packages imagemagick) + #:use-module (gnu packages ocr) #:use-module (guix gexp) #:use-module (guix store) #:use-module (guix monads) @@ -241,6 +243,20 @@ info --version") marionette) (file-exists? "tty1.ppm"))) + (test-assert "screen text" + (let ((text (marionette-screen-text marionette + #:ocrad + #$(file-append ocrad + "/bin/ocrad")))) + ;; Check whether the welcome message and shell prompt are + ;; displayed. Note: OCR confuses "y" and "V" for instance, so + ;; we cannot reliably match the whole text. + (and (string-contains text "This is the GNU") + (string-contains text + (string-append + "root@" + #$(operating-system-host-name os)))))) + (test-end) (exit (= (test-runner-fail-count (test-runner-current)) 0))))) -- cgit v1.2.3 From f7f292d359e0eb77617f4ecf6b3164f868ec1784 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 23 Nov 2016 20:59:13 +0100 Subject: install: Enable "cryptodisk" handling in GRUB. This allows 'grub-install' to do the right thing when / or /boot is a LUKS-encrypted partition. Fixes . * gnu/build/install.scm (install-grub): Add 'setenv' to set 'GRUB_ENABLE_CRYPTODISK'. (wait-for-screen-text): New test. * gnu/tests/base.scm (run-basic-test): Add #:initialization parameter and honor it. * gnu/tests/install.scm (%encrypted-root-os)[kernel-arguments]: Remove. (%encrypted-root-installation-script): Pass '--uuid' to 'cryptsetup luksFormat'. Remove 'sed' invocation. (enter-luks-passphrase): New procedure. (%test-encrypted-os)[value]: Pass #:initialization to 'run-basic-test'. --- gnu/build/install.scm | 5 ++++ gnu/build/marionette.scm | 19 ++++++++++++++ gnu/tests/base.scm | 13 ++++++++-- gnu/tests/install.scm | 66 ++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 90 insertions(+), 13 deletions(-) (limited to 'gnu/tests') diff --git a/gnu/build/install.scm b/gnu/build/install.scm index 3d1594e203..5c2b35632d 100644 --- a/gnu/build/install.scm +++ b/gnu/build/install.scm @@ -46,6 +46,11 @@ Note that the caller must make sure that GRUB.CFG is registered as a GC root so that the fonts, background images, etc. referred to by GRUB.CFG are not GC'd." (install-grub-config grub.cfg mount-point) + + ;; Tell 'grub-install' that there might be a LUKS-encrypted /boot or root + ;; partition. + (setenv "GRUB_ENABLE_CRYPTODISK" "y") + (unless (zero? (system* "grub-install" "--no-floppy" "--boot-directory" (string-append mount-point "/boot") diff --git a/gnu/build/marionette.scm b/gnu/build/marionette.scm index 8070b6b439..506d6da420 100644 --- a/gnu/build/marionette.scm +++ b/gnu/build/marionette.scm @@ -27,6 +27,7 @@ marionette-eval marionette-control marionette-screen-text + wait-for-screen-text %qwerty-us-keystrokes marionette-type)) @@ -204,6 +205,24 @@ this by invoking OCRAD (file name for GNU Ocrad's command)" (lambda () (false-if-exception (delete-file image)))))) +(define* (wait-for-screen-text marionette predicate + #:key (timeout 30) (ocrad "ocrad")) + "Wait for TIMEOUT seconds or until the screen text on MARIONETTE matches +PREDICATE, whichever comes first. Raise an error when TIMEOUT is exceeded." + (define start + (car (gettimeofday))) + + (define end + (+ start timeout)) + + (let loop () + (if (> (car (gettimeofday)) end) + (error "'wait-for-screen-text' timeout" predicate) + (or (predicate (marionette-screen-text marionette #:ocrad ocrad)) + (begin + (sleep 1) + (loop)))))) + (define %qwerty-us-keystrokes ;; Maps "special" characters to their keystrokes. '((#\newline . "ret") diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm index 3be1c55b41..86242d9665 100644 --- a/gnu/tests/base.scm +++ b/gnu/tests/base.scm @@ -67,10 +67,16 @@ %base-user-accounts)))) -(define* (run-basic-test os command #:optional (name "basic")) +(define* (run-basic-test os command #:optional (name "basic") + #:key initialization) "Return a derivation called NAME that tests basic features of the OS started using COMMAND, a gexp that evaluates to a list of strings. Compare some -properties of running system to what's declared in OS, an ." +properties of running system to what's declared in OS, an . + +When INITIALIZATION is true, it must be a one-argument procedure that is +passed a gexp denoting the marionette, and it must return gexp that is +inserted before the first test. This is used to introduce an extra +initialization step, such as entering a LUKS passphrase." (define test (with-imported-modules '((gnu build marionette)) #~(begin @@ -88,6 +94,9 @@ properties of running system to what's declared in OS, an ." (test-begin "basic") + #$(and initialization + (initialization #~marionette)) + (test-assert "uname" (match (marionette-eval '(uname) marionette) (#("Linux" host-name version _ architecture) diff --git a/gnu/tests/install.scm b/gnu/tests/install.scm index 98f8649af8..ecf1ac1923 100644 --- a/gnu/tests/install.scm +++ b/gnu/tests/install.scm @@ -24,6 +24,7 @@ #:use-module (gnu system install) #:use-module (gnu system vm) #:use-module ((gnu build vm) #:select (qemu-command)) + #:use-module (gnu packages ocr) #:use-module (gnu packages qemu) #:use-module (gnu packages package-management) #:use-module (guix store) @@ -398,17 +399,20 @@ by 'mdadm'.") (locale "en_US.UTF-8") (bootloader (grub-configuration (device "/dev/vdb"))) - (kernel-arguments '("console=ttyS0")) + + ;; Note: Do not pass "console=ttyS0" so we can use our passphrase prompt + ;; detection logic in 'enter-luks-passphrase'. + + (mapped-devices (list (mapped-device + (source (uuid "12345678-1234-1234-1234-123456789abc")) + (target "the-root-device") + (type luks-device-mapping)))) (file-systems (cons (file-system (device "/dev/mapper/the-root-device") (title 'device) (mount-point "/") (type "ext4")) %base-file-systems)) - (mapped-devices (list (mapped-device - (source "REPLACE-WITH-LUKS-UUID") - (target "the-root-device") - (type luks-device-mapping)))) (users (cons (user-account (name "charlie") (group "users") @@ -435,7 +439,8 @@ parted --script /dev/vdb mklabel gpt \\ mkpart primary ext2 3M 1G \\ set 1 boot on \\ set 1 bios_grub on -echo -n thepassphrase | cryptsetup luksFormat -q /dev/vdb2 - +echo -n thepassphrase | \\ + cryptsetup luksFormat --uuid=12345678-1234-1234-1234-123456789abc -q /dev/vdb2 - echo -n thepassphrase | \\ cryptsetup open --type luks --key-file - /dev/vdb2 the-root-device mkfs.ext4 -L my-root /dev/mapper/the-root-device @@ -443,15 +448,53 @@ mount LABEL=my-root /mnt herd start cow-store /mnt mkdir /mnt/etc cp /etc/target-config.scm /mnt/etc/config.scm -cat /mnt/etc/config -luks_uuid=`cryptsetup luksUUID /dev/vdb2` -sed -i /mnt/etc/config.scm \\ - -e \"s/\\\"REPLACE-WITH-LUKS-UUID\\\"/(uuid \\\"$luks_uuid\\\")/g\" guix system build /mnt/etc/config.scm guix system init /mnt/etc/config.scm /mnt --no-substitutes sync reboot\n") +(define (enter-luks-passphrase marionette) + "Return a gexp to be inserted in the basic system test running on MARIONETTE +to enter the LUKS passphrase." + (let ((ocrad (file-append ocrad "/bin/ocrad"))) + #~(begin + (define (passphrase-prompt? text) + (string-contains (pk 'screen-text text) "Enter pass")) + + (define (bios-boot-screen? text) + ;; Return true if TEXT corresponds to the boot screen, before GRUB's + ;; menu. + (string-prefix? "SeaBIOS" text)) + + (test-assert "enter LUKS passphrase for GRUB" + (begin + ;; At this point we have no choice but to use OCR to determine + ;; when the passphrase should be entered. + (wait-for-screen-text #$marionette passphrase-prompt? + #:ocrad #$ocrad) + (marionette-type "thepassphrase\n" #$marionette) + + ;; Now wait until we leave the boot screen. This is necessary so + ;; we can then be sure we match the "Enter passphrase" prompt from + ;; 'cryptsetup', in the initrd. + (wait-for-screen-text #$marionette (negate bios-boot-screen?) + #:ocrad #$ocrad + #:timeout 20))) + + (test-assert "enter LUKS passphrase for the initrd" + (begin + ;; XXX: Here we use OCR as well but we could instead use QEMU + ;; '-serial stdio' and run it in an input pipe, + (wait-for-screen-text #$marionette passphrase-prompt? + #:ocrad #$ocrad + #:timeout 60) + (marionette-type "thepassphrase\n" #$marionette) + + ;; Take a screenshot for debugging purposes. + (marionette-control (string-append "screendump " #$output + "/post-initrd-passphrase.ppm") + #$marionette)))))) + (define %test-encrypted-os (system-test (name "encrypted-root-os") @@ -465,6 +508,7 @@ build (current-guix) and then store a couple of full system images.") #:script %encrypted-root-installation-script)) (command (qemu-command/writable-image image))) - (run-basic-test %encrypted-root-os command "encrypted-root-os"))))) + (run-basic-test %encrypted-root-os command "encrypted-root-os" + #:initialization enter-luks-passphrase))))) ;;; install.scm ends here -- cgit v1.2.3 From cc73339b97901cf1e50f07a6f0114974b34592c4 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 23 Nov 2016 21:10:49 +0100 Subject: tests: Remove 'GUIX_LOCPATH' hack that had been added for glibc@2.23. * gnu/tests/base.scm (run-basic-test)[test]("locale"): Remove 'GUIX_LOCPATH' hack, which is no longer needed since commit 9f58fe3d1c32e3f0ced065e286532a10cad1b5e3. --- gnu/tests/base.scm | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'gnu/tests') diff --git a/gnu/tests/base.scm b/gnu/tests/base.scm index 86242d9665..6370d6951b 100644 --- a/gnu/tests/base.scm +++ b/gnu/tests/base.scm @@ -199,14 +199,8 @@ info --version") (test-equal "locale" "en_US.utf8" - (marionette-eval '(begin - ;; XXX: This 'setenv' call wouldn't be needed - ;; but our glibc@2.23 currently ignores - ;; /run/current-system/locale. - (setenv "GUIX_LOCPATH" - "/run/current-system/locale") - (let ((before (setlocale LC_ALL "en_US.utf8"))) - (setlocale LC_ALL before))) + (marionette-eval '(let ((before (setlocale LC_ALL "en_US.utf8"))) + (setlocale LC_ALL before)) marionette)) (test-assert "/run/current-system is a GC root" -- cgit v1.2.3