This commit is contained in:
2026-04-02 21:55:57 -04:00
parent 29d8dfb608
commit 06f6a587de
21 changed files with 673 additions and 457 deletions

View File

@ -1463,6 +1463,18 @@ fn test_queryer_0_8() {
crate::tests::runner::run_test_case(&path, 0, 8).unwrap();
}
#[test]
fn test_queryer_0_9() {
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 9).unwrap();
}
#[test]
fn test_queryer_0_10() {
let path = format!("{}/fixtures/queryer.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 10).unwrap();
}
#[test]
fn test_not_0_0() {
let path = format!("{}/fixtures/not.json", env!("CARGO_MANIFEST_DIR"));

View File

@ -14,7 +14,7 @@ where
}
// Type alias for easier reading
type CompiledSuite = Arc<Vec<(Suite, Arc<crate::database::Database>)>>;
type CompiledSuite = Arc<Vec<(Suite, Arc<Result<Arc<crate::database::Database>, crate::drop::Drop>>)>>;
// Global cache mapping filename -> Vector of (Parsed JSON suite, Compiled Database)
static CACHE: OnceLock<RwLock<HashMap<String, CompiledSuite>>> = OnceLock::new();
@ -43,19 +43,11 @@ fn get_cached_file(path: &str) -> CompiledSuite {
let mut compiled_suites = Vec::new();
for suite in suites {
let db_result = crate::database::Database::new(&suite.database);
if let Err(drop) = db_result {
let error_messages: Vec<String> = drop
.errors
.into_iter()
.map(|e| format!("Error {} at path {}: {}", e.code, e.details.path, e.message))
.collect();
panic!(
"System Setup Compilation failed for {}:\n{}",
path,
error_messages.join("\n")
);
}
compiled_suites.push((suite, Arc::new(db_result.unwrap())));
let compiled_db = match db_result {
Ok(db) => Ok(Arc::new(db)),
Err(drop) => Err(drop),
};
compiled_suites.push((suite, Arc::new(compiled_db)));
}
let new_data = Arc::new(compiled_suites);
@ -85,11 +77,36 @@ pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<()
let test = &group.tests[case_idx];
let mut failures = Vec::<String>::new();
// For validate/merge/query, if setup failed we must structurally fail this test
let db_unwrapped = if test.action.as_str() != "compile" && test.action.as_str() != "database_compile" {
match &**db {
Ok(valid_db) => Some(valid_db.clone()),
Err(drop) => {
let error_messages: Vec<String> = drop
.errors
.iter()
.map(|e| format!("Error {} at path {}: {}", e.code, e.details.path, e.message))
.collect();
failures.push(format!(
"[{}] Cannot run '{}' test '{}': System Setup Compilation structurally failed:\n{}",
group.description, test.action, test.description, error_messages.join("\n")
));
None
}
}
} else {
None
};
if !failures.is_empty() {
return Err(failures.join("\n"));
}
// 4. Run Tests
match test.action.as_str() {
"compile" => {
let result = test.run_compile(db.clone());
"compile" | "database_compile" => {
let result = test.run_compile(db);
if let Err(e) = result {
println!("TEST COMPILE ERROR FOR '{}': {}", test.description, e);
failures.push(format!(
@ -99,7 +116,7 @@ pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<()
}
}
"validate" => {
let result = test.run_validate(db.clone());
let result = test.run_validate(db_unwrapped.unwrap());
if let Err(e) = result {
println!("TEST VALIDATE ERROR FOR '{}': {}", test.description, e);
failures.push(format!(
@ -109,7 +126,7 @@ pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<()
}
}
"merge" => {
let result = test.run_merge(db.clone());
let result = test.run_merge(db_unwrapped.unwrap());
if let Err(e) = result {
println!("TEST MERGE ERROR FOR '{}': {}", test.description, e);
failures.push(format!(
@ -119,7 +136,7 @@ pub fn run_test_case(path: &str, suite_idx: usize, case_idx: usize) -> Result<()
}
}
"query" => {
let result = test.run_query(db.clone());
let result = test.run_query(db_unwrapped.unwrap());
if let Err(e) = result {
println!("TEST QUERY ERROR FOR '{}': {}", test.description, e);
failures.push(format!(

View File

@ -35,21 +35,21 @@ fn default_action() -> String {
}
impl Case {
pub fn run_compile(&self, _db: Arc<Database>) -> Result<(), String> {
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
pub fn run_compile(
&self,
db_res: &Result<Arc<Database>, crate::drop::Drop>,
) -> Result<(), String> {
let expect = match &self.expect {
Some(e) => e,
None => return Ok(()),
};
// We assume db has already been setup and compiled successfully by runner.rs's `jspg_setup`
// We just need to check if there are compilation errors vs expected success
let got_success = true; // Setup ensures success unless setup fails, which runner handles
let result = match db_res {
Ok(_) => crate::drop::Drop::success(),
Err(d) => d.clone(),
};
if expected_success != got_success {
return Err(format!(
"Expected success: {}, Got: {}",
expected_success, got_success
));
}
Ok(())
expect.assert_drop(&result)
}
pub fn run_validate(&self, db: Arc<Database>) -> Result<(), String> {
@ -57,8 +57,6 @@ impl Case {
let validator = Validator::new(db);
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
let schema_id = &self.schema_id;
if !validator.db.schemas.contains_key(schema_id) {
return Err(format!(
@ -70,19 +68,8 @@ impl Case {
let test_data = self.data.clone().unwrap_or(Value::Null);
let result = validator.validate(schema_id, &test_data);
let got_valid = result.errors.is_empty();
if got_valid != expected_success {
let error_msg = if result.errors.is_empty() {
"None".to_string()
} else {
format!("{:?}", result.errors)
};
return Err(format!(
"Expected: {}, Got: {}. Errors: {}",
expected_success, got_valid, error_msg
));
if let Some(expect) = &self.expect {
expect.assert_drop(&result)?;
}
Ok(())
@ -101,24 +88,16 @@ impl Case {
let test_data = self.data.clone().unwrap_or(Value::Null);
let result = merger.merge(&self.schema_id, test_data);
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
let got_success = result.errors.is_empty();
let error_msg = if result.errors.is_empty() {
"None".to_string()
} else {
format!("{:?}", result.errors)
};
let return_val = if expected_success != got_success {
Err(format!(
"Merge Expected: {}, Got: {}. Errors: {}",
expected_success, got_success, error_msg
))
} else if let Some(expect) = &self.expect {
let queries = db.executor.get_queries();
expect.assert_pattern(&queries)?;
expect.assert_sql(&queries)
let return_val = if let Some(expect) = &self.expect {
if let Err(e) = expect.assert_drop(&result) {
Err(format!("Merge {}", e))
} else if result.errors.is_empty() {
// Only assert SQL if merge succeeded
let queries = db.executor.get_queries();
expect.assert_pattern(&queries).and_then(|_| expect.assert_sql(&queries))
} else {
Ok(())
}
} else {
Ok(())
};
@ -139,24 +118,15 @@ impl Case {
let result = queryer.query(&self.schema_id, self.filters.as_ref());
let expected_success = self.expect.as_ref().map(|e| e.success).unwrap_or(false);
let got_success = result.errors.is_empty();
let error_msg = if result.errors.is_empty() {
"None".to_string()
} else {
format!("{:?}", result.errors)
};
let return_val = if expected_success != got_success {
Err(format!(
"Query Expected: {}, Got: {}. Errors: {}",
expected_success, got_success, error_msg
))
} else if let Some(expect) = &self.expect {
let queries = db.executor.get_queries();
expect.assert_pattern(&queries)?;
expect.assert_sql(&queries)
let return_val = if let Some(expect) = &self.expect {
if let Err(e) = expect.assert_drop(&result) {
Err(format!("Query {}", e))
} else if result.errors.is_empty() {
let queries = db.executor.get_queries();
expect.assert_pattern(&queries).and_then(|_| expect.assert_sql(&queries))
} else {
Ok(())
}
} else {
Ok(())
};

View File

@ -0,0 +1,87 @@
use super::Expect;
impl Expect {
pub fn assert_drop(&self, drop: &crate::drop::Drop) -> Result<(), String> {
let got_success = drop.errors.is_empty();
if self.success != got_success {
let mut err_msg = format!("Expected success: {}, Got: {}.", self.success, got_success);
if !drop.errors.is_empty() {
err_msg.push_str(&format!(" Actual Errors: {:?}", drop.errors));
}
return Err(err_msg);
}
if !self.success {
if let Some(expected_errors) = &self.errors {
let actual_values: Vec<serde_json::Value> = drop.errors
.iter()
.map(|e| serde_json::to_value(e).unwrap())
.collect();
for (i, expected_val) in expected_errors.iter().enumerate() {
let mut matched = false;
for actual_val in &actual_values {
if subset_match(expected_val, actual_val) {
matched = true;
break;
}
}
if !matched {
return Err(format!(
"Expected error {} was not found in actual errors.\nExpected subset: {}\nActual full errors: {:?}",
i,
serde_json::to_string_pretty(expected_val).unwrap(),
drop.errors,
));
}
}
}
}
Ok(())
}
}
// Helper to check if `expected` is a structural subset of `actual`
fn subset_match(expected: &serde_json::Value, actual: &serde_json::Value) -> bool {
match (expected, actual) {
(serde_json::Value::Object(exp_map), serde_json::Value::Object(act_map)) => {
for (k, v) in exp_map {
let mut act_v = act_map.get(k);
// Transparent fallback: if testing legacy flat "path", gracefully check inside "details"
if act_v.is_none() && k == "path" {
if let Some(serde_json::Value::Object(details)) = act_map.get("details") {
act_v = details.get("path");
}
}
if let Some(target) = act_v {
if !subset_match(v, target) {
return false;
}
} else {
return false;
}
}
true
}
(serde_json::Value::Array(exp_arr), serde_json::Value::Array(act_arr)) => {
// Basic check: array sizes and elements must match exactly in order
if exp_arr.len() != act_arr.len() {
return false;
}
for (e, a) in exp_arr.iter().zip(act_arr.iter()) {
if !subset_match(e, a) {
return false;
}
}
true
}
// For primitives, exact match
(e, a) => e == a,
}
}

View File

@ -1,5 +1,6 @@
pub mod pattern;
pub mod sql;
pub mod drop;
use serde::Deserialize;