#![cfg(feature = "python")]
use quarb::{QueryResult, Value};
use std::sync::Arc;
const HELPERS: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/helpers.py");
fn doc() -> quarb_json::JsonAdapter {
quarb_json::JsonAdapter::parse(
r#"{"w":["pear","apple","fig"],
"p":[{"n":"a","x":2},{"n":"b","x":1}]}"#,
)
.unwrap()
}
fn mounted() -> Arc<dyn quarb::CommandLibrary> {
Arc::new(quarb_pyvalue::ModuleEnvironment::mount("py", HELPERS).unwrap())
}
fn run(q: &str) -> quarb::Result<Vec<String>> {
let run = quarb::Run {
libraries: vec![mounted()],
..quarb::Run::ambient()
};
let out = quarb::Query::parse(q, &doc())?.run(&doc(), &run)?;
Ok(match out.result {
QueryResult::Values(vs) => vs.iter().map(Value::to_string).collect(),
QueryResult::Nodes(ns) => ns.iter().map(|n| format!("{n:?}")).collect(),
})
}
fn refuse(q: &str) -> String {
run(q).unwrap_err().to_string()
}
#[test]
fn the_mount_imports_the_module_or_refuses() {
let e = quarb_pyvalue::ModuleEnvironment::mount("py", "/no/such/helpers.py").unwrap_err();
assert!(e.contains("'py=python:/no/such/helpers.py'"), "{e}");
let e = quarb_pyvalue::ModuleEnvironment::mount("py", "no_such_module_xyz").unwrap_err();
assert!(e.contains("No module named"), "{e}");
let m = quarb_pyvalue::ModuleEnvironment::mount("txt", "textwrap").unwrap();
assert_eq!(quarb::CommandLibrary::prefix(&m), "txt");
}
#[test]
fn after_the_plain_pipe_the_input_is_the_first_positional() {
assert_eq!(
run("/w/* | :: | py:shout").unwrap(),
vec!["PEAR", "APPLE", "FIG"]
);
assert_eq!(run("= 0.666 | py:round_to(1)").unwrap(), vec!["0.7"]);
assert_eq!(run("= 0.666 | py:round_to(--n=2)").unwrap(), vec!["0.67"]);
}
#[test]
fn the_topic_variable_places_the_input() {
assert_eq!(
run(r#"/w/* | :: | py:sub("^p"; "P"; $_)"#).unwrap(),
vec!["Pear", "apple", "fig"]
);
assert_eq!(
run(r#"/w/* | :: | py:tag(--data=$_ --k="w")"#).unwrap(),
vec!["%(w = \"pear\")", "%(w = \"apple\")", "%(w = \"fig\")"]
);
}
#[test]
fn values_cross_as_the_hosts_own() {
assert_eq!(
run("/w/* | :: | py:kind").unwrap(),
vec!["str", "str", "str"]
);
assert_eq!(run("/p/* | ::x | py:kind").unwrap(), vec!["int", "int"]);
assert_eq!(
run("/p/* | %(n = ::n; x = ::x) | py:kind").unwrap(),
vec!["dict", "dict"]
);
assert_eq!(run("/p/0 | @(::n; ::x) | py:kind").unwrap(), vec!["list"]);
assert_eq!(run("/p/* | py:kind").unwrap(), vec!["dict", "dict"]);
}
#[test]
fn after_the_aggregation_pipe_one_call_over_the_stream() {
assert_eq!(run("/p/* | ::x @| py:total").unwrap(), vec!["3"]);
assert_eq!(run("/p/* | ::x @| py:spread").unwrap(), vec!["4", "2"]);
assert_eq!(
run("/p/* | ::x @| .n(count) @| py:spread | %(v = $_; n = $.n)").unwrap(),
vec!["%(v = 4; n = 2)", "%(v = 2; n = 2)"]
);
}
#[test]
fn a_raised_exception_is_null_per_capsa_and_a_refusal_over_the_context() {
assert_eq!(
run("/w/* | :: | py:fail | default(\"nil\")").unwrap(),
vec!["nil", "nil", "nil"]
);
let e = refuse("/w/* | :: @| py:fail");
assert!(e.contains("py:fail") && e.contains("no"), "{e}");
}
#[test]
fn an_unknown_name_refuses_naming_the_environment() {
let e = refuse("/w/* | :: | py:nope");
assert!(
e.contains("environment 'py'") && e.contains("has no 'nope'") && e.contains("helpers.py"),
"{e}"
);
}
#[test]
fn an_environment_is_not_pure_so_nothing_is_withheld() {
let text = run(&format!("= \"{HELPERS}\" | py:read_file")).unwrap();
assert!(text[0].contains("def shout"), "{}", text[0]);
}
#[test]
fn the_cli_mounts_a_module_and_refuses_an_interpreter() {
let dir = std::env::temp_dir().join(format!("quarb-python-env-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let json = dir.join("w.json");
std::fs::write(&json, r#"{"w":["pear","fig"]}"#).unwrap();
let out = std::process::Command::new(env!("CARGO_BIN_EXE_qua"))
.arg("/w/* | :: | py:shout")
.arg(format!("py=python:{HELPERS}"))
.arg(&json)
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
assert_eq!(String::from_utf8_lossy(&out.stdout), "PEAR\nFIG\n");
let out = std::process::Command::new(env!("CARGO_BIN_EXE_qua"))
.arg("/w/* | :: | py:shout")
.arg("py=python:python3")
.arg(&json)
.output()
.unwrap();
assert!(!out.status.success());
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("an interpreter alone is no target"), "{err}");
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn headless_the_first_operand_fills_the_slot_when_the_operands_reach() {
let math: Arc<dyn quarb::CommandLibrary> =
Arc::new(quarb_pyvalue::ModuleEnvironment::mount("m", "math").unwrap());
let run_math = |q: &str| -> quarb::Result<Vec<String>> {
let run = quarb::Run {
libraries: vec![math.clone()],
..quarb::Run::ambient()
};
let out = quarb::Query::parse_with(q, &quarb::Defs::default(), &doc(), &run)?
.run(&doc(), &run)?;
Ok(match out.result {
QueryResult::Values(vs) => vs.iter().map(Value::to_string).collect(),
QueryResult::Nodes(ns) => ns.iter().map(|n| format!("{n:?}")).collect(),
})
};
assert_eq!(run_math("= m:pow(2; 3)").unwrap(), vec!["8"]);
assert_eq!(run_math("= 2 | m:pow(3)").unwrap(), vec!["8"]);
assert_eq!(run_math("= 2 | %(p = m:pow(3))").unwrap(), vec!["%(p = 8)"]);
assert_eq!(
run_math("= 2 | %(p = m:pow(2; 3))").unwrap(),
vec!["%(p = 8)"]
);
let e = run_math("= 2 | m:pow(2; 3)").unwrap_err().to_string();
assert!(e.contains("takes 1 operand, 2 given"), "{e}");
assert_eq!(
run("/p/* | ::x | %(r = py:round_to(1))").unwrap(),
vec!["%(r = 2)", "%(r = 1)"]
);
assert_eq!(
run("/p/0 | %(r = py:round_to(2.567; 1))").unwrap(),
vec!["%(r = 2.6)"]
);
}