-
Notifications
You must be signed in to change notification settings - Fork 15
Add POSIX compatibility spec and acceptance tests for TruShell v3 #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # POSIX compatibility spec for TruShell v3 | ||
|
|
||
| This document captures the minimum POSIX-oriented compatibility expectations for the v3 shell experience in TruShell. The goal is to preserve familiar shell behavior for common builtins and external commands while keeping the language parser available for TruShell-specific expressions. | ||
|
|
||
| ## Scope | ||
|
|
||
| The v3 shell should support: | ||
|
|
||
| - interactive prompt behavior with standard input and output | ||
| - basic builtins such as `cd` and `exit` | ||
| - execution of external commands through the host system PATH | ||
| - preservation of quoted arguments for external commands | ||
| - simple fallback behavior when a line is not a TruShell expression | ||
|
|
||
| ## Acceptance criteria | ||
|
|
||
| ### 1. `cd` without arguments | ||
|
|
||
| - Running `cd` changes the shell working directory to the user's home directory. | ||
| - A following `pwd` command prints that directory. | ||
|
|
||
| ### 2. Quoted arguments for external commands | ||
|
|
||
| - A command such as `printf '%s %s\n' hello world` receives the arguments `hello` and `world` exactly as written. | ||
| - The shell should preserve the quoting semantics required for the external process to see the intended arguments. | ||
|
|
||
| ### 3. Builtin exit behavior | ||
|
|
||
| - `exit` terminates the interactive shell cleanly. | ||
| - The shell should not hang waiting for more input after receiving `exit`. | ||
|
|
||
| ### 4. Fallback to external commands | ||
|
|
||
| - If a line cannot be parsed as a TruShell expression, the shell should attempt to execute it as an external command. | ||
| - This allows ordinary POSIX-style commands to continue working in the REPL. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::io::Write; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::process::{Command, Stdio}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn run_shell(input: &str) -> std::process::Output { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut child = Command::new(env!("CARGO")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .arg("run") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .arg("--quiet") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .stdin(Stdio::piped()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .stdout(Stdio::piped()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .stderr(Stdio::piped()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .spawn() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect("failed to spawn trushell"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Some(stdin) = child.stdin.as_mut() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stdin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .write_all(input.as_bytes()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .expect("failed to write shell input"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| child.wait_with_output().expect("failed to read shell output") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn cd_without_arguments_uses_the_home_directory() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output = run_shell("cd\npwd\nexit\n"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let stdout = String::from_utf8_lossy(&output.stdout); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.status.success(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "shell exited with {:?}: {}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String::from_utf8_lossy(&output.stderr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stdout.contains(&home), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "expected pwd output to contain home directory {home}, got: {stdout}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+24
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Align the test fallback with the shell implementation. When the Consider aligning the test's fallback value with the runtime implementation's exact behavior. 💚 Proposed fix fn cd_without_arguments_uses_the_home_directory() {
- let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
+ let home = std::env::var("HOME").unwrap_or_else(|_| {
+ std::env::current_dir().unwrap().to_string_lossy().into_owned()
+ });
let output = run_shell("cd\npwd\nexit\n");
let stdout = String::from_utf8_lossy(&output.stdout);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #[test] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fn quoted_arguments_are_preserved_for_external_commands() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let output = run_shell("printf '%s %s\\n' hello world\nexit\n"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let stdout = String::from_utf8_lossy(&output.stdout); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.status.success(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "shell exited with {:?}: {}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String::from_utf8_lossy(&output.stderr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| assert!( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stdout.contains("hello world"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "expected printf output to include the quoted arguments, got: {stdout}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.