use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
#[test]
fn test_version_flag() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.arg("--version")
.output()
.expect("Failed to run arf");
assert!(output.status.success(), "arf --version should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("arf") || stdout.contains("0.1.0"),
"Version output should contain version info: {}",
stdout
);
}
#[test]
fn test_help_flag() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.arg("--help")
.output()
.expect("Failed to run arf");
assert!(output.status.success(), "arf --help should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("--reprex") && stdout.contains("--no-banner") && stdout.contains("--eval"),
"Help should show CLI options: {}",
stdout
);
}
#[test]
fn test_completions_subcommand() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["completions", "bash"])
.output()
.expect("Failed to run arf completions");
assert!(
output.status.success(),
"arf completions bash should succeed"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("complete") || stdout.contains("arf"),
"Completion output should contain bash completion code: {}",
stdout
);
}
#[test]
fn test_history_schema_subcommand() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["history", "schema"])
.output()
.expect("Failed to run arf history schema");
assert!(output.status.success(), "arf history schema should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("# History Database"),
"Should contain title: {}",
stdout
);
assert!(
stdout.contains("## Location"),
"Should contain location section: {}",
stdout
);
assert!(
stdout.contains("## SQLite Schema"),
"Should contain schema section: {}",
stdout
);
assert!(
stdout.contains("## Indexes"),
"Should contain indexes section: {}",
stdout
);
assert!(
stdout.contains("## Analyze or Export"),
"Should contain export section: {}",
stdout
);
assert!(
stdout.contains("CREATE TABLE history"),
"Should contain CREATE TABLE: {}",
stdout
);
assert!(
stdout.contains("command_line"),
"Should contain command_line column: {}",
stdout
);
assert!(
stdout.contains("library(DBI)"),
"Should contain R DBI example: {}",
stdout
);
assert!(
stdout.contains("dbConnect"),
"Should contain dbConnect: {}",
stdout
);
}
#[test]
fn test_history_schema_piped_no_colors() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["history", "schema"])
.output()
.expect("Failed to run arf history schema");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.contains("\x1b["),
"Piped output should not contain ANSI escape codes: {:?}",
&stdout[..stdout.len().min(200)]
);
}
#[test]
fn test_history_import_rejects_self_import_r_db() {
use reedline::SqliteBackedHistory;
use tempfile::TempDir;
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let history_dir = temp_dir.path();
let r_db_path = history_dir.join("r.db");
let _db = SqliteBackedHistory::with_file(r_db_path.clone(), None, None)
.expect("Failed to create r.db");
drop(_db);
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args([
"--history-dir",
history_dir.to_str().unwrap(),
"history",
"import",
"--from",
"arf",
"--file",
r_db_path.to_str().unwrap(),
])
.output()
.expect("Failed to run arf history import");
assert!(
!output.status.success(),
"Self-import should fail, but succeeded"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Refusing to import") && stderr.contains("into itself"),
"Error should mention refusing self-import, got: {}",
stderr
);
}
#[test]
fn test_history_import_rejects_self_import_shell_db() {
use reedline::SqliteBackedHistory;
use tempfile::TempDir;
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let history_dir = temp_dir.path();
let shell_db_path = history_dir.join("shell.db");
let _db = SqliteBackedHistory::with_file(shell_db_path.clone(), None, None)
.expect("Failed to create shell.db");
drop(_db);
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args([
"--history-dir",
history_dir.to_str().unwrap(),
"history",
"import",
"--from",
"arf",
"--file",
shell_db_path.to_str().unwrap(),
])
.output()
.expect("Failed to run arf history import");
assert!(
!output.status.success(),
"Self-import of shell.db should fail, but succeeded"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Refusing to import") && stderr.contains("into itself"),
"Error should mention refusing self-import for shell.db, got: {}",
stderr
);
}
#[test]
fn test_history_import_arf_requires_file() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["history", "import", "--from", "arf"])
.output()
.expect("Failed to run arf history import");
assert!(
!output.status.success(),
"Import from arf without --file should fail"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("--file") && stderr.contains("required"),
"Error should mention --file is required, got: {}",
stderr
);
}
#[test]
fn test_eval_basic() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["-e", "1 + 1"])
.output()
.expect("Failed to run arf -e");
assert!(output.status.success(), "arf -e should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("[1] 2"), "Should output [1] 2: {}", stdout);
}
#[test]
fn test_eval_multiple_expressions() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["-e", "x <- 5\nx * 2"])
.output()
.expect("Failed to run arf -e");
assert!(output.status.success(), "arf -e should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("[1] 10"),
"Should output [1] 10: {}",
stdout
);
}
#[test]
fn test_eval_function() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["-e", "f <- function(x) { x + 1 }\nf(10)"])
.output()
.expect("Failed to run arf -e");
assert!(output.status.success(), "arf -e should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("[1] 11"),
"Function should return 11: {}",
stdout
);
}
#[test]
fn test_eval_error_handling() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["-e", "stop('Test error')"])
.output()
.expect("Failed to run arf -e");
assert!(
output.status.success(),
"arf -e should succeed even with R errors"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Test error") || stderr.contains("Error"),
"Should show error message: {}",
stderr
);
}
#[test]
fn test_eval_pipe_error() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["-e", "1 |> 1"])
.output()
.expect("Failed to run arf -e");
assert!(
output.status.success(),
"arf -e should succeed even with R errors"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Error") || stderr.contains("error"),
"Should show pipe error: {}",
stderr
);
}
#[test]
fn test_eval_reprex_mode() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["--reprex", "-e", "1 + 1"])
.output()
.expect("Failed to run arf --reprex -e");
assert!(output.status.success(), "arf --reprex -e should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("1 + 1"),
"Output should echo source code: {}",
stdout
);
assert!(
stdout.contains("#> [1] 2"),
"Output should be prefixed with #>: {}",
stdout
);
}
#[test]
fn test_eval_reprex_custom_comment() {
let mut config_file = NamedTempFile::new().expect("Failed to create temp config file");
writeln!(
config_file,
r###"[mode.reprex]
comment = "## "
"###
)
.expect("Failed to write config file");
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args([
"--config",
config_file.path().to_str().unwrap(),
"--reprex",
"-e",
"1 + 1",
])
.output()
.expect("Failed to run arf --reprex with config file");
assert!(
output.status.success(),
"arf with custom reprex comment should succeed"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("1 + 1"),
"Output should echo source code: {}",
stdout
);
assert!(
stdout.contains("## [1] 2"),
"Output should be prefixed with custom comment: {}",
stdout
);
}
#[test]
fn test_eval_reprex_cat_output() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["--reprex", "-e", r#"cat("hello")"#])
.output()
.expect("Failed to run arf --reprex -e cat()");
assert!(
output.status.success(),
"arf --reprex -e cat() should succeed"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains(r#"cat("hello")"#),
"Output should echo source code: {}",
stdout
);
assert!(
stdout.contains("#> hello"),
"cat() output should be prefixed with #>: {}",
stdout
);
}
#[test]
fn test_eval_reprex_cat_with_newline() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["--reprex", "-e", r#"cat("hello\n")"#])
.output()
.expect("Failed to run arf --reprex -e cat() with newline");
assert!(
output.status.success(),
"arf --reprex -e cat() with newline should succeed"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("#> hello"),
"cat() output with newline should be prefixed with #>: {}",
stdout
);
}
#[test]
fn test_script_file() {
let mut file = NamedTempFile::new().expect("Failed to create temp file");
writeln!(file, "x <- 5").expect("Failed to write");
writeln!(file, "y <- 10").expect("Failed to write");
writeln!(file, "x + y").expect("Failed to write");
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.arg("-f")
.arg(file.path())
.output()
.expect("Failed to run arf with script file");
assert!(output.status.success(), "arf -f script.R should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("[1] 15"),
"Script should output [1] 15: {}",
stdout
);
}
#[test]
fn test_script_file_function() {
let mut file = NamedTempFile::new().expect("Failed to create temp file");
writeln!(file, "f <- function(x) {{").expect("Failed to write");
writeln!(file, " x + 1").expect("Failed to write");
writeln!(file, "}}").expect("Failed to write");
writeln!(file, "f(10)").expect("Failed to write");
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.arg("-f")
.arg(file.path())
.output()
.expect("Failed to run arf with script file");
assert!(output.status.success(), "arf -f script.R should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("[1] 11"),
"Function should return 11: {}",
stdout
);
}
#[test]
fn test_script_file_reprex() {
let mut file = NamedTempFile::new().expect("Failed to create temp file");
writeln!(file, "1 + 1").expect("Failed to write");
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.arg("--reprex")
.arg("-f")
.arg(file.path())
.output()
.expect("Failed to run arf --reprex -f script.R");
assert!(
output.status.success(),
"arf --reprex -f script.R should succeed"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("1 + 1"),
"Output should echo source code: {}",
stdout
);
assert!(
stdout.contains("#> [1] 2"),
"Output should be prefixed with #>: {}",
stdout
);
}
#[test]
fn test_script_file_not_found() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.arg("-f")
.arg("/nonexistent/path/to/script.R")
.output()
.expect("Failed to run arf");
assert!(
!output.status.success(),
"arf should fail for non-existent file"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("Failed to read") || stderr.contains("No such file"),
"Should show file not found error: {}",
stderr
);
}
#[test]
fn test_positional_script_rejected() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.arg("some_script.R")
.output()
.expect("Failed to run arf");
assert!(
!output.status.success(),
"positional script arg should be rejected"
);
assert_eq!(
output.status.code(),
Some(2),
"positional script rejection should use clap's parse error exit code"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("some_script.R"),
"Error should mention the offending token: {}",
stderr
);
}
#[test]
fn test_eval_with_subcommand_rejected() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["-e", "1+1", "completions", "bash"])
.output()
.expect("Failed to run arf");
assert!(
!output.status.success(),
"--eval with subcommand should be rejected"
);
assert_eq!(
output.status.code(),
Some(2),
"--eval with subcommand should exit with code 2"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("--eval")
&& stderr.contains("cannot be used")
&& stderr.contains("completions"),
"Should show conflict error mentioning --eval and the subcommand: {}",
stderr
);
}
#[test]
fn test_file_with_subcommand_rejected() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args(["-f", "some.R", "completions", "bash"])
.output()
.expect("Failed to run arf");
assert!(
!output.status.success(),
"--file with subcommand should be rejected"
);
assert_eq!(
output.status.code(),
Some(2),
"--file with subcommand should exit with code 2"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("--file")
&& stderr.contains("cannot be used")
&& stderr.contains("completions"),
"Should show conflict error mentioning --file and the subcommand: {}",
stderr
);
}
#[test]
fn test_r_completion_functions() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args([
"-e",
r#"
utils:::.assignLinebuffer("pri")
utils:::.assignEnd(3)
token <- utils:::.guessTokenFromLine()
print(token)
"#,
])
.output()
.expect("Failed to run arf -e");
assert!(output.status.success(), "arf -e should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("pri"), "Token should be 'pri': {}", stdout);
}
#[test]
fn test_r_complete_token() {
let output = Command::new(env!("CARGO_BIN_EXE_arf"))
.args([
"-e",
r#"
utils:::.assignLinebuffer("prin")
utils:::.assignEnd(4L)
utils:::.guessTokenFromLine()
utils:::.completeToken()
comps <- utils:::.retrieveCompletions()
print(comps)
"#,
])
.output()
.expect("Failed to run arf -e");
assert!(output.status.success(), "arf -e should succeed");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("print"),
"Completions should include 'print': {}",
stdout
);
}