Add a basic usable installer TUI and tests

This commit is contained in:
2026-04-08 13:52:02 +02:00
parent 56925773f4
commit d5311d4844
8 changed files with 685 additions and 60 deletions
+5
View File
@@ -1,6 +1,7 @@
(define-module (fruix installer)
#:use-module (fruix installer state)
#:use-module (fruix installer steps)
#:use-module (fruix installer flow)
#:use-module (fruix installer final)
#:use-module (fruix installer newt)
#:re-export (default-installer-network-mode
@@ -35,6 +36,10 @@
next-installer-step-id
previous-installer-step-id
validate-installer-state-for-step
installer-network-mode-options
installer-target-device-candidates
installer-command-apply
run-installer-command-script
installer-final-summary-text
newt-required-bindings
missing-newt-bindings
+2 -2
View File
@@ -22,5 +22,5 @@
(format #f "current step: ~a\n\n" (installer-state-selected-step state))
layout-summary
"\n\n"
"This prototype only validates the installer state and presents a Newt-based\n"
"review flow. It does not yet invoke the final install action from the TUI.\n")))
"Selecting Install in the TUI hands control back to the Fruix installer\n"
"engine, which then applies the shared storage/install backend.\n")))
+140
View File
@@ -0,0 +1,140 @@
(define-module (fruix installer flow)
#:use-module (fruix installer state)
#:use-module (fruix installer steps)
#:use-module (fruix system storage)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-13)
#:export (installer-network-mode-options
installer-target-device-candidates
installer-command-apply
run-installer-command-script))
(define installer-network-mode-options
'(none dhcp))
(define invalid-target-device-prefixes
'("cd" "md" "pass" "ses" "fd" "ugen" "lo" "pflog" "tap" "tun"))
(define (target-device-name-candidate? name)
(and (non-empty-string? name)
(not (any (lambda (prefix)
(string-prefix? prefix name))
invalid-target-device-prefixes))))
(define (normalize-target-device value)
(cond
((not (non-empty-string? value)) #f)
((string-prefix? "/dev/" value) value)
(else (string-append "/dev/" value))))
(define* (installer-target-device-candidates state #:key kern-disks)
(let* ((explicit-target (normalize-target-device (installer-state-target-device state)))
(discovered (if kern-disks
(string-tokenize kern-disks)
'()))
(devices (append (if explicit-target (list explicit-target) '())
(map (lambda (name)
(string-append "/dev/" name))
(filter target-device-name-candidate? discovered)))))
(delete-duplicates devices string=?)))
(define (step-transition state next-step)
(installer-state-update state #:selected-step next-step))
(define (command-success state result target-devices)
`((state . ,state)
(result . ,result)
(target-devices . ,target-devices)))
(define (update-state-field state field value)
(match field
('storage-layout (installer-state-update state #:storage-layout value))
('target-device (installer-state-update state #:target-device (normalize-target-device value)))
('host-name (installer-state-update state #:host-name value))
('root-size (installer-state-update state #:root-size value))
('disk-capacity (installer-state-update state #:disk-capacity value))
('network-mode (installer-state-update state #:network-mode value))
('selected-step (installer-state-update state #:selected-step value))
('metadata (installer-state-update state #:metadata value))
(_
(error "unsupported installer state field" field))))
(define* (installer-command-apply state command #:key target-devices)
(let ((target-devices (or target-devices
(installer-target-device-candidates state))))
(catch #t
(lambda ()
(match command
(('set field value)
(command-success (update-state-field state field value)
'continue
target-devices))
(('action 'next)
(let* ((current (installer-state-selected-step state))
(_ (validate-installer-state-for-step state current))
(next (next-installer-step-id current)))
(command-success (if next
(step-transition state next)
state)
'continue
target-devices)))
(('action 'back)
(let ((prev (previous-installer-step-id (installer-state-selected-step state))))
(command-success (if prev
(step-transition state prev)
state)
'continue
target-devices)))
(('action 'proceed)
(let* ((current (installer-state-selected-step state))
(_ (unless (memq current '(summary install))
(error "installer proceed is only valid from summary/install" current state)))
(_ (validate-installer-state-for-step state 'install))
(final-state (step-transition state 'install)))
(command-success final-state 'proceed target-devices)))
(('action 'abort)
(command-success state 'abort target-devices))
(('action 'goto step-id)
(unless (find-installer-step step-id)
(error "unknown installer step" step-id))
(command-success (step-transition state step-id)
'continue
target-devices))
(_
(error "unsupported installer command" command))))
(lambda args
`((state . ,state)
(result . error)
(error . ,args)
(target-devices . ,target-devices))))))
(define* (run-installer-command-script state commands #:key target-devices)
(let loop ((state state)
(pending commands)
(transcript '()))
(if (null? pending)
`((state . ,state)
(result . completed)
(transcript . ,(reverse transcript))
(target-devices . ,(or target-devices
(installer-target-device-candidates state))))
(let* ((command (car pending))
(response (installer-command-apply state command
#:target-devices target-devices))
(next-state (assoc-ref response 'state))
(result (assoc-ref response 'result))
(entry `((command . ,command)
(result . ,result)
(selected-step . ,(installer-state-selected-step next-state))
(state . ,(installer-state-spec next-state))
(error . ,(or (assoc-ref response 'error) #f)))))
(if (eq? result 'continue)
(loop next-state
(cdr pending)
(cons entry transcript))
`((state . ,next-state)
(result . ,result)
(transcript . ,(reverse (cons entry transcript)))
(error . ,(or (assoc-ref response 'error) #f))
(target-devices . ,(assoc-ref response 'target-devices))))))))
+409 -36
View File
@@ -1,8 +1,14 @@
(define-module (fruix installer newt)
#:use-module (fruix installer final)
#:use-module (fruix installer flow)
#:use-module (fruix installer state)
#:use-module (fruix installer steps)
#:use-module (fruix system storage)
#:use-module (ice-9 match)
#:use-module (ice-9 popen)
#:use-module (rnrs io ports)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-13)
#:export (newt-required-bindings
missing-newt-bindings
newt-available?
@@ -14,7 +20,25 @@
newt-finish
clear-screen
message-window
choice-window))
choice-window
make-centered-window
pop-window
make-form
add-components-to-form
set-current-component
run-form
destroy-form
make-button
make-label
make-textbox
set-textbox-text
make-entry
entry-value
make-radio-button
current-radio-button
components=?
FLAG-BORDER
FLAG-SCROLL))
(define (newt-interface)
(false-if-exception (resolve-interface '(newt))))
@@ -33,27 +57,353 @@
(define (newt-available?)
(null? (missing-newt-bindings)))
(define (call-newt name . args)
(let ((proc (newt-binding name)))
(unless proc
(define (newt-ref name)
(let ((value (newt-binding name)))
(unless value
(error "missing required Newt binding" name))
value))
(define (call-newt name . args)
(let ((proc (newt-ref name)))
(apply proc args)))
(define (newt-result base-fields mode available? result error missing)
(define (render-error args)
(match args
((_ message . _) (if (string? message)
message
(call-with-output-string
(lambda (port)
(write args port)))))
(_
(call-with-output-string
(lambda (port)
(write args port))))))
(define (state-fields state steps)
`((state . ,(installer-state-spec state))
(step-count . ,(length steps))
(step-ids . ,(map installer-step-id steps))))
(define (newt-result state mode available? result error missing steps)
(append `((ui . newt)
(mode . ,mode)
(available? . ,available?)
(result . ,result)
(error . ,error)
(missing-bindings . ,missing))
base-fields))
(state-fields state steps)))
(define (show-error-page title text)
(call-newt 'message-window title "OK" text)
'ok)
(define (button-x-position count index)
(cond
((= count 1) 30)
((= count 2) (list-ref '(20 42) index))
((= count 3) (list-ref '(8 30 52) index))
(else (+ 4 (* index 14)))))
(define (make-buttons button-specs y)
(let ((count (length button-specs)))
(let loop ((rest button-specs)
(index 0)
(result '()))
(match rest
(() (reverse result))
((spec . tail)
(loop tail
(+ index 1)
(cons (cons (car spec)
(call-newt 'make-button
(button-x-position count index)
y
(cdr spec)))
result)))))))
(define (button-action buttons component)
(let ((match-entry (find (lambda (entry)
(call-newt 'components=? (cdr entry) component))
buttons)))
(and match-entry (car match-entry))))
(define (run-form-page title width height components focus buttons)
(let ((form (call-newt 'make-form)))
(dynamic-wind
(lambda ()
(call-newt 'make-centered-window width height title))
(lambda ()
(apply call-newt (append (list 'add-components-to-form form) components))
(when focus
(call-newt 'set-current-component form focus))
(let loop ()
(call-with-values (lambda ()
(call-newt 'run-form form))
(lambda (reason payload)
(match reason
('exit-component
(or (button-action buttons payload)
(loop)))
(_
(loop)))))))
(lambda ()
(false-if-exception
(call-newt 'destroy-form form))
(false-if-exception
(call-newt 'pop-window))))))
(define* (run-text-page title info-text button-specs
#:key
(width 72)
(height 16)
(textbox-height 8))
(let* ((textbox (call-newt 'make-textbox 2 1 66 textbox-height
(+ (newt-ref 'FLAG-BORDER)
(newt-ref 'FLAG-SCROLL))))
(_ (call-newt 'set-textbox-text textbox info-text))
(buttons (make-buttons button-specs (- height 3))))
(run-form-page title width height
(append (list textbox) (map cdr buttons))
(cdr (car buttons))
buttons)))
(define* (run-entry-page title info-text field-label initial-value button-specs
#:key
(width 72)
(height 16))
(let* ((textbox (call-newt 'make-textbox 2 1 66 4
(+ (newt-ref 'FLAG-BORDER)
(newt-ref 'FLAG-SCROLL))))
(_ (call-newt 'set-textbox-text textbox info-text))
(label (call-newt 'make-label 2 7 field-label))
(entry (call-newt 'make-entry 2 8 66 #:initial-value (or initial-value "")))
(buttons (make-buttons button-specs (- height 3)))
(action (run-form-page title width height
(append (list textbox label entry) (map cdr buttons))
entry
buttons)))
`((action . ,action)
(value . ,(call-newt 'entry-value entry)))))
(define* (run-radio-page title info-text items current-value button-specs
#:key
(width 72)
(height 18))
(if (null? items)
'((action . abort) (value . #f))
(let* ((textbox (call-newt 'make-textbox 2 1 66 4
(+ (newt-ref 'FLAG-BORDER)
(newt-ref 'FLAG-SCROLL))))
(_ (call-newt 'set-textbox-text textbox info-text))
(radios
(let loop ((rest items)
(index 0)
(previous #f)
(result '()))
(match rest
(() (reverse result))
(((value . text) . tail)
(let* ((radio (if previous
(call-newt 'make-radio-button 4 (+ 6 index) text
(and current-value (equal? value current-value))
previous)
(call-newt 'make-radio-button 4 (+ 6 index) text
(and current-value (equal? value current-value))))))
(loop tail
(+ index 1)
radio
(cons (cons value radio) result)))))))
(focus (cdar radios))
(buttons (make-buttons button-specs (- height 3)))
(action (run-form-page title width height
(append (list textbox)
(map cdr radios)
(map cdr buttons))
focus
buttons))
(selected-radio (call-newt 'current-radio-button focus))
(selected-entry (find (lambda (entry)
(call-newt 'components=? (cdr entry) selected-radio))
radios)))
`((action . ,action)
(value . ,(and selected-entry (car selected-entry)))))))
(define (apply-installer-commands state commands target-devices)
(let loop ((current-state state)
(pending commands))
(if (null? pending)
`((state . ,current-state)
(result . continue)
(target-devices . ,target-devices))
(let* ((response (installer-command-apply current-state
(car pending)
#:target-devices target-devices))
(result (assoc-ref response 'result))
(next-state (assoc-ref response 'state)))
(if (and (eq? result 'continue)
(pair? (cdr pending)))
(loop next-state (cdr pending))
response)))))
(define (handle-page-response state commands target-devices)
(let ((response (apply-installer-commands state commands target-devices)))
(if (eq? (assoc-ref response 'result) 'error)
(begin
(show-error-page "Installer validation error"
(render-error (assoc-ref response 'error)))
`((state . ,(assoc-ref response 'state))
(result . retry)))
response)))
(define (welcome-text)
(string-append
"Welcome to the Fruix installer.\n\n"
"This first interactive installer guides you through target disk selection,\n"
"review of the default GPT + EFI + UFS storage layout, hostname setup,\n"
"and a minimal network policy before handing control back to the installer\n"
"engine to apply the plan."))
(define (storage-layout-text state)
(let ((layout (installer-state-effective-storage-layout state)))
(string-append
"The default Fruix v1 layout uses a single GPT disk with an EFI system\n"
"partition and a UFS root filesystem.\n\n"
(if layout
(render-storage-layout-summary layout)
"storage-layout <unset>"))))
(define (network-mode-text mode)
(case mode
((dhcp) "Use DHCP when networking is needed during installation")
(else "Do not make installer networking changes")))
(define (run-welcome-page state)
(let ((action (run-text-page "Fruix installer" (welcome-text)
'((next . "Continue")
(abort . "Abort"))
#:height 15
#:textbox-height 7)))
(case action
((next) (handle-page-response state '((action next)) '()))
(else `((state . ,state) (result . abort))))))
(define (read-kern-disks)
(false-if-exception
(let ((port (open-pipe* OPEN_READ "sysctl" "-n" "kern.disks")))
(let ((text (get-string-all port)))
(close-pipe port)
text))))
(define (run-target-disk-page state)
(let* ((target-devices (installer-target-device-candidates state #:kern-disks (or (read-kern-disks) "")))
(items (map (lambda (device)
(cons device device))
target-devices)))
(if (null? items)
(begin
(show-error-page "No install target disks"
"No suitable target disks were discovered. Re-run the installer with --install-target-device /dev/NAME or boot inside the installer environment.")
`((state . ,state) (result . abort)))
(let* ((response (run-radio-page "Target disk"
"Choose the disk that Fruix should overwrite and install to. The selected disk will be repartitioned."
items
(or (installer-state-target-device state)
(caar items))
'((back . "Back")
(next . "Next")
(abort . "Abort"))))
(action (assoc-ref response 'action))
(value (assoc-ref response 'value)))
(case action
((back) (handle-page-response state '((action back)) target-devices))
((next) (handle-page-response state
`((set target-device ,value)
(action next))
target-devices))
(else `((state . ,state) (result . abort))))))))
(define (run-storage-layout-page state)
(let ((action (run-text-page "Storage layout"
(storage-layout-text state)
'((back . "Back")
(next . "Next")
(abort . "Abort"))
#:height 18
#:textbox-height 11)))
(case action
((back) (handle-page-response state '((action back)) '()))
((next) (handle-page-response state '((action next)) '()))
(else `((state . ,state) (result . abort))))))
(define (run-hostname-page state)
(let* ((response (run-entry-page "Hostname"
"Choose the hostname that the installed Fruix system should use on first boot."
"Host name"
(installer-state-host-name state)
'((back . "Back")
(next . "Next")
(abort . "Abort"))))
(action (assoc-ref response 'action))
(value (string-trim-both (assoc-ref response 'value))))
(case action
((back) (handle-page-response state '((action back)) '()))
((next) (handle-page-response state
`((set host-name ,value)
(action next))
'()))
(else `((state . ,state) (result . abort))))))
(define (run-network-page state)
(let* ((items '((none . "No installer networking changes")
(dhcp . "Use wired DHCP when networking is needed")))
(response (run-radio-page "Network"
"Choose the simple installer networking policy. Fruix keeps the first interactive installer deliberately small and wired-first."
items
(installer-state-network-mode state)
'((back . "Back")
(next . "Next")
(abort . "Abort"))))
(action (assoc-ref response 'action))
(value (assoc-ref response 'value)))
(case action
((back) (handle-page-response state '((action back)) '()))
((next) (handle-page-response state
`((set network-mode ,value)
(action next))
'()))
(else `((state . ,state) (result . abort))))))
(define (run-summary-page state)
(let* ((summary-text
(string-append
(installer-final-summary-text state)
"\n"
(format #f "network policy: ~a\n" (network-mode-text (installer-state-network-mode state)))
"\nSelect Install to hand control back to the Fruix installer engine.\n"
"The engine will apply the shared storage/install backend outside this UI."))
(action (run-text-page "Review and install"
summary-text
'((back . "Back")
(proceed . "Install")
(abort . "Abort"))
#:height 20
#:textbox-height 13)))
(case action
((back) (handle-page-response state '((action back)) '()))
((proceed) (handle-page-response state '((action proceed)) '()))
(else `((state . ,state) (result . abort))))))
(define* (run-newt-self-test #:key (steps %default-installer-steps))
(let* ((missing (missing-newt-bindings))
(base-fields `((step-count . ,(length steps))
(step-ids . ,(map installer-step-id steps)))))
(let ((missing (missing-newt-bindings)))
(if (pair? missing)
(newt-result base-fields 'self-test #f 'unavailable #f missing)
`((ui . newt)
(mode . self-test)
(available? . #f)
(result . unavailable)
(error . #f)
(missing-bindings . ,missing)
(step-count . ,(length steps))
(step-ids . ,(map installer-step-id steps)))
(catch #t
(lambda ()
(dynamic-wind
@@ -61,20 +411,31 @@
(call-newt 'newt-init)
(call-newt 'clear-screen))
(lambda ()
(newt-result base-fields 'self-test #t 'self-test-ok #f '()))
`((ui . newt)
(mode . self-test)
(available? . #t)
(result . self-test-ok)
(error . #f)
(missing-bindings . ())
(step-count . ,(length steps))
(step-ids . ,(map installer-step-id steps))))
(lambda ()
(false-if-exception
(call-newt 'newt-finish)))))
(lambda args
(newt-result base-fields 'self-test #t 'error args '()))))))
`((ui . newt)
(mode . self-test)
(available? . #t)
(result . error)
(error . ,args)
(missing-bindings . ())
(step-count . ,(length steps))
(step-ids . ,(map installer-step-id steps))))))))
(define* (run-newt-installer state #:key (steps %default-installer-steps))
(let* ((missing (missing-newt-bindings))
(base-fields `((state . ,(installer-state-spec state))
(step-count . ,(length steps))
(step-ids . ,(map installer-step-id steps)))))
(let ((missing (missing-newt-bindings)))
(if (pair? missing)
(newt-result base-fields 'interactive #f 'unavailable #f missing)
(newt-result state 'interactive #f 'unavailable #f missing steps)
(catch #t
(lambda ()
(dynamic-wind
@@ -82,26 +443,38 @@
(call-newt 'newt-init)
(call-newt 'clear-screen))
(lambda ()
(call-newt 'message-window
"Fruix installer"
"Continue"
(installer-state-summary state))
(let ((result (call-newt 'choice-window
"Fruix installer review"
"Proceed"
"Abort"
(installer-final-summary-text state))))
(newt-result base-fields
'interactive
#t
(match result
(1 'proceed)
(2 'abort)
(_ 'unknown))
#f
'())))
(let loop ((current-state state))
(let* ((step-id (installer-state-selected-step current-state))
(response (case step-id
((welcome) (run-welcome-page current-state))
((target-disk) (run-target-disk-page current-state))
((storage-layout) (run-storage-layout-page current-state))
((hostname) (run-hostname-page current-state))
((network) (run-network-page current-state))
((summary install complete) (run-summary-page current-state))
(else
`((state . ,current-state)
(result . error)
(error . (unknown-step ,step-id))))))
(result (assoc-ref response 'result))
(next-state (assoc-ref response 'state)))
(case result
((continue retry)
(loop next-state))
((proceed abort)
(newt-result next-state 'interactive #t result #f '() steps))
((error)
(newt-result next-state 'interactive #t 'error
(assoc-ref response 'error)
'()
steps))
(else
(newt-result next-state 'interactive #t 'error
`(unexpected-result ,result)
'()
steps))))))
(lambda ()
(false-if-exception
(call-newt 'newt-finish)))))
(lambda args
(newt-result base-fields 'interactive #t 'error args '()))))))
(newt-result state 'interactive #t 'error args '() steps))))))
+2
View File
@@ -497,6 +497,8 @@
. "share/fruix/node/modules/fruix/installer.scm")
(,(string-append repo-root "/modules/fruix/installer/final.scm")
. "share/fruix/node/modules/fruix/installer/final.scm")
(,(string-append repo-root "/modules/fruix/installer/flow.scm")
. "share/fruix/node/modules/fruix/installer/flow.scm")
(,(string-append repo-root "/modules/fruix/installer/newt.scm")
. "share/fruix/node/modules/fruix/installer/newt.scm")
(,(string-append repo-root "/modules/fruix/installer/state.scm")
+29 -22
View File
@@ -627,28 +627,35 @@ Common options:\n\
(installer_store_item_count . ,(length installer-store-items))))))
(define (emit-installer-tui-metadata os-file resolved-symbol store-dir state result)
(emit-metadata
`((action . "installer-tui")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(newt_mode . ,(assoc-ref result 'mode))
(newt_available . ,(assoc-ref result 'available?))
(newt_result . ,(assoc-ref result 'result))
(newt_error . ,(or (assoc-ref result 'error) #f))
(missing_bindings . ,(assoc-ref result 'missing-bindings))
(step_count . ,(assoc-ref result 'step-count))
(step_ids . ,(assoc-ref result 'step-ids))
(selected_step . ,(installer-state-selected-step state))
(host_name . ,(installer-state-host-name state))
(target_device . ,(installer-state-target-device state))
(root_size . ,(installer-state-root-size state))
(disk_capacity . ,(installer-state-disk-capacity state))
(network_mode . ,(installer-state-network-mode state))
(storage_layout . ,(and (installer-state-storage-layout state)
(storage-layout-spec (installer-state-storage-layout state))))
(effective_storage_layout . ,(and (installer-state-effective-storage-layout state)
(storage-layout-spec (installer-state-effective-storage-layout state)))))))
(let* ((state-spec (or (assoc-ref result 'state)
(installer-state-spec state)))
(state-value (lambda (key fallback)
(let ((entry (assoc key state-spec)))
(if entry (cdr entry) fallback)))))
(emit-metadata
`((action . "installer-tui")
(os_file . ,os-file)
(system_variable . ,resolved-symbol)
(store_dir . ,store-dir)
(newt_mode . ,(assoc-ref result 'mode))
(newt_available . ,(assoc-ref result 'available?))
(newt_result . ,(assoc-ref result 'result))
(newt_error . ,(or (assoc-ref result 'error) #f))
(missing_bindings . ,(assoc-ref result 'missing-bindings))
(step_count . ,(assoc-ref result 'step-count))
(step_ids . ,(assoc-ref result 'step-ids))
(selected_step . ,(state-value 'selected-step (installer-state-selected-step state)))
(host_name . ,(state-value 'host-name (installer-state-host-name state)))
(target_device . ,(state-value 'target-device (installer-state-target-device state)))
(root_size . ,(state-value 'root-size (installer-state-root-size state)))
(disk_capacity . ,(state-value 'disk-capacity (installer-state-disk-capacity state)))
(network_mode . ,(state-value 'network-mode (installer-state-network-mode state)))
(storage_layout . ,(state-value 'storage-layout
(and (installer-state-storage-layout state)
(storage-layout-spec (installer-state-storage-layout state)))))
(effective_storage_layout . ,(state-value 'effective-storage-layout
(and (installer-state-effective-storage-layout state)
(storage-layout-spec (installer-state-effective-storage-layout state)))))))))
(define (emit-native-build-promotion-metadata store-dir result-root result)
(emit-metadata
+83
View File
@@ -0,0 +1,83 @@
(use-modules (srfi srfi-64)
(srfi srfi-13)
(fruix installer)
(fruix system freebsd))
(define (make-test-state)
(installer-state
#:operating-system #f
#:storage-layout (default-freebsd-efi-ufs-storage-layout #:device #f)
#:target-device #f
#:host-name #f
#:root-size #f
#:disk-capacity #f
#:network-mode default-installer-network-mode
#:selected-step 'welcome
#:metadata '()))
(define (response-state response)
(assoc-ref response 'state))
(define (response-result response)
(assoc-ref response 'result))
(test-begin "installer-basic")
(let ((candidates (installer-target-device-candidates
(installer-state #:storage-layout (default-freebsd-efi-ufs-storage-layout #:device #f)
#:target-device "/dev/ada0"
#:host-name "seed"
#:selected-step 'target-disk)
#:kern-disks "cd0 ada1 ada0 md0 pass0 nvd0")))
(test-equal "target-device candidate filtering"
'("/dev/ada0" "/dev/ada1" "/dev/nvd0")
candidates))
(let* ((response (run-installer-command-script
(make-test-state)
'((action next)
(set target-device "/dev/ada0")
(action next)
(action next)
(set host-name "apollo")
(action next)
(set network-mode dhcp)
(action next)
(action proceed))
#:target-devices '("/dev/ada0" "/dev/ada1")))
(state (response-state response)))
(test-equal "happy-path script result" 'proceed (response-result response))
(test-equal "happy-path final step" 'install (installer-state-selected-step state))
(test-equal "happy-path target device" "/dev/ada0" (installer-state-target-device state))
(test-equal "happy-path host name" "apollo" (installer-state-host-name state))
(test-equal "happy-path network mode" 'dhcp (installer-state-network-mode state))
(test-assert "happy-path ready for install"
(installer-state-ready-for-install? state))
(test-assert "happy-path review text mentions host"
(string-contains (installer-final-summary-text state) "host-name: apollo")))
(let* ((response (run-installer-command-script
(make-test-state)
'((action next)
(action next))
#:target-devices '("/dev/ada0")))
(state (response-state response)))
(test-equal "missing target device yields error" 'error (response-result response))
(test-equal "missing target device stays on target step" 'target-disk (installer-state-selected-step state)))
(let* ((response (run-installer-command-script
(make-test-state)
'((action next)
(set target-device "/dev/ada0")
(action next)
(action back))
#:target-devices '("/dev/ada0")))
(state (response-state response)))
(test-equal "back navigation returns to target-disk" 'target-disk (installer-state-selected-step state)))
(let* ((response (installer-command-apply (make-test-state)
'(action proceed)
#:target-devices '("/dev/ada0"))))
(test-equal "proceed only valid from summary/install" 'error (response-result response)))
(test-end "installer-basic")
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
set -eu
repo_root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
. "$HOME/.local/opt/fruix-builder/env.sh"
guile_version=$($GUILE_BIN -c '(display (effective-version))')
guile_load_path="$repo_root/modules:$GUIX_SOURCE_DIR:$HOME/.local/opt/fruix-builder/shepherd/share/guile/site/$guile_version${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}"
guile_load_compiled_path="$HOME/.local/opt/fruix-builder/shepherd/lib/guile/$guile_version/site-ccache${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}"
env \
GUILE_AUTO_COMPILE=0 \
GUILE_LOAD_PATH="$guile_load_path" \
GUILE_LOAD_COMPILED_PATH="$guile_load_compiled_path" \
"$GUILE_BIN" --no-auto-compile "$repo_root/tests/installer-basic.scm"