mod common;
use common::sqry_bin;
use assert_cmd::Command;
use serde_json::Value;
use std::collections::BTreeSet;
use std::fs;
use std::io::{self, Write as IoWrite};
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn sqry_cmd() -> Command {
Command::new(sqry_bin())
}
fn copy_fixture(relative: &str) -> TempDir {
let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("workspace root exists")
.to_path_buf();
let source = workspace_root.join(relative);
assert!(
source.exists(),
"fixture directory {source:?} not found — expected at {relative}"
);
let dest = TempDir::new().expect("create temp dir");
copy_dir_all(&source, dest.path()).expect("copy fixture into temp dir");
dest
}
fn copy_dir_all(src: &Path, dst: &Path) -> io::Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let file_type = entry.file_type()?;
let dest_path = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir_all(&entry.path(), &dest_path)?;
} else {
let content = fs::read(entry.path())?;
let normalised: Vec<u8> = if content.contains(&b'\r') {
content.into_iter().filter(|&b| b != b'\r').collect()
} else {
content
};
let mut f = fs::File::create(&dest_path)?;
f.write_all(&normalised)?;
}
}
Ok(())
}
fn index_project(path: &Path) {
sqry_cmd()
.arg("index")
.arg("--force")
.arg(path)
.assert()
.success();
}
fn parse_json(stdout: &[u8], stderr: &[u8]) -> Value {
serde_json::from_slice(stdout).unwrap_or_else(|e| {
panic!(
"Failed to parse JSON output: {e}\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(stdout),
String::from_utf8_lossy(stderr)
)
})
}
fn extract_names(json: &Value) -> BTreeSet<String> {
json["results"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|entry| entry["name"].as_str().map(String::from))
.collect()
})
.unwrap_or_default()
}
const FIXTURE: &str = "test-fixtures/e2e-scenarios/multi-lang";
#[test]
fn parity_function_query() {
let project = copy_fixture(FIXTURE);
index_project(project.path());
let output = sqry_cmd()
.args(["--json", "query", "kind:function"])
.arg(project.path())
.output()
.expect("sqry query kind:function");
assert!(
output.status.success(),
"sqry query kind:function failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let json = parse_json(&output.stdout, &output.stderr);
let names = extract_names(&json);
assert!(
names.contains("main"),
"expected 'main' in function query results; got: {names:?}"
);
assert!(
names.contains("process"),
"expected 'process' in function query results; got: {names:?}"
);
assert!(
names.contains("helper"),
"expected 'helper' in function query results; got: {names:?}"
);
}
#[test]
fn parity_index_stats() {
let project = copy_fixture(FIXTURE);
index_project(project.path());
let output = sqry_cmd()
.arg("graph")
.arg("--path")
.arg(project.path())
.arg("--format")
.arg("json")
.arg("stats")
.output()
.expect("sqry graph stats");
assert!(
output.status.success(),
"sqry graph stats failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let json = parse_json(&output.stdout, &output.stderr);
let node_count = json["node_count"]
.as_u64()
.expect("stats JSON must contain 'node_count'");
assert!(
node_count > 0,
"expected non-zero node_count in graph stats; got {node_count}"
);
let edge_count = json["edge_count"]
.as_u64()
.expect("stats JSON must contain 'edge_count'");
assert!(
edge_count > 0,
"expected non-zero edge_count in graph stats; got {edge_count}"
);
}
#[test]
fn parity_search_results() {
let project = copy_fixture(FIXTURE);
index_project(project.path());
let output = sqry_cmd()
.args(["--json", "query", "name:process"])
.arg(project.path())
.output()
.expect("sqry query name:process");
assert!(
output.status.success(),
"sqry query name:process failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let stdout_str = String::from_utf8_lossy(&output.stdout);
assert!(
stdout_str.contains("process"),
"expected 'process' in search results; stdout:\n{stdout_str}"
);
let json = parse_json(&output.stdout, &output.stderr);
let total_matches = json["stats"]["total_matches"].as_u64().unwrap_or(0);
assert!(
total_matches >= 1,
"expected at least 1 match for 'name:process'; got {total_matches}"
);
}
#[test]
fn parity_multi_language() {
let project = copy_fixture(FIXTURE);
index_project(project.path());
let output = sqry_cmd()
.args(["--json", "query", "kind:function"])
.arg(project.path())
.output()
.expect("sqry query kind:function (multi-language)");
assert!(
output.status.success(),
"sqry query kind:function (multi-language) failed:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let json = parse_json(&output.stdout, &output.stderr);
let names = extract_names(&json);
assert!(
names.contains("main") || names.contains("process"),
"expected at least one Rust function ('main' or 'process') in results; got: {names:?}"
);
assert!(
names.contains("handle_request") || names.contains("transform"),
"expected at least one Python function ('handle_request' or 'transform') in results; got: {names:?}"
);
assert!(
names.contains("formatOutput"),
"expected TypeScript function 'formatOutput' in results; got: {names:?}"
);
}