mirror of
https://github.com/imjasonh/testscript-rs
synced 2026-07-08 17:16:38 +00:00
Implement network and advanced condition support
Co-authored-by: imjasonh <210737+imjasonh@users.noreply.github.com>
This commit is contained in:
parent
8f82c9c11b
commit
26a39e2384
8 changed files with 335 additions and 3 deletions
55
examples/advanced_conditions.rs
Normal file
55
examples/advanced_conditions.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//! Example demonstrating network and advanced condition support
|
||||
|
||||
use testscript_rs::testscript;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Running testscript with advanced condition support...");
|
||||
|
||||
// Example 1: Basic usage with auto-detection
|
||||
println!("\n=== Example 1: Auto-detect network and programs ===");
|
||||
testscript::run("testdata")
|
||||
.auto_detect_network()
|
||||
.auto_detect_programs(&["docker", "git", "npm", "echo"])
|
||||
.execute()?;
|
||||
|
||||
// Example 2: Manual condition setting with environment variables
|
||||
println!("\n=== Example 2: Environment-based conditions ===");
|
||||
|
||||
// Set a test environment variable
|
||||
std::env::set_var("CI", "true");
|
||||
|
||||
testscript::run("testdata")
|
||||
.condition("net", check_network_available())
|
||||
.condition("docker", command_exists("docker"))
|
||||
.execute()?;
|
||||
|
||||
println!("All tests completed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if network is available
|
||||
fn check_network_available() -> bool {
|
||||
std::process::Command::new("ping")
|
||||
.args(if cfg!(windows) {
|
||||
vec!["-n", "1", "-w", "1000", "1.1.1.1"]
|
||||
} else {
|
||||
vec!["-c", "1", "-W", "1", "1.1.1.1"]
|
||||
})
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check if a command exists
|
||||
fn command_exists(program: &str) -> bool {
|
||||
#[cfg(windows)]
|
||||
let check_cmd = "where";
|
||||
#[cfg(not(windows))]
|
||||
let check_cmd = "which";
|
||||
|
||||
std::process::Command::new(check_cmd)
|
||||
.arg(program)
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
53
src/lib.rs
53
src/lib.rs
|
|
@ -79,7 +79,7 @@ fn run(params: &mut RunParams, test_data_glob: &str) -> Result<()> {
|
|||
/// // Simple usage
|
||||
/// testscript::run("testdata").execute().unwrap();
|
||||
///
|
||||
/// // With customization
|
||||
/// // With customization and advanced conditions
|
||||
/// testscript::run("testdata")
|
||||
/// .setup(|env| {
|
||||
/// // Compile your CLI tool
|
||||
|
|
@ -93,8 +93,28 @@ fn run(params: &mut RunParams, test_data_glob: &str) -> Result<()> {
|
|||
/// // Custom command implementation
|
||||
/// Ok(())
|
||||
/// })
|
||||
/// .condition("net", check_network_available())
|
||||
/// .condition("docker", command_exists("docker"))
|
||||
/// .condition("env:CI", std::env::var("CI").is_ok())
|
||||
/// .execute()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // Auto-detect network and programs
|
||||
/// testscript::run("testdata")
|
||||
/// .auto_detect_network()
|
||||
/// .auto_detect_programs(&["docker", "git", "npm"])
|
||||
/// .execute()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// fn check_network_available() -> bool {
|
||||
/// // Your network check implementation
|
||||
/// true
|
||||
/// }
|
||||
///
|
||||
/// fn command_exists(cmd: &str) -> bool {
|
||||
/// // Your command existence check
|
||||
/// false
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Builder {
|
||||
dir: String,
|
||||
|
|
@ -149,6 +169,37 @@ impl Builder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Automatically detect network availability and set the 'net' condition
|
||||
///
|
||||
/// This will attempt to ping reliable hosts to determine if network
|
||||
/// connectivity is available and set the condition accordingly.
|
||||
pub fn auto_detect_network(mut self) -> Self {
|
||||
self.params = self.params.auto_detect_network();
|
||||
self
|
||||
}
|
||||
|
||||
/// Automatically detect availability of specified programs
|
||||
///
|
||||
/// This will check if the given programs are available in PATH and
|
||||
/// set conditions like `exec:program` for each one.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `programs` - Slice of program names to check for
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// use testscript_rs::testscript;
|
||||
///
|
||||
/// testscript::run("testdata")
|
||||
/// .auto_detect_programs(&["docker", "git", "npm"])
|
||||
/// .execute()
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
pub fn auto_detect_programs(mut self, programs: &[&str]) -> Self {
|
||||
self.params = self.params.auto_detect_programs(programs);
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute all test scripts in the configured directory
|
||||
///
|
||||
/// This will discover all `.txt` files in the directory and run them as test scripts.
|
||||
|
|
|
|||
|
|
@ -196,10 +196,16 @@ fn execute_command_inner(
|
|||
if let Some(ref condition) = command.condition {
|
||||
let condition_met = if let Some(value) = params.conditions.get(condition) {
|
||||
*value
|
||||
} else if condition.starts_with("env:") {
|
||||
// Dynamic environment variable condition
|
||||
RunParams::check_env_condition(condition)
|
||||
} else if let Some(base_condition) = condition.strip_prefix('!') {
|
||||
// Handle negated conditions
|
||||
if let Some(value) = params.conditions.get(base_condition) {
|
||||
!value
|
||||
} else if base_condition.starts_with("env:") {
|
||||
// Dynamic negated environment variable condition
|
||||
!RunParams::check_env_condition(base_condition)
|
||||
} else {
|
||||
return Err(Error::UnknownCondition {
|
||||
condition: base_condition.to_string(),
|
||||
|
|
|
|||
|
|
@ -86,14 +86,67 @@ impl RunParams {
|
|||
self
|
||||
}
|
||||
|
||||
/// Check if a program exists in PATH
|
||||
/// Automatically detect network availability and set the 'net' condition
|
||||
pub fn auto_detect_network(mut self) -> Self {
|
||||
self.conditions.insert("net".to_string(), Self::check_network_available());
|
||||
self
|
||||
}
|
||||
|
||||
/// Automatically detect availability of specified programs
|
||||
pub fn auto_detect_programs(mut self, programs: &[&str]) -> Self {
|
||||
for program in programs {
|
||||
let condition_name = format!("exec:{}", program);
|
||||
self.conditions.insert(condition_name, Self::program_exists(program));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Check if a program exists in PATH (cross-platform)
|
||||
fn program_exists(program: &str) -> bool {
|
||||
std::process::Command::new("which")
|
||||
// Use different commands based on platform
|
||||
#[cfg(windows)]
|
||||
let check_cmd = "where";
|
||||
#[cfg(not(windows))]
|
||||
let check_cmd = "which";
|
||||
|
||||
std::process::Command::new(check_cmd)
|
||||
.arg(program)
|
||||
.output()
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Check if network is available by attempting to reach a reliable host
|
||||
fn check_network_available() -> bool {
|
||||
// Try multiple reliable hosts to increase reliability
|
||||
let test_hosts = ["1.1.1.1", "8.8.8.8", "9.9.9.9"];
|
||||
|
||||
for host in &test_hosts {
|
||||
let output = std::process::Command::new("ping")
|
||||
.args(if cfg!(windows) {
|
||||
vec!["-n", "1", "-w", "1000", host]
|
||||
} else {
|
||||
vec!["-c", "1", "-W", "1", host]
|
||||
})
|
||||
.output();
|
||||
|
||||
if let Ok(result) = output {
|
||||
if result.status.success() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check environment variable condition
|
||||
pub fn check_env_condition(condition: &str) -> bool {
|
||||
if let Some(env_var) = condition.strip_prefix("env:") {
|
||||
std::env::var(env_var).is_ok()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RunParams {
|
||||
|
|
|
|||
19
testdata/env_conditions.txt
vendored
Normal file
19
testdata/env_conditions.txt
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Test environment variable conditions
|
||||
|
||||
# Set an env var in the test
|
||||
env TEST_VAR=hello
|
||||
|
||||
# This should execute since TEST_VAR is now set
|
||||
[env:TEST_VAR] exec echo "Environment variable TEST_VAR is set"
|
||||
[env:TEST_VAR] stdout "Environment variable TEST_VAR is set"
|
||||
|
||||
# This should be skipped since MISSING_VAR is not set
|
||||
[env:MISSING_VAR] exec echo "This should not appear"
|
||||
|
||||
# This should execute since MISSING_VAR is not set (negated condition)
|
||||
[!env:MISSING_VAR] exec echo "MISSING_VAR is not set (as expected)"
|
||||
[!env:MISSING_VAR] stdout "MISSING_VAR is not set (as expected)"
|
||||
|
||||
# Test PATH environment variable which should exist on all systems
|
||||
[env:PATH] exec echo "PATH environment variable exists"
|
||||
[env:PATH] stdout "PATH environment variable exists"
|
||||
11
testdata/network_conditions.txt
vendored
Normal file
11
testdata/network_conditions.txt
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Test network condition support
|
||||
|
||||
# This command will only run if network is available
|
||||
[net] exec echo "Network is available"
|
||||
|
||||
# This command will only run if network is not available
|
||||
[!net] exec echo "Network is not available"
|
||||
|
||||
# At least one of the above should execute, so we should see some output
|
||||
exec echo "Network test completed"
|
||||
stdout "Network test completed"
|
||||
16
testdata/program_conditions.txt
vendored
Normal file
16
testdata/program_conditions.txt
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Test program detection conditions
|
||||
|
||||
# Test that echo is available (should work on all platforms)
|
||||
[exec:echo] exec echo "echo command is available"
|
||||
[exec:echo] stdout "echo command is available"
|
||||
|
||||
# Test negation for a program that shouldn't exist
|
||||
[!exec:nonexistent_program_xyz123] exec echo "nonexistent program not found"
|
||||
[!exec:nonexistent_program_xyz123] stdout "nonexistent program not found"
|
||||
|
||||
# Test built-in program detections that are set by default
|
||||
[exec:ls] exec echo "ls command detected"
|
||||
[exec:mkdir] exec echo "mkdir command detected"
|
||||
|
||||
# At least mkdir should work since it's tested
|
||||
[exec:mkdir] stdout "mkdir command detected"
|
||||
121
tests/advanced_conditions.rs
Normal file
121
tests/advanced_conditions.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//! Tests for network and advanced condition support
|
||||
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
use testscript_rs::{run::RunParams, testscript};
|
||||
|
||||
#[test]
|
||||
fn test_env_conditions() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let testdata_dir = temp_dir.path().join("testdata");
|
||||
fs::create_dir(&testdata_dir).unwrap();
|
||||
|
||||
// Set an environment variable for testing
|
||||
std::env::set_var("TEST_CONDITION", "true");
|
||||
|
||||
let test_content = r#"[env:TEST_CONDITION] exec echo "env condition works"
|
||||
stdout "env condition works"
|
||||
|
||||
[!env:NONEXISTENT_VAR] exec echo "negated env condition works"
|
||||
stdout "negated env condition works"
|
||||
|
||||
[env:NONEXISTENT_VAR] exec echo "should be skipped"
|
||||
! stdout "should be skipped"
|
||||
"#;
|
||||
|
||||
fs::write(testdata_dir.join("env_test.txt"), test_content).unwrap();
|
||||
|
||||
let result = testscript::run(testdata_dir.to_string_lossy()).execute();
|
||||
assert!(result.is_ok(), "Environment condition test failed: {:?}", result);
|
||||
|
||||
// Clean up
|
||||
std::env::remove_var("TEST_CONDITION");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_detect_network() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let testdata_dir = temp_dir.path().join("testdata");
|
||||
fs::create_dir(&testdata_dir).unwrap();
|
||||
|
||||
// Create a test that should work whether network is available or not
|
||||
let test_content = r#"[net] exec echo "network available"
|
||||
[!net] exec echo "network not available"
|
||||
|
||||
# At least one should execute
|
||||
exec echo "test completed"
|
||||
stdout "test completed"
|
||||
"#;
|
||||
|
||||
fs::write(testdata_dir.join("network_test.txt"), test_content).unwrap();
|
||||
|
||||
let result = testscript::run(testdata_dir.to_string_lossy())
|
||||
.auto_detect_network()
|
||||
.execute();
|
||||
assert!(result.is_ok(), "Network condition test failed: {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_detect_programs() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let testdata_dir = temp_dir.path().join("testdata");
|
||||
fs::create_dir(&testdata_dir).unwrap();
|
||||
|
||||
// Test with echo which should be available on all platforms
|
||||
let test_content = r#"[exec:echo] exec echo "echo is available"
|
||||
stdout "echo is available"
|
||||
|
||||
[!exec:nonexistent_program_xyz] exec echo "nonexistent program not found"
|
||||
stdout "nonexistent program not found"
|
||||
"#;
|
||||
|
||||
fs::write(testdata_dir.join("program_test.txt"), test_content).unwrap();
|
||||
|
||||
let result = testscript::run(testdata_dir.to_string_lossy())
|
||||
.auto_detect_programs(&["echo", "nonexistent_program_xyz"])
|
||||
.execute();
|
||||
assert!(result.is_ok(), "Program detection test failed: {:?}", result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runparams_condition_helpers() {
|
||||
// Test the helper functions directly
|
||||
std::env::set_var("HELPER_TEST", "value");
|
||||
|
||||
assert!(RunParams::check_env_condition("env:HELPER_TEST"));
|
||||
assert!(!RunParams::check_env_condition("env:NONEXISTENT"));
|
||||
assert!(!RunParams::check_env_condition("not_env_condition"));
|
||||
|
||||
std::env::remove_var("HELPER_TEST");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_combined_conditions() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let testdata_dir = temp_dir.path().join("testdata");
|
||||
fs::create_dir(&testdata_dir).unwrap();
|
||||
|
||||
// Set environment for testing
|
||||
std::env::set_var("COMBINED_TEST", "true");
|
||||
|
||||
let test_content = r#"# Test combining different condition types
|
||||
# Use separate lines since parser doesn't support multiple conditions per line
|
||||
[unix] exec echo "unix detected"
|
||||
[windows] exec echo "windows detected"
|
||||
|
||||
# This should work on any platform with the env var
|
||||
[env:COMBINED_TEST] exec echo "env var is set"
|
||||
stdout "env var is set"
|
||||
"#;
|
||||
|
||||
fs::write(testdata_dir.join("combined_test.txt"), test_content).unwrap();
|
||||
|
||||
let result = testscript::run(testdata_dir.to_string_lossy())
|
||||
.auto_detect_network()
|
||||
.auto_detect_programs(&["echo"])
|
||||
.execute();
|
||||
assert!(result.is_ok(), "Combined condition test failed: {:?}", result);
|
||||
|
||||
// Clean up
|
||||
std::env::remove_var("COMBINED_TEST");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue