8000 minigraf/src/repl.rs at v1.1.0 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

Latest commit

 

History

History
595 lines (532 loc) · 19.5 KB

repl.rs

File metadata and controls

595 lines (532 loc) · 19.5 KB
#[test]
fn multiline_command_is_buffered_until_complete() {
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
// The first line is an incomplete command (unmatched paren); the second
// line completes it — exercises the `is_multiline = true` branch.
repl.run_impl(
std::io::Cursor::new(b"(query [:find ?e\n:where [?e :x 1]])\nEXIT\n"),
false,
);
}
#[test]
fn interactive_mode_prints_output_after_command() {
// interactive=true exercises the `println!()` after a successful command.
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_impl(
std::io::Cursor::new(b"(query [:find ?e :where [?e :x 1]])\nEXIT\n"),
true,
);
}
#[test]
fn read_error_exits_loop() {
struct ErrorReader;
impl std::io::Read for ErrorReader {
fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"simulated read error",
))
}
}
impl std::io::BufRead for ErrorReader {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
Err(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"simulated read error",
))
}
fn consume(&mut self, _amt: usize) {}
}
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_impl(ErrorReader, false);
}
#[test]
fn query_integer_result_covers_format_value_integer() {
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_impl(
std::io::Cursor::new(
b"(transact [[:db/add \"e1\" :item/count 42]])\n\
(query [:find ?c :where [_ :item/count ?c]])\n\
EXIT\n",
),
false,
);
}
#[test]
fn query_float_result_covers_format_value_float() {
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_impl(
std::io::Cursor::new(
b"(transact [[:db/add \"e1\" :item/price 9.99]])\n\
(query [:find ?p :where [_ :item/price ?p]])\n\
EXIT\n",
),
false,
);
}
#[test]
fn query_keyword_result_covers_format_value_keyword() {
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_impl(
std::io::Cursor::new(
b"(transact [[:db/add \"e1\" :item/status :active]])\n\
(query [:find ?s :where [_ :item/status ?s]])\n\
EXIT\n",
),
false,
);
}
#[test]
fn query_boolean_result_covers_format_value_boolean() {
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_impl(
std::io::Cursor::new(
b"(transact [[:db/add \"e1\" :item/in-stock true]])\n\
(query [:find ?s :where [_ :item/in-stock ?s]])\n\
EXIT\n",
),
false,
);
}
#[test]
fn rule_definition_covers_result_ok_arm() {
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_impl(
std::io::Cursor::new(
b"(rule [(parent ?x ?y) [?x :parent/of ?y]])\n\
EXIT\n",
),
false,
);
}
#[test]
fn multiline_command_in_interactive_mode_covers_continuation_prompt() {
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
// First line is incomplete (unmatched paren) → no continuation prompt (suppressed in multiline mode).
repl.run_impl(
std::io::Cursor::new(b"(query [:find ?e\n:where [?e :x 1]])\nEXIT\n"),
true,
);
}
// --- Direct tests for print_result ---
#[test]
fn print_result_transacted() {
use crate::query::datalog::QueryResult as DResult;
Repl::print_result(DResult::Transacted(12345678));
}
#[test]
fn print_result_retracted() {
use crate::query::datalog::QueryResult as DResult;
Repl::print_result(DResult::Retracted(12345678));
}
#[test]
fn print_result_ok() {
use crate::query::datalog::QueryResult as DResult;
Repl::print_result(DResult::Ok);
}
#[test]
fn print_result_query_no_results() {
use crate::query::datalog::QueryResult as DResult;
Repl::print_result(DResult::QueryResults {
vars: vec!["?x".to_string()],
results: vec![],
});
}
#[test]
fn print_result_query_with_rows() {
use crate::graph::types::Value;
use crate::query::datalog::QueryResult as DResult;
Repl::print_result(DResult::QueryResults {
vars: vec!["?x".to_string(), "?y".to_string()],
results: vec![vec![Value::String("hello".to_string()), Value::Integer(42)]],
});
}
// --- Direct tests for format_value ---
#[test]
fn format_value_string() {
use crate::graph::types::Value;
assert_eq!(
Repl::format_value(&Value::String("hi".to_string())),
"\"hi\""
);
}
#[test]
fn format_value_integer() {
use crate::graph::types::Value;
assert_eq!(Repl::format_value(&Value::Integer(7)), "7");
}
#[test]
fn format_value_float() {
use crate::graph::types::Value;
assert_eq!(Repl::format_value(&Value::Float(3.14)), "3.14");
}
#[test]
fn format_value_boolean() {
use crate::graph::types::Value;
assert_eq!(Repl::format_value(&Value::Boolean(true)), "true");
}
#[test]
fn format_value_ref() {
use crate::graph::types::Value;
let id = uuid::Uuid::new_v4();
assert_eq!(Repl::format_value(&Value::Ref(id)), format!("#uuid {}", id));
}
#[test]
fn format_value_keyword() {
use crate::graph::types::Value;
assert_eq!(
Repl::format_value(&Value::Keyword(":active".to_string())),
":active"
);
}
#[test]
fn format_value_null() {
use crate::graph::types::Value;
assert_eq!(Repl::format_value(&Value::Null), "nil");
}
// --- Direct tests for is_command_complete ---
#[test]
fn is_command_complete_handles_escaped_quote_in_string() {
// Exercises the `escape_next` branches (lines 135-136, 140-141).
assert!(Repl::is_command_complete(
r#"(query [:find ?x :where [?x :name "he said \"hi\""]])"#
));
}
#[test]
fn is_command_complete_default_arm() {
// A plain character inside a string hits the `_ => {}` arm.
assert!(Repl::is_command_complete(r#"(foo "bar")"#));
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn run_with_init_sources_file() {
// run_with_init only processes the init file (no interactive loop), so
// this test is safe to run in any environment without blocking on stdin.
use std::io::Write;
let mut tmp = tempfile::NamedTempFile::new().expect("tempfile");
writeln!(
tmp,
"(transact [[:alice :person/name \"Alice\"]])\n\
(rule [(has-name ?e ?n) [?e :person/name ?n]])"
)
.expect("write init");
tmp.flush().expect("flush");
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_with_init(tmp.path());
let result = db
.execute("(query [:find ?n :where (has-name _ ?n)])")
.expect("query");
match result {
crate::query::datalog::QueryResult::QueryResults { results, .. } => {
assert_eq!(results.len(), 1);
}
_ => panic!("expected query results"),
}
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn run_with_init_missing_file_prints_error() {
// A non-existent init path should print an error to stderr but not panic.
// run_with_init no longer starts the interactive loop, so no stdin blocking.
let db = Minigraf::in_memory().expect("in-memory db");
let repl = db.repl();
repl.run_with_init(std::path::Path::new("/nonexistent/path/rules.datalog"));
}
}
0