diff --git a/internal/prompter/accessible_prompter_test.go b/internal/prompter/accessible_prompter_test.go index b67156bdf..ee6eba3a9 100644 --- a/internal/prompter/accessible_prompter_test.go +++ b/internal/prompter/accessible_prompter_test.go @@ -1,10 +1,11 @@ -//go:build !windows +//go:build linux || darwin package prompter_test import ( "fmt" "io" + "os" "slices" "strings" "testing" @@ -17,6 +18,7 @@ import ( "github.com/hinshun/vt10x" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" ) // The following tests are broadly testing the accessible prompter, and NOT asserting @@ -34,8 +36,6 @@ import ( // but doesn't mandate that prompts always look exactly the same. func TestAccessiblePrompter(t *testing.T) { - beforePasswordSendTimeout := 100 * time.Millisecond - t.Run("Select", func(t *testing.T) { console := newTestVirtualTerminal(t) p := newTestAccessiblePrompter(t, console) @@ -505,8 +505,8 @@ func TestAccessiblePrompter(t *testing.T) { _, err := console.ExpectString("Enter password") require.NoError(t, err) - // Wait to ensure huh has time to set the echo mode - time.Sleep(beforePasswordSendTimeout) + // Wait until huh has disabled echo mode on the TTY + require.NoError(t, waitForEchoDisabled(console.Tty(), 5*time.Second)) // Enter a number _, err = console.SendLine(dummyPassword) @@ -596,8 +596,8 @@ func TestAccessiblePrompter(t *testing.T) { _, err := console.ExpectString("Paste your authentication token:") require.NoError(t, err) - // Wait to ensure huh has time to set the echo mode - time.Sleep(beforePasswordSendTimeout) + // Wait until huh has disabled echo mode on the TTY + require.NoError(t, waitForEchoDisabled(console.Tty(), 5*time.Second)) // Enter some dummy auth token _, err = console.SendLine(dummyAuthToken) @@ -641,8 +641,8 @@ func TestAccessiblePrompter(t *testing.T) { _, err = console.ExpectString("Paste your authentication token:") require.NoError(t, err) - // Wait to ensure huh has time to set the echo mode - time.Sleep(beforePasswordSendTimeout) + // Wait until huh has disabled echo mode on the TTY + require.NoError(t, waitForEchoDisabled(console.Tty(), 5*time.Second)) // Now enter some dummy auth token to return control back to the test _, err = console.SendLine(dummyAuthTokenForAfterFailure) @@ -956,3 +956,21 @@ func testCloser(t *testing.T, closer io.Closer) { t.Errorf("Close failed: %s", err) } } + +// waitForEchoDisabled polls the TTY until echo mode is disabled or the +// timeout is reached. This is used in password and auth token tests to +// ensure that huh has configured the terminal before we send input. +func waitForEchoDisabled(tty *os.File, timeout time.Duration) error { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + termios, err := unix.IoctlGetTermios(int(tty.Fd()), ioctlGetTermios) + if err != nil { + return fmt.Errorf("getting terminal attributes: %w", err) + } + if termios.Lflag&unix.ECHO == 0 { + return nil + } + time.Sleep(time.Millisecond) + } + return fmt.Errorf("timed out waiting for echo mode to be disabled") +} diff --git a/internal/prompter/echo_darwin_test.go b/internal/prompter/echo_darwin_test.go new file mode 100644 index 000000000..2cb3130d9 --- /dev/null +++ b/internal/prompter/echo_darwin_test.go @@ -0,0 +1,7 @@ +//go:build darwin + +package prompter_test + +import "golang.org/x/sys/unix" + +const ioctlGetTermios = unix.TIOCGETA diff --git a/internal/prompter/echo_linux_test.go b/internal/prompter/echo_linux_test.go new file mode 100644 index 000000000..ad63bd1d5 --- /dev/null +++ b/internal/prompter/echo_linux_test.go @@ -0,0 +1,7 @@ +//go:build linux + +package prompter_test + +import "golang.org/x/sys/unix" + +const ioctlGetTermios = unix.TCGETS