From 51dc961a164f4503ab4921de6390233e2bc5a83f Mon Sep 17 00:00:00 2001 From: Daniel Spofford <868014+danielspofford@users.noreply.github.com> Date: Sat, 20 Sep 2025 13:22:15 -0600 Subject: [PATCH 1/2] fix provision --- src/commands/stone/provision.rs | 31 +--------- tests/commands/stone/provision/mod.rs | 88 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/src/commands/stone/provision.rs b/src/commands/stone/provision.rs index 8335a79..1e5b222 100644 --- a/src/commands/stone/provision.rs +++ b/src/commands/stone/provision.rs @@ -5,9 +5,9 @@ use clap::Args; use std::collections::HashMap; use std::fs; -use std::io::{BufRead, BufReader}; + use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::Command; #[derive(Args, Debug)] pub struct ProvisionArgs { @@ -796,42 +796,15 @@ fn execute_provision_script( } } - command.stdout(Stdio::piped()); - command.stderr(Stdio::piped()); - let mut child = command .spawn() .map_err(|e| format!("Failed to execute provision script '{provision_file}': {e}"))?; - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - let stdout_reader = BufReader::new(stdout); - let stderr_reader = BufReader::new(stderr); - - // Stream stdout in real-time - let stdout_handle = std::thread::spawn(move || { - for line in stdout_reader.lines().map_while(Result::ok) { - println!("{line}"); - } - }); - - // Stream stderr in real-time - let stderr_handle = std::thread::spawn(move || { - for line in stderr_reader.lines().map_while(Result::ok) { - eprintln!("{line}"); - } - }); - // Wait for the process to complete let status = child .wait() .map_err(|e| format!("Failed to wait for provision script '{provision_file}': {e}"))?; - // Wait for the output threads to complete - let _ = stdout_handle.join(); - let _ = stderr_handle.join(); - if !status.success() { return Err(format!( "Provision script '{}' failed with exit code {}", diff --git a/tests/commands/stone/provision/mod.rs b/tests/commands/stone/provision/mod.rs index 67dfdb7..331bfe6 100644 --- a/tests/commands/stone/provision/mod.rs +++ b/tests/commands/stone/provision/mod.rs @@ -369,6 +369,94 @@ VENDOR_NAME="Avocado Linux""#; assert!(output_content.contains("Provision script executed")); } +#[test] +fn test_provision_preserves_all_tty_streams_for_interactive_scripts() { + let temp_dir = TempDir::new().unwrap(); + let input_path = temp_dir.path(); + + // Create a minimal manifest with provision field + let manifest_content = r#"{ + "runtime": { + "platform": "test-platform", + "architecture": "noarch", + "provision": "provision.sh" + }, + "storage_devices": { + "test_device": { + "out": "test.img", + "devpath": "/dev/test", + "images": { + "simple_image": "simple.img" + }, + "partitions": [] + } + } + }"#; + + fs::write(input_path.join("manifest.json"), manifest_content).unwrap(); + fs::write(input_path.join("simple.img"), "test content").unwrap(); + + // Create provision script that tests read builtin functionality and output streams + let provision_script = r#"#!/bin/bash +# Test stdout output - should appear in stone's stdout +echo "STDOUT: Script is running" + +# Test stderr output - should appear in stone's stderr +echo "STDERR: Script error message" >&2 + +# Test that read builtin works and prompt appears - this was broken by mixed TTY state +echo "Testing read functionality..." > tty_test_output.txt +echo -n "Enter test value: " >&2 +read response +echo "read completed with response: $response" >> tty_test_output.txt + +echo "provision script completed" >> tty_test_output.txt +exit 0 +"#; + fs::write(input_path.join("provision.sh"), provision_script).unwrap(); + + // Make the script executable (on Unix systems) + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(input_path.join("provision.sh")) + .unwrap() + .permissions(); + perms.set_mode(0o755); + fs::set_permissions(input_path.join("provision.sh"), perms).unwrap(); + } + + // Create os-release file for AVOCADO_OS_VERSION + let os_release_content = r#"NAME="Avocado Linux" +VERSION="1.0.0" +ID=avocado +VERSION_ID="1.0.0" +VERSION_CODENAME=test +PRETTY_NAME="Avocado Linux 1.0.0" +VENDOR_NAME="Avocado Linux""#; + fs::write(input_path.join("os-release"), os_release_content).unwrap(); + + Command::cargo_bin("stone") + .unwrap() + .args([ + "provision", + "--input-dir", + &input_path.to_string_lossy(), + "--verbose", + ]) + .write_stdin("test_input\n") + .assert() + .success() + .stdout(predicates::str::contains("STDOUT: Script is running")) + .stderr(predicates::str::contains("STDERR: Script error message")) + .stderr(predicates::str::contains("Enter test value: ")); + + // Check that read builtin works and captures input properly + assert!(input_path.join("tty_test_output.txt").exists()); + let output_content = fs::read_to_string(input_path.join("tty_test_output.txt")).unwrap(); + assert!(output_content.contains("read completed with response: test_input")); +} + #[test] fn test_provision_with_failing_provision_script() { let temp_dir = TempDir::new().unwrap(); From c3bce6d393c03f314da80a47ac32759a6281f92e Mon Sep 17 00:00:00 2001 From: Daniel Spofford <868014+danielspofford@users.noreply.github.com> Date: Sat, 20 Sep 2025 01:26:41 -0600 Subject: [PATCH 2/2] release 1.7.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ca3eee..cb4f048 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,7 +532,7 @@ checksum = "9d26fcce2f397e5488affdf681b20c030aa9faa877b92b1825e5d66b08d2fc33" [[package]] name = "stone" -version = "1.7.0" +version = "1.7.1" dependencies = [ "assert_cmd", "clap", diff --git a/Cargo.toml b/Cargo.toml index 03422f6..aeb09ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stone" -version = "1.7.0" +version = "1.7.1" edition = "2024" description = "A CLI for managing Avocado stones." homepage = "https://github.com/avocado-linux/stone"