Skip to main content

max / alloy

Open a seeded step on the field the medium could not answer
Author: Max Johnson <me@maxj.phd> · 2026-09-03 17:54 UTC
Signed with PGP, not checked
Commit: b86a468df0b49413230ad442ac8aad2f6e32427b
Parent: f3bb4fb
2 files changed, +62 insertions, -2 deletions
@@ -93,6 +93,10 @@
93 93 "set?) and the status line says which" % step, s)
94 94
95 95 # 2. The account, with the half that can be prefilled already there.
96 + # Focus arrives on the password, not on the username, because the
97 + # username is answered: see InstallView::focus_first_gap. So this
98 + # types the password, tabs once to the confirmation, and submits,
99 + # which also leaves the seeded key field alone.
96 100 screen = s.screen.text()
97 101 check(args.user in screen,
98 102 "the account step does not show the username %r the recipe baked in" % args.user, s)
@@ -102,9 +106,9 @@
102 106 s.send(ENTER)
103 107
104 108 # 3. Encryption. `ALLOY_ENCRYPT=yes` answers the checkbox and never the
105 - # passphrase, so this step is shown with one thing left to type.
109 + # passphrase, so this step is shown with one thing left to type and
110 + # focus already on it, one slot past the checkbox.
106 111 s.wait_for(r"step 4 of 6", 30)
107 - s.send(TAB)
108 112 s.send(args.passphrase)
109 113 s.send(TAB)
110 114 s.send(args.passphrase)
@@ -3510,6 +3510,33 @@
3510 3510 break;
3511 3511 }
3512 3512 }
3513 + self.focus_first_gap();
3514 + }
3515 +
3516 + /// Put focus on the first thing the medium could not answer.
3517 + ///
3518 + /// The two steps that are never skipped are never skipped for the same
3519 + /// reason: they hold a secret. On a fully seeded medium that leaves one
3520 + /// field on each screen, and landing on the field above it means the first
3521 + /// thing typed goes into an answer that was already correct.
3522 + ///
3523 + /// Found by driving the install in a VM rather than by reading this code:
3524 + /// the account step opened on the prefilled username, so a password typed
3525 + /// straight away was appended to the account name and the step failed on a
3526 + /// confirmation that never got filled. See build/vmtest/install_preseeded.py.
3527 + ///
3528 + /// Only on arrival, and only where the value came from the medium. Focus a
3529 + /// user moved is theirs, and stepping back does not come through here.
3530 + fn focus_first_gap(&mut self) {
3531 + match self.step() {
3532 + Step::Account if self.from_medium.username && !self.username.value().is_empty() => {
3533 + self.fields.focus(FIELD_PASSWORD);
3534 + }
3535 + Step::Encryption if self.from_medium.encrypt && self.encrypt => {
3536 + self.crypt.focus(FIELD_PASSPHRASE);
3537 + }
3538 + _ => {}
3539 + }
3513 3540 }
3514 3541
3515 3542 /// Take the medium's answer for one step, and say whether it answered it
@@ -5776,6 +5803,35 @@
5776 5803 assert_eq!(view.answers.disk, None);
5777 5804 }
5778 5805
5806 + /// The one field left to type is the one focus lands on.
5807 + ///
5808 + /// This is the defect the VM scenario found: with the username prefilled and
5809 + /// focus still on it, the first keystroke of a password went into the account
5810 + /// name.
5811 + #[test]
5812 + fn a_seeded_account_step_opens_on_the_password() {
5813 + let view = preseeded(fw12_sheet(), vec![target("nvme0n1", Some("nvme"))]);
5814 + assert_eq!(view.step(), Step::Account);
5815 + assert_eq!(
5816 + view.fields.current(),
5817 + FIELD_PASSWORD,
5818 + "focus is on the field the medium already answered",
5819 + );
5820 + }
5821 +
5822 + /// And the same on the encryption step, whose checkbox the medium answered.
5823 + #[test]
5824 + fn a_seeded_encryption_step_opens_on_the_passphrase() {
5825 + let mut view = preseeded(fw12_sheet(), vec![target("nvme0n1", Some("nvme"))]);
5826 + view.steps = Steps::new(STEPS.len());
5827 + while view.step() != Step::Encryption {
5828 + view.steps.advance();
5829 + }
5830 + view.skip_answered();
5831 + assert_eq!(view.step(), Step::Encryption);
5832 + assert_eq!(view.crypt.current(), FIELD_PASSPHRASE);
5833 + }
5834 +
5779 5835 /// Stepping back into the skipped disk step shows the disk that was chosen.
5780 5836 #[test]
5781 5837 fn the_skipped_disk_step_comes_back_with_its_disk_selected() {