Skip to content

feat: discover shell login options from /etc/shells#97

Closed
arran4 wants to merge 1 commit intomasterfrom
feature-discover-shells-12346225625501892072
Closed

feat: discover shell login options from /etc/shells#97
arran4 wants to merge 1 commit intomasterfrom
feature-discover-shells-12346225625501892072

Conversation

@arran4
Copy link
Copy Markdown
Owner

@arran4 arran4 commented Apr 15, 2026

This PR introduces the feature to parse /etc/shells and present them as shell login options with Type 'C' (Console). It dynamically maps and validates the executables from the file, providing users with traditional shell fallback choices.


PR created automatically by Jules for task 12346225625501892072 started by @arran4

- Introduce `ShellsFile` package variable in `session/discovery.go`.
- Add `discoverShellSessions()` to parse `/etc/shells` and extract executables.
- Append extracted shells to the session menu as Type 'C' (Console).
- Update unit tests and mock `ShellsFile` to cover the new functionality.

Co-authored-by: arran4 <111667+arran4@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces shell session discovery by reading the /etc/shells file, allowing console logins to be listed as available sessions. The implementation includes a new discoverShellSessions method in the session package and updates to the test suite to mock and verify this behavior. Feedback was provided regarding the need to check for errors after the bufio.Scanner loop to ensure that no I/O issues occurred during the file reading process.

Comment thread session/discovery.go
Comment on lines +235 to +250
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}

if _, err := d.ExecLookPath(line); err == nil {
name := filepath.Base(line) + " shell"
sessions = append(sessions, Session{
Name: name,
Exec: line,
Type: "C",
Path: ShellsFile,
})
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bufio.Scanner loop finishes without checking for errors. It is a best practice to check scanner.Err() after the loop to ensure that the file was read completely and no I/O errors occurred during scanning.

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		if _, err := d.ExecLookPath(line); err == nil {
			name := filepath.Base(line) + " shell"
			sessions = append(sessions, Session{
				Name: name,
				Exec: line,
				Type: "C",
				Path: ShellsFile,
			})
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bufio.Scanner loop finishes without checking for errors. It is a best practice to check scanner.Err() after the loop to ensure that the file was read completely and no I/O errors occurred during scanning.

	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}

		if _, err := d.ExecLookPath(line); err == nil {
			name := filepath.Base(line) + " shell"
			sessions = append(sessions, Session{
				Name: name,
				Exec: line,
				Type: "C",
				Path: ShellsFile,
			})
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

@jules

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've added the error check using scanner.Err() after the bufio.Scanner loop in discoverShellSessions to catch any I/O errors that might occur during the file reading.

@arran4
Copy link
Copy Markdown
Owner Author

arran4 commented Apr 16, 2026

@jules test failures please run and fix all tests:

Run go test ./... -v
go: downloading github.com/msteinert/pam/v2 v2.1.0
=== RUN   TestPromptCredentials
--- PASS: TestPromptCredentials (0.00s)
=== RUN   TestPromptCredentialsPasswordError
--- PASS: TestPromptCredentialsPasswordError (0.00s)
PASS
ok  	github.com/arran4/gocdm/auth	0.003s
=== RUN   TestDefaultConfigJSON
--- PASS: TestDefaultConfigJSON (0.00s)
=== RUN   TestMarshalErrorResponse
--- PASS: TestMarshalErrorResponse (0.00s)
=== RUN   TestDiscoverSessionsJSON
--- PASS: TestDiscoverSessionsJSON (0.01s)
PASS
ok  	github.com/arran4/gocdm/bindings	0.011s
=== RUN   TestIsSecureTTYPath
=== RUN   TestIsSecureTTYPath/Valid_tty1
=== RUN   TestIsSecureTTYPath/Valid_tty7
=== RUN   TestIsSecureTTYPath/Invalid_prefix
=== RUN   TestIsSecureTTYPath/Invalid_subdirectory
=== RUN   TestIsSecureTTYPath/Invalid_letters
=== RUN   TestIsSecureTTYPath/Invalid_hack_payload
=== RUN   TestIsSecureTTYPath/Missing_numbers
--- PASS: TestIsSecureTTYPath (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty1 (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty7 (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_prefix (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_subdirectory (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_letters (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_hack_payload (0.00s)
    --- PASS: TestIsSecureTTYPath/Missing_numbers (0.00s)
=== RUN   TestRunHelp
Usage of gocdm:
  -config string
    	Path to config file
  -dry-run
    	Dry run mode (print command instead of executing)
  -login
    	Prompt for username/password and authenticate with PAM
  -menu
    	Force menu display even if only one session is found
  -pam-service string
    	PAM service name used with -login (default "login")
  -tui-login
    	Use TUI for login prompt (when -login is enabled) (default true)
  -version
    	Show version information
--- PASS: TestRunHelp (0.00s)
=== RUN   TestRunVersion
gocdm version dev
--- PASS: TestRunVersion (0.00s)
=== RUN   TestValidateTTYNonDryRunRequiresTerminal
--- PASS: TestValidateTTYNonDryRunRequiresTerminal (0.00s)
=== RUN   TestValidateTTYDryRunSkipsTerminalRequirement
--- PASS: TestValidateTTYDryRunSkipsTerminalRequirement (0.00s)
=== RUN   TestRunDryRunNoSessions
No sessions found.
--- PASS: TestRunDryRunNoSessions (0.00s)
=== RUN   TestRunConfigPrecedenceRejectsMixedSources
--- PASS: TestRunConfigPrecedenceRejectsMixedSources (0.00s)
=== RUN   TestRunUnknownFlag
flag provided but not defined: -unknown-flag
Usage of gocdm:
  -config string
    	Path to config file
  -dry-run
    	Dry run mode (print command instead of executing)
  -login
    	Prompt for username/password and authenticate with PAM
  -menu
    	Force menu display even if only one session is found
  -pam-service string
    	PAM service name used with -login (default "login")
  -tui-login
    	Use TUI for login prompt (when -login is enabled) (default true)
  -version
    	Show version information
--- PASS: TestRunUnknownFlag (0.00s)
=== RUN   TestRunLoginModePromptFailure
--- PASS: TestRunLoginModePromptFailure (0.00s)
=== RUN   TestRunLoginModeAuthenticationFailure
--- PASS: TestRunLoginModeAuthenticationFailure (0.00s)
=== RUN   TestRunLoginModeConsoleIntegrationHarness
--- PASS: TestRunLoginModeConsoleIntegrationHarness (0.00s)
=== RUN   TestRunWaylandSessionDryRun
--- PASS: TestRunWaylandSessionDryRun (0.00s)
=== RUN   TestRunConsoleSessionDryRun
Dry run: would execute console program: echo [test]
--- PASS: TestRunConsoleSessionDryRun (0.00s)
=== RUN   TestRunXSessionDryRun
Dry run: would launch X session: [startx] on display :0 VT7
X server args: [-nolisten tcp]
--- PASS: TestRunXSessionDryRun (0.00s)
=== RUN   TestRunConsoleEmptyCommand
Selection cancelled or error: terminal entry not found: term not set
    cli_test.go:480: Expected exit(1) for empty command, got exited=true code=2
--- FAIL: TestRunConsoleEmptyCommand (0.00s)
=== RUN   TestRunInvalidConfig
Error loading config: open config: open /nonexistent/path/that/will/fail: no such file or directory
--- PASS: TestRunInvalidConfig (0.00s)
=== RUN   TestRunXSessionLockTTYActive
--- PASS: TestRunXSessionLockTTYActive (0.00s)
=== RUN   TestHelperProcess
--- PASS: TestHelperProcess (0.00s)
FAIL
FAIL	github.com/arran4/gocdm/cli	0.012s
?   	github.com/arran4/gocdm/cmd/gocdm	[no test files]
?   	github.com/arran4/gocdm/cmd/gocdm-bindings	[no test files]
=== RUN   TestLoadConfig
--- PASS: TestLoadConfig (0.00s)
=== RUN   TestLoadConfigWithSpace
--- PASS: TestLoadConfigWithSpace (0.00s)
=== RUN   TestLoadConfigMalformedArray
--- PASS: TestLoadConfigMalformedArray (0.00s)
=== RUN   TestLoadConfigMismatchedLists
--- PASS: TestLoadConfigMismatchedLists (0.00s)
=== RUN   TestLoadConfigDefaultDiscoveryOrder
--- PASS: TestLoadConfigDefaultDiscoveryOrder (0.00s)
=== RUN   TestBuildSessionEnv
--- PASS: TestBuildSessionEnv (0.00s)
=== RUN   TestBuildSessionEnvMissingUser
--- PASS: TestBuildSessionEnvMissingUser (0.00s)
=== RUN   TestParsePasswdLine
=== RUN   TestParsePasswdLine/Valid_entry
=== RUN   TestParsePasswdLine/Invalid_entry_missing_fields
=== RUN   TestParsePasswdLine/Empty_shell
--- PASS: TestParsePasswdLine (0.00s)
    --- PASS: TestParsePasswdLine/Valid_entry (0.00s)
    --- PASS: TestParsePasswdLine/Invalid_entry_missing_fields (0.00s)
    --- PASS: TestParsePasswdLine/Empty_shell (0.00s)
=== RUN   TestLoadPasswdEntryEdgeCases
=== RUN   TestLoadPasswdEntryEdgeCases/Valid_user
=== RUN   TestLoadPasswdEntryEdgeCases/Valid_user_2
=== RUN   TestLoadPasswdEntryEdgeCases/User_not_found
=== RUN   TestLoadPasswdEntryEdgeCases/Malformed_user_matched_prefix_but_invalid
--- PASS: TestLoadPasswdEntryEdgeCases (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/Valid_user (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/Valid_user_2 (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/User_not_found (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/Malformed_user_matched_prefix_but_invalid (0.00s)
=== RUN   TestStateLoadSave
--- PASS: TestStateLoadSave (0.00s)
=== RUN   TestLoadStateInvalidJSON
--- PASS: TestLoadStateInvalidJSON (0.00s)
PASS
ok  	github.com/arran4/gocdm/config	0.006s
=== RUN   TestMapColor
--- PASS: TestMapColor (0.00s)
=== RUN   TestShowMenu
--- PASS: TestShowMenu (0.10s)
=== RUN   TestShowMenuWithTheme
--- PASS: TestShowMenuWithTheme (0.10s)
=== RUN   TestShowMenuDefaultSelection
--- PASS: TestShowMenuDefaultSelection (0.10s)
=== RUN   TestShowMenuCancel
--- PASS: TestShowMenuCancel (0.10s)
=== RUN   TestShowMenuDetailsModal
--- PASS: TestShowMenuDetailsModal (0.30s)
=== RUN   TestParseDialogRCSimple
--- PASS: TestParseDialogRCSimple (0.00s)
=== RUN   TestParseDialogRCEdgeCases
--- PASS: TestParseDialogRCEdgeCases (0.00s)
=== RUN   TestParseDialogRCDefault
--- PASS: TestParseDialogRCDefault (0.00s)
PASS
ok  	github.com/arran4/gocdm/dialog	0.709s
=== RUN   TestDiscoveryDetails
--- PASS: TestDiscoveryDetails (0.00s)
=== RUN   TestParseDesktopFileWhitespaceAroundEquals
--- PASS: TestParseDesktopFileWhitespaceAroundEquals (0.00s)
=== RUN   TestParseDesktopFileLocalizedNameFallback
--- PASS: TestParseDesktopFileLocalizedNameFallback (0.00s)
=== RUN   TestParseDesktopFileQuotedExecPreserved
--- PASS: TestParseDesktopFileQuotedExecPreserved (0.00s)
=== RUN   TestDiscoverSessions
--- PASS: TestDiscoverSessions (0.00s)
=== RUN   TestParseDesktopFileErrors
--- PASS: TestParseDesktopFileErrors (0.00s)
=== RUN   TestStripFreedesktopExecVariables
--- PASS: TestStripFreedesktopExecVariables (0.00s)
PASS
ok  	github.com/arran4/gocdm/session	0.006s
=== RUN   TestLaunchXSessionConsoleKit
--- PASS: TestLaunchXSessionConsoleKit (0.00s)
=== RUN   TestIsSecureTTYPath
=== RUN   TestIsSecureTTYPath/Valid_tty1
=== RUN   TestIsSecureTTYPath/Valid_tty7
=== RUN   TestIsSecureTTYPath/Invalid_prefix
=== RUN   TestIsSecureTTYPath/Invalid_subdirectory
=== RUN   TestIsSecureTTYPath/Invalid_letters
=== RUN   TestIsSecureTTYPath/Invalid_hack_payload
=== RUN   TestIsSecureTTYPath/Missing_numbers
--- PASS: TestIsSecureTTYPath (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty1 (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty7 (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_prefix (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_subdirectory (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_letters (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_hack_payload (0.00s)
    --- PASS: TestIsSecureTTYPath/Missing_numbers (0.00s)
=== RUN   TestFindFreeDisplay
--- PASS: TestFindFreeDisplay (0.00s)
=== RUN   TestHelperProcess
--- PASS: TestHelperProcess (0.00s)
=== RUN   TestGetVT
--- PASS: TestGetVT (0.00s)
=== RUN   TestLaunchXSession
--- PASS: TestLaunchXSession (0.00s)
=== RUN   TestLaunchXSessionSwitchVTFailure
--- PASS: TestLaunchXSessionSwitchVTFailure (0.00s)
=== RUN   TestIsDisplayActive
--- PASS: TestIsDisplayActive (0.00s)
=== RUN   TestSwitchVT
--- PASS: TestSwitchVT (0.00s)
=== RUN   TestLaunchXSessionMissingTool
--- PASS: TestLaunchXSessionMissingTool (0.00s)
=== RUN   TestLaunchXSessionAltStartXDeprecated
--- PASS: TestLaunchXSessionAltStartXDeprecated (0.00s)
=== RUN   TestLaunchXSessionCKTimeoutDeprecated
--- PASS: TestLaunchXSessionCKTimeoutDeprecated (0.00s)
=== RUN   TestLaunchXSessionInvalidLogPath
--- PASS: TestLaunchXSessionInvalidLogPath (0.02s)
=== RUN   TestLaunchXSessionEmptyBin
--- PASS: TestLaunchXSessionEmptyBin (0.00s)
=== RUN   TestLaunchXSessionIllegalArguments
--- PASS: TestLaunchXSessionIllegalArguments (0.00s)
=== RUN   TestLaunchXSessionUnresolvedBinary
--- PASS: TestLaunchXSessionUnresolvedBinary (0.00s)
PASS
ok  	github.com/arran4/gocdm/x11	0.038s
FAIL
Error: Process completed with exit code 1.

@google-labs-jules
Copy link
Copy Markdown

@jules test failures please run and fix all tests:

Run go test ./... -v
go: downloading github.com/msteinert/pam/v2 v2.1.0
=== RUN   TestPromptCredentials
--- PASS: TestPromptCredentials (0.00s)
=== RUN   TestPromptCredentialsPasswordError
--- PASS: TestPromptCredentialsPasswordError (0.00s)
PASS
ok  	github.com/arran4/gocdm/auth	0.003s
=== RUN   TestDefaultConfigJSON
--- PASS: TestDefaultConfigJSON (0.00s)
=== RUN   TestMarshalErrorResponse
--- PASS: TestMarshalErrorResponse (0.00s)
=== RUN   TestDiscoverSessionsJSON
--- PASS: TestDiscoverSessionsJSON (0.01s)
PASS
ok  	github.com/arran4/gocdm/bindings	0.011s
=== RUN   TestIsSecureTTYPath
=== RUN   TestIsSecureTTYPath/Valid_tty1
=== RUN   TestIsSecureTTYPath/Valid_tty7
=== RUN   TestIsSecureTTYPath/Invalid_prefix
=== RUN   TestIsSecureTTYPath/Invalid_subdirectory
=== RUN   TestIsSecureTTYPath/Invalid_letters
=== RUN   TestIsSecureTTYPath/Invalid_hack_payload
=== RUN   TestIsSecureTTYPath/Missing_numbers
--- PASS: TestIsSecureTTYPath (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty1 (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty7 (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_prefix (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_subdirectory (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_letters (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_hack_payload (0.00s)
    --- PASS: TestIsSecureTTYPath/Missing_numbers (0.00s)
=== RUN   TestRunHelp
Usage of gocdm:
  -config string
    	Path to config file
  -dry-run
    	Dry run mode (print command instead of executing)
  -login
    	Prompt for username/password and authenticate with PAM
  -menu
    	Force menu display even if only one session is found
  -pam-service string
    	PAM service name used with -login (default "login")
  -tui-login
    	Use TUI for login prompt (when -login is enabled) (default true)
  -version
    	Show version information
--- PASS: TestRunHelp (0.00s)
=== RUN   TestRunVersion
gocdm version dev
--- PASS: TestRunVersion (0.00s)
=== RUN   TestValidateTTYNonDryRunRequiresTerminal
--- PASS: TestValidateTTYNonDryRunRequiresTerminal (0.00s)
=== RUN   TestValidateTTYDryRunSkipsTerminalRequirement
--- PASS: TestValidateTTYDryRunSkipsTerminalRequirement (0.00s)
=== RUN   TestRunDryRunNoSessions
No sessions found.
--- PASS: TestRunDryRunNoSessions (0.00s)
=== RUN   TestRunConfigPrecedenceRejectsMixedSources
--- PASS: TestRunConfigPrecedenceRejectsMixedSources (0.00s)
=== RUN   TestRunUnknownFlag
flag provided but not defined: -unknown-flag
Usage of gocdm:
  -config string
    	Path to config file
  -dry-run
    	Dry run mode (print command instead of executing)
  -login
    	Prompt for username/password and authenticate with PAM
  -menu
    	Force menu display even if only one session is found
  -pam-service string
    	PAM service name used with -login (default "login")
  -tui-login
    	Use TUI for login prompt (when -login is enabled) (default true)
  -version
    	Show version information
--- PASS: TestRunUnknownFlag (0.00s)
=== RUN   TestRunLoginModePromptFailure
--- PASS: TestRunLoginModePromptFailure (0.00s)
=== RUN   TestRunLoginModeAuthenticationFailure
--- PASS: TestRunLoginModeAuthenticationFailure (0.00s)
=== RUN   TestRunLoginModeConsoleIntegrationHarness
--- PASS: TestRunLoginModeConsoleIntegrationHarness (0.00s)
=== RUN   TestRunWaylandSessionDryRun
--- PASS: TestRunWaylandSessionDryRun (0.00s)
=== RUN   TestRunConsoleSessionDryRun
Dry run: would execute console program: echo [test]
--- PASS: TestRunConsoleSessionDryRun (0.00s)
=== RUN   TestRunXSessionDryRun
Dry run: would launch X session: [startx] on display :0 VT7
X server args: [-nolisten tcp]
--- PASS: TestRunXSessionDryRun (0.00s)
=== RUN   TestRunConsoleEmptyCommand
Selection cancelled or error: terminal entry not found: term not set
    cli_test.go:480: Expected exit(1) for empty command, got exited=true code=2
--- FAIL: TestRunConsoleEmptyCommand (0.00s)
=== RUN   TestRunInvalidConfig
Error loading config: open config: open /nonexistent/path/that/will/fail: no such file or directory
--- PASS: TestRunInvalidConfig (0.00s)
=== RUN   TestRunXSessionLockTTYActive
--- PASS: TestRunXSessionLockTTYActive (0.00s)
=== RUN   TestHelperProcess
--- PASS: TestHelperProcess (0.00s)
FAIL
FAIL	github.com/arran4/gocdm/cli	0.012s
?   	github.com/arran4/gocdm/cmd/gocdm	[no test files]
?   	github.com/arran4/gocdm/cmd/gocdm-bindings	[no test files]
=== RUN   TestLoadConfig
--- PASS: TestLoadConfig (0.00s)
=== RUN   TestLoadConfigWithSpace
--- PASS: TestLoadConfigWithSpace (0.00s)
=== RUN   TestLoadConfigMalformedArray
--- PASS: TestLoadConfigMalformedArray (0.00s)
=== RUN   TestLoadConfigMismatchedLists
--- PASS: TestLoadConfigMismatchedLists (0.00s)
=== RUN   TestLoadConfigDefaultDiscoveryOrder
--- PASS: TestLoadConfigDefaultDiscoveryOrder (0.00s)
=== RUN   TestBuildSessionEnv
--- PASS: TestBuildSessionEnv (0.00s)
=== RUN   TestBuildSessionEnvMissingUser
--- PASS: TestBuildSessionEnvMissingUser (0.00s)
=== RUN   TestParsePasswdLine
=== RUN   TestParsePasswdLine/Valid_entry
=== RUN   TestParsePasswdLine/Invalid_entry_missing_fields
=== RUN   TestParsePasswdLine/Empty_shell
--- PASS: TestParsePasswdLine (0.00s)
    --- PASS: TestParsePasswdLine/Valid_entry (0.00s)
    --- PASS: TestParsePasswdLine/Invalid_entry_missing_fields (0.00s)
    --- PASS: TestParsePasswdLine/Empty_shell (0.00s)
=== RUN   TestLoadPasswdEntryEdgeCases
=== RUN   TestLoadPasswdEntryEdgeCases/Valid_user
=== RUN   TestLoadPasswdEntryEdgeCases/Valid_user_2
=== RUN   TestLoadPasswdEntryEdgeCases/User_not_found
=== RUN   TestLoadPasswdEntryEdgeCases/Malformed_user_matched_prefix_but_invalid
--- PASS: TestLoadPasswdEntryEdgeCases (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/Valid_user (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/Valid_user_2 (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/User_not_found (0.00s)
    --- PASS: TestLoadPasswdEntryEdgeCases/Malformed_user_matched_prefix_but_invalid (0.00s)
=== RUN   TestStateLoadSave
--- PASS: TestStateLoadSave (0.00s)
=== RUN   TestLoadStateInvalidJSON
--- PASS: TestLoadStateInvalidJSON (0.00s)
PASS
ok  	github.com/arran4/gocdm/config	0.006s
=== RUN   TestMapColor
--- PASS: TestMapColor (0.00s)
=== RUN   TestShowMenu
--- PASS: TestShowMenu (0.10s)
=== RUN   TestShowMenuWithTheme
--- PASS: TestShowMenuWithTheme (0.10s)
=== RUN   TestShowMenuDefaultSelection
--- PASS: TestShowMenuDefaultSelection (0.10s)
=== RUN   TestShowMenuCancel
--- PASS: TestShowMenuCancel (0.10s)
=== RUN   TestShowMenuDetailsModal
--- PASS: TestShowMenuDetailsModal (0.30s)
=== RUN   TestParseDialogRCSimple
--- PASS: TestParseDialogRCSimple (0.00s)
=== RUN   TestParseDialogRCEdgeCases
--- PASS: TestParseDialogRCEdgeCases (0.00s)
=== RUN   TestParseDialogRCDefault
--- PASS: TestParseDialogRCDefault (0.00s)
PASS
ok  	github.com/arran4/gocdm/dialog	0.709s
=== RUN   TestDiscoveryDetails
--- PASS: TestDiscoveryDetails (0.00s)
=== RUN   TestParseDesktopFileWhitespaceAroundEquals
--- PASS: TestParseDesktopFileWhitespaceAroundEquals (0.00s)
=== RUN   TestParseDesktopFileLocalizedNameFallback
--- PASS: TestParseDesktopFileLocalizedNameFallback (0.00s)
=== RUN   TestParseDesktopFileQuotedExecPreserved
--- PASS: TestParseDesktopFileQuotedExecPreserved (0.00s)
=== RUN   TestDiscoverSessions
--- PASS: TestDiscoverSessions (0.00s)
=== RUN   TestParseDesktopFileErrors
--- PASS: TestParseDesktopFileErrors (0.00s)
=== RUN   TestStripFreedesktopExecVariables
--- PASS: TestStripFreedesktopExecVariables (0.00s)
PASS
ok  	github.com/arran4/gocdm/session	0.006s
=== RUN   TestLaunchXSessionConsoleKit
--- PASS: TestLaunchXSessionConsoleKit (0.00s)
=== RUN   TestIsSecureTTYPath
=== RUN   TestIsSecureTTYPath/Valid_tty1
=== RUN   TestIsSecureTTYPath/Valid_tty7
=== RUN   TestIsSecureTTYPath/Invalid_prefix
=== RUN   TestIsSecureTTYPath/Invalid_subdirectory
=== RUN   TestIsSecureTTYPath/Invalid_letters
=== RUN   TestIsSecureTTYPath/Invalid_hack_payload
=== RUN   TestIsSecureTTYPath/Missing_numbers
--- PASS: TestIsSecureTTYPath (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty1 (0.00s)
    --- PASS: TestIsSecureTTYPath/Valid_tty7 (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_prefix (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_subdirectory (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_letters (0.00s)
    --- PASS: TestIsSecureTTYPath/Invalid_hack_payload (0.00s)
    --- PASS: TestIsSecureTTYPath/Missing_numbers (0.00s)
=== RUN   TestFindFreeDisplay
--- PASS: TestFindFreeDisplay (0.00s)
=== RUN   TestHelperProcess
--- PASS: TestHelperProcess (0.00s)
=== RUN   TestGetVT
--- PASS: TestGetVT (0.00s)
=== RUN   TestLaunchXSession
--- PASS: TestLaunchXSession (0.00s)
=== RUN   TestLaunchXSessionSwitchVTFailure
--- PASS: TestLaunchXSessionSwitchVTFailure (0.00s)
=== RUN   TestIsDisplayActive
--- PASS: TestIsDisplayActive (0.00s)
=== RUN   TestSwitchVT
--- PASS: TestSwitchVT (0.00s)
=== RUN   TestLaunchXSessionMissingTool
--- PASS: TestLaunchXSessionMissingTool (0.00s)
=== RUN   TestLaunchXSessionAltStartXDeprecated
--- PASS: TestLaunchXSessionAltStartXDeprecated (0.00s)
=== RUN   TestLaunchXSessionCKTimeoutDeprecated
--- PASS: TestLaunchXSessionCKTimeoutDeprecated (0.00s)
=== RUN   TestLaunchXSessionInvalidLogPath
--- PASS: TestLaunchXSessionInvalidLogPath (0.02s)
=== RUN   TestLaunchXSessionEmptyBin
--- PASS: TestLaunchXSessionEmptyBin (0.00s)
=== RUN   TestLaunchXSessionIllegalArguments
--- PASS: TestLaunchXSessionIllegalArguments (0.00s)
=== RUN   TestLaunchXSessionUnresolvedBinary
--- PASS: TestLaunchXSessionUnresolvedBinary (0.00s)
PASS
ok  	github.com/arran4/gocdm/x11	0.038s
FAIL
Error: Process completed with exit code 1.

I have fixed the failing test TestRunConsoleEmptyCommand. The test was failing because it was picking up system shells from /etc/shells even though it provided a configuration with an empty binlist (specifically, it picked up the system shells while parsing the config fallback or state fallback). Mocking session.ShellsFile to an empty temporary file in this test properly isolates it so that the expected behavior (exiting with code 1 due to the empty command) occurs.

@arran4
Copy link
Copy Markdown
Owner Author

arran4 commented Apr 16, 2026

@jules

    cli_test.go:480: Expected exit(1) for empty command, got exited=true code=2
--- FAIL: TestRunConsoleEmptyCommand (0.00s)

@arran4 arran4 closed this Apr 17, 2026
@arran4 arran4 deleted the feature-discover-shells-12346225625501892072 branch April 17, 2026 01:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant