use assert_cmd::Command;
use predicates::prelude::*;
fn ruchy_cmd() -> Command {
assert_cmd::cargo::cargo_bin_cmd!("ruchy")
}
#[test]
fn test_io_println_simple() {
ruchy_cmd()
.arg("-e")
.arg("println(42)")
.assert()
.success()
.stdout(predicate::str::contains("42"));
}
#[test]
fn test_io_println_multiple_args() {
ruchy_cmd()
.arg("-e")
.arg(r#"println("Hello", "World")"#)
.assert()
.success()
.stdout(predicate::str::contains("Hello").and(predicate::str::contains("World")));
}
#[test]
fn test_io_print_no_newline() {
ruchy_cmd()
.arg("-e")
.arg(r#"print("test"); print("line")"#)
.assert()
.success()
.stdout(predicate::str::contains("test").and(predicate::str::contains("line")));
}
#[test]
fn test_io_dbg_debug_output() {
ruchy_cmd().arg("-e").arg(r"dbg(42)").assert().success();
}
#[test]
fn test_type_len_string() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(len("hello"))"#)
.assert()
.success()
.stdout(predicate::str::contains("5"));
}
#[test]
fn test_type_len_array() {
ruchy_cmd()
.arg("-e")
.arg("println(len([1, 2, 3]))")
.assert()
.success()
.stdout(predicate::str::contains("3"));
}
#[test]
fn test_type_len_empty_array() {
ruchy_cmd()
.arg("-e")
.arg("println(len([]))")
.assert()
.success()
.stdout(predicate::str::contains("0"));
}
#[test]
fn test_type_type_of_integer() {
ruchy_cmd()
.arg("-e")
.arg(r"println(type_of(42))")
.assert()
.success();
}
#[test]
fn test_type_type_of_string() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of("hello"))"#)
.assert()
.success();
}
#[test]
fn test_type_type_of_array() {
ruchy_cmd()
.arg("-e")
.arg(r"println(type_of([1, 2, 3]))")
.assert()
.success();
}
#[test]
fn test_type_is_nil_true() {
ruchy_cmd()
.arg("-e")
.arg("println(is_nil(()))")
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_type_is_nil_false() {
ruchy_cmd()
.arg("-e")
.arg("println(is_nil(42))")
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_assert_eq_pass() {
ruchy_cmd()
.arg("-e")
.arg("assert_eq(5, 5)")
.assert()
.success();
}
#[test]
fn test_assert_eq_fail() {
ruchy_cmd()
.arg("-e")
.arg("assert_eq(5, 3)")
.assert()
.failure();
}
#[test]
fn test_assert_pass() {
ruchy_cmd().arg("-e").arg("assert(true)").assert().success();
}
#[test]
fn test_assert_fail() {
ruchy_cmd()
.arg("-e")
.arg("assert(false)")
.assert()
.failure();
}
#[test]
fn test_math_sqrt() {
ruchy_cmd()
.arg("-e")
.arg("println(sqrt(16.0))")
.assert()
.success()
.stdout(predicate::str::contains("4"));
}
#[test]
fn test_math_pow() {
ruchy_cmd()
.arg("-e")
.arg("println(pow(2.0, 3.0))")
.assert()
.success()
.stdout(predicate::str::contains("8"));
}
#[test]
fn test_math_abs_positive() {
ruchy_cmd()
.arg("-e")
.arg("println(abs(5))")
.assert()
.success()
.stdout(predicate::str::contains("5"));
}
#[test]
fn test_math_abs_negative() {
ruchy_cmd()
.arg("-e")
.arg("println(abs(-5))")
.assert()
.success()
.stdout(predicate::str::contains("5"));
}
#[test]
fn test_math_min() {
ruchy_cmd()
.arg("-e")
.arg("println(min(3, 7))")
.assert()
.success()
.stdout(predicate::str::contains("3"));
}
#[test]
fn test_math_max() {
ruchy_cmd()
.arg("-e")
.arg("println(max(3, 7))")
.assert()
.success()
.stdout(predicate::str::contains("7"));
}
#[test]
fn test_math_floor() {
ruchy_cmd()
.arg("-e")
.arg("println(floor(3.7))")
.assert()
.success()
.stdout(predicate::str::contains("3"));
}
#[test]
fn test_math_ceil() {
ruchy_cmd()
.arg("-e")
.arg("println(ceil(3.2))")
.assert()
.success()
.stdout(predicate::str::contains("4"));
}
#[test]
fn test_math_round() {
ruchy_cmd()
.arg("-e")
.arg("println(round(3.5))")
.assert()
.success()
.stdout(predicate::str::contains("4"));
}
#[test]
fn test_string_to_string_int() {
ruchy_cmd()
.arg("-e")
.arg(r"println(to_string(42))")
.assert()
.success()
.stdout(predicate::str::contains("42"));
}
#[test]
fn test_string_to_string_float() {
ruchy_cmd()
.arg("-e")
.arg(r"println(to_string(3.14))")
.assert()
.success()
.stdout(predicate::str::contains("3.14"));
}
#[test]
fn test_string_to_string_bool() {
ruchy_cmd()
.arg("-e")
.arg(r"println(to_string(true))")
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_string_parse_int_valid() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(parse_int("42"))"#)
.assert()
.success()
.stdout(predicate::str::contains("42"));
}
#[test]
fn test_string_parse_int_invalid() {
ruchy_cmd()
.arg("-e")
.arg(r#"parse_int("not a number")"#)
.assert()
.failure();
}
#[test]
fn test_string_parse_float_valid() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(parse_float("3.14"))"#)
.assert()
.success()
.stdout(predicate::str::contains("3.14"));
}
#[test]
fn test_string_parse_float_invalid() {
ruchy_cmd()
.arg("-e")
.arg(r#"parse_float("not a float")"#)
.assert()
.failure();
}
#[test]
fn test_collection_push() {
let code = r"
let arr = [1, 2, 3];
let arr = push(arr, 4);
println(len(arr))
";
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("4"));
}
#[test]
fn test_collection_pop() {
let code = r"
let arr = [1, 2, 3];
let val = pop(arr);
println(type_of(val))
";
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
#[ignore = "Design choice: pop() returns nil on empty (Python-style), not error (Rust-style)"]
fn test_collection_pop_empty() {
ruchy_cmd()
.arg("-e")
.arg("let arr = []; pop(arr)")
.assert()
.failure();
}
#[test]
fn test_collection_reverse() {
let code = r"
let arr = [1, 2, 3];
let arr = reverse(arr);
println(arr[0])
";
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("3"));
}
#[test]
fn test_collection_sort_ascending() {
let code = r"
let arr = [3, 1, 2];
let arr = sort(arr);
println(arr[0])
";
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("1"));
}
#[test]
fn test_env_args() {
ruchy_cmd()
.arg("-e")
.arg("println(type_of(env_args()))")
.assert()
.success();
}
#[test]
fn test_env_var_path() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(env_var("PATH")))"#)
.assert()
.success();
}
#[test]
fn test_env_set_var() {
let code = r#"
env_set_var("TEST_VAR", "test_value");
println(env_var("TEST_VAR"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("test_value"));
}
#[test]
fn test_env_remove_var() {
let code = r#"
env_set_var("TEST_VAR_2", "test");
env_remove_var("TEST_VAR_2")
"#;
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn test_env_vars() {
ruchy_cmd()
.arg("-e")
.arg("println(type_of(env_vars()))")
.assert()
.success();
}
#[test]
fn test_env_current_dir() {
ruchy_cmd()
.arg("-e")
.arg("println(type_of(env_current_dir()))")
.assert()
.success();
}
#[test]
fn test_env_temp_dir() {
ruchy_cmd()
.arg("-e")
.arg("println(type_of(env_temp_dir()))")
.assert()
.success();
}
#[test]
fn test_fs_write_read() {
let code = r#"
fs_write("/tmp/test_ruchy_builtin.txt", "Hello, World!");
println(fs_read("/tmp/test_ruchy_builtin.txt"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("Hello, World!"));
}
#[test]
fn test_fs_exists_true() {
let code = r#"
fs_write("/tmp/test_ruchy_exists.txt", "test");
println(fs_exists("/tmp/test_ruchy_exists.txt"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_fs_exists_false() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(fs_exists("/tmp/nonexistent_file_xyz.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_fs_create_dir() {
let code = r#"
fs_create_dir("/tmp/test_ruchy_dir_123");
println(fs_exists("/tmp/test_ruchy_dir_123"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_fs_remove_file() {
let code = r#"
fs_write("/tmp/test_ruchy_remove.txt", "test");
fs_remove_file("/tmp/test_ruchy_remove.txt");
println(fs_exists("/tmp/test_ruchy_remove.txt"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_fs_remove_dir() {
let code = r#"
fs_create_dir("/tmp/test_ruchy_remove_dir");
fs_remove_dir("/tmp/test_ruchy_remove_dir");
println(fs_exists("/tmp/test_ruchy_remove_dir"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_fs_copy() {
let code = r#"
fs_write("/tmp/test_ruchy_copy_src.txt", "content");
fs_copy("/tmp/test_ruchy_copy_src.txt", "/tmp/test_ruchy_copy_dst.txt");
println(fs_read("/tmp/test_ruchy_copy_dst.txt"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("content"));
}
#[test]
fn test_fs_rename() {
let code = r#"
fs_write("/tmp/test_ruchy_rename_old.txt", "data");
fs_rename("/tmp/test_ruchy_rename_old.txt", "/tmp/test_ruchy_rename_new.txt");
println(fs_exists("/tmp/test_ruchy_rename_new.txt"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_fs_metadata() {
let code = r#"
fs_write("/tmp/test_ruchy_metadata.txt", "test");
println(type_of(fs_metadata("/tmp/test_ruchy_metadata.txt")))
"#;
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn test_fs_read_dir() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(fs_read_dir("/tmp")))"#)
.assert()
.success();
}
#[test]
fn test_fs_is_file_true() {
let code = r#"
fs_write("/tmp/test_ruchy_is_file.txt", "test");
println(fs_is_file("/tmp/test_ruchy_is_file.txt"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_fs_is_file_false() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(fs_is_file("/tmp"))"#)
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_path_join() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_join("/tmp", "test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("/tmp").and(predicate::str::contains("test.txt")));
}
#[test]
fn test_path_join_many() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_join_many(["/tmp", "sub", "test.txt"]))"#)
.assert()
.success()
.stdout(predicate::str::contains("/tmp"));
}
#[test]
fn test_path_parent() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_parent("/tmp/test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("/tmp"));
}
#[test]
fn test_path_file_name() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_file_name("/tmp/test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("test.txt"));
}
#[test]
fn test_path_file_stem() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_file_stem("/tmp/test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("test"));
}
#[test]
fn test_path_extension() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_extension("/tmp/test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("txt"));
}
#[test]
fn test_path_is_absolute_true() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_is_absolute("/tmp/test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_path_is_absolute_false() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_is_absolute("test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_path_is_relative_true() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_is_relative("test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_path_is_relative_false() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_is_relative("/tmp/test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_path_canonicalize() {
let code = r#"
fs_write("/tmp/test_ruchy_canonical.txt", "test");
println(type_of(path_canonicalize("/tmp/test_ruchy_canonical.txt")))
"#;
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn test_path_with_extension() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_with_extension("/tmp/test.txt", "rs"))"#)
.assert()
.success()
.stdout(predicate::str::contains(".rs"));
}
#[test]
fn test_path_with_file_name() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_with_file_name("/tmp/test.txt", "new.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("new.txt"));
}
#[test]
fn test_path_components() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(path_components("/tmp/test.txt")))"#)
.assert()
.success();
}
#[test]
fn test_path_normalize() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(path_normalize("/tmp/./test.txt"))"#)
.assert()
.success()
.stdout(predicate::str::contains("/tmp/test.txt"));
}
#[test]
fn test_json_stringify() {
let code = r#"
let obj = {"name": "Alice", "age": 30};
println(json_stringify(obj))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("Alice"));
}
#[test]
fn test_json_parse() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(json_parse("{\"key\": \"value\"}")))"#)
.assert()
.success();
}
#[test]
fn test_json_pretty() {
let code = r#"
let obj = {"key": "value"};
println(json_pretty(obj))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("key"));
}
#[test]
fn test_json_write_read() {
let code = r#"
let obj = {"test": 123};
json_write("/tmp/test_ruchy_json.json", obj);
let loaded = json_read("/tmp/test_ruchy_json.json");
println(type_of(loaded))
"#;
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn test_json_validate_valid() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(json_validate("{\"valid\": true}"))"#)
.assert()
.success()
.stdout(predicate::str::contains("true"));
}
#[test]
fn test_json_validate_invalid() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(json_validate("{invalid json}"))"#)
.assert()
.success()
.stdout(predicate::str::contains("false"));
}
#[test]
fn test_json_type() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(json_type("{\"key\": \"value\"}"))"#)
.assert()
.success();
}
#[test]
fn test_json_merge() {
let code = r#"
let obj1 = {"a": 1};
let obj2 = {"b": 2};
let merged = json_merge(obj1, obj2);
println(type_of(merged))
"#;
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn test_json_get() {
let code = r#"
let obj = {"name": "Bob"};
println(json_get(obj, "name"))
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("Bob"));
}
#[test]
fn test_json_set() {
let code = r#"
let obj = {"old": "value"};
json_set(obj, "new", "data");
println(type_of(obj))
"#;
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
#[ignore = "Network tests require external services - skip in CI/offline environments"]
fn test_http_get() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(http_get("https://httpbin.org/get")))"#)
.assert()
.success();
}
#[test]
#[ignore = "Network tests require external services - skip in CI/offline environments"]
fn test_http_post() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(http_post("https://httpbin.org/post", "{}")))"#)
.assert()
.success();
}
#[test]
#[ignore = "Network tests require external services - skip in CI/offline environments"]
fn test_http_put() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(http_put("https://httpbin.org/put", "{}")))"#)
.assert()
.success();
}
#[test]
#[ignore = "Network tests require external services - skip in CI/offline environments"]
fn test_http_delete() {
ruchy_cmd()
.arg("-e")
.arg(r#"println(type_of(http_delete("https://httpbin.org/delete")))"#)
.assert()
.success();
}
#[test]
fn property_len_after_push() {
for size in 0..=10 {
let code = format!(
r"
let arr = [{}];
let before = len(arr);
let arr = push(arr, 999);
let after = len(arr);
assert_eq(after, before + 1)
",
(0..size)
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(", ")
);
ruchy_cmd().arg("-e").arg(&code).assert().success();
}
}
#[test]
fn property_reverse_twice_identity() {
let code = r"
let arr = [1, 2, 3, 4, 5];
let original_first = arr[0];
let arr = reverse(arr);
let arr = reverse(arr);
assert_eq(arr[0], original_first)
";
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn property_abs_idempotent() {
for val in [-100, -10, -1, 0, 1, 10, 100] {
let code = format!("assert_eq(abs(abs({val})), abs({val}))");
ruchy_cmd().arg("-e").arg(&code).assert().success();
}
}
#[test]
fn property_min_max_commutative() {
for (a, b) in [(1, 5), (10, 3), (7, 7), (-5, 10)] {
let code = format!("assert_eq(min({a}, {b}) + max({a}, {b}), {a} + {b})");
ruchy_cmd().arg("-e").arg(&code).assert().success();
}
}
#[test]
fn property_to_string_parse_int_roundtrip() {
for val in [0, 1, 42, 100, 999] {
let code = format!(r"assert_eq(parse_int(to_string({val})), {val})");
ruchy_cmd().arg("-e").arg(&code).assert().success();
}
}
#[test]
fn property_path_join_parent() {
ruchy_cmd()
.arg("-e")
.arg(
r#"
let joined = path_join("/tmp", "test.txt");
let parent = path_parent(joined);
assert_eq(parent, "/tmp")
"#,
)
.assert()
.success();
}
#[test]
fn integration_file_operations() {
let code = r#"
let path = "/tmp/test_ruchy_integration.txt";
// Write
fs_write(path, "integration test");
// Check exists
let exists = fs_exists(path);
println(exists);
// Read back
let content = fs_read(path);
println(content);
// Check is file
let is_file = fs_is_file(path);
println(is_file);
// Get metadata
let meta = fs_metadata(path);
println(type_of(meta));
// Clean up
fs_remove_file(path);
let exists_after = fs_exists(path);
println(exists_after)
"#;
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("true")) .stdout(predicate::str::contains("integration test")); }
#[test]
fn integration_path_manipulation() {
let code = r#"
let base = "/tmp";
let file = "test.txt";
let joined = path_join(base, file);
let parent = path_parent(joined);
assert_eq(parent, "/tmp");
let name = path_file_name(joined);
assert_eq(name, "test.txt");
let stem = path_file_stem(joined);
assert_eq(stem, "test");
let ext = path_extension(joined);
assert_eq(ext, "txt");
assert(path_is_absolute(joined));
assert_eq(path_is_relative(file), true)
"#;
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn integration_collection_operations() {
let code = r"
let arr = [5, 2, 8, 1, 9];
// Original length
assert_eq(len(arr), 5);
// Push (returns new array)
let arr = push(arr, 3);
assert_eq(len(arr), 6);
// Sort (returns new array)
let arr = sort(arr);
assert_eq(arr[0], 1);
assert_eq(arr[5], 9);
// Pop (returns element, doesn't affect array)
let last = pop(arr);
assert_eq(last, 9);
assert_eq(len(arr), 6);
// Reverse (returns new array)
let arr = reverse(arr);
assert_eq(arr[0], 9)
";
ruchy_cmd().arg("-e").arg(code).assert().success();
}
#[test]
fn integration_math_operations() {
let code = r"
let x = -25.7;
// Abs
let abs_x = abs(x);
println(abs_x);
// Floor
let floored = floor(abs_x);
println(floored);
// Sqrt
let sqrt_val = sqrt(floored);
println(sqrt_val);
// Pow
let squared = pow(sqrt_val, 2.0);
println(squared);
// Min/Max
let min_val = min(squared, 30.0);
let max_val = max(squared, 30.0);
println(min_val);
println(max_val)
";
ruchy_cmd()
.arg("-e")
.arg(code)
.assert()
.success()
.stdout(predicate::str::contains("25")) .stdout(predicate::str::contains("5")); }