use assert_cmd::Command;
use serde_json::Value;
use std::path::Path;
fn tldr_cmd() -> Command {
Command::new(assert_cmd::cargo::cargo_bin!("tldr"))
}
fn skip_if_missing(path: &str) -> bool {
if !Path::new(path).exists() {
eprintln!("[skip] {} not present", path);
return true;
}
false
}
fn run_json(args: &[&str]) -> Value {
let out = tldr_cmd().args(args).output().expect("spawn tldr");
let stdout = String::from_utf8_lossy(&out.stdout);
serde_json::from_str(&stdout).unwrap_or_else(|e| {
panic!(
"parse JSON for `tldr {}`: {}\nstdout: {}\nstderr: {}",
args.join(" "),
e,
stdout,
String::from_utf8_lossy(&out.stderr)
)
})
}
fn run_status(args: &[&str]) -> (i32, String, String) {
let out = tldr_cmd().args(args).output().expect("spawn tldr");
let code = out.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
(code, stdout, stderr)
}
#[test]
fn agg13_5_ocaml_context_file_func_finds_vendored_function() {
let file = "/tmp/repos/ocaml-dune/vendor/opam/src/core/opamStd.ml";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:concat_map", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "concat_map");
let funcs = report["functions"].as_array().expect("functions array");
assert!(
!funcs.is_empty(),
"expected ≥1 function for concat_map, got 0: {report}"
);
assert_eq!(funcs[0]["name"], "concat_map");
}
#[test]
fn agg13_5_c_context_file_func_finds_sdsnewlen() {
let file = "/tmp/repos/c-sds/sds.c";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:sdsnewlen", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "sdsnewlen");
let funcs = report["functions"].as_array().expect("functions array");
assert!(
!funcs.is_empty(),
"expected ≥1 function for sdsnewlen, got 0"
);
}
#[test]
fn agg13_5_cpp_context_file_func_finds_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:XMLDocument::Parse", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "XMLDocument::Parse");
let funcs = report["functions"].as_array().expect("functions array");
assert!(
!funcs.is_empty(),
"expected ≥1 function for XMLDocument::Parse, got 0: {report}"
);
}
#[test]
fn agg14_8_typescript_context_file_func_finds_emitwebidl() {
let file = "/tmp/repos/ts-dom-gen/src/build/emitter.ts";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:emitWebIdl", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "emitWebIdl");
let funcs = report["functions"].as_array().expect("functions array");
assert!(
!funcs.is_empty(),
"expected ≥1 function for emitWebIdl, got 0: {report}"
);
assert!(
funcs[0]["file"]
.as_str()
.unwrap_or("")
.ends_with("emitter.ts"),
"expected function file ending in emitter.ts, got {}",
funcs[0]["file"]
);
}
#[test]
fn agg14_3_cpp_reaching_defs_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["reaching-defs", file, "XMLDocument::Parse"]);
assert_eq!(report["function"], "XMLDocument::Parse");
let blocks = report["blocks"].as_array().expect("blocks array");
assert!(!blocks.is_empty(), "expected ≥1 CFG block");
}
#[test]
fn agg14_3_cpp_available_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let (code, stdout, stderr) =
run_status(&["available", file, "XMLDocument::Parse"]);
assert_eq!(
code, 0,
"available exit non-zero: stdout={stdout} stderr={stderr}"
);
let v: Value = serde_json::from_str(&stdout).expect("parse json");
assert!(v.get("avail_in").is_some() || v.get("avail_out").is_some());
}
#[test]
fn agg14_3_cpp_dead_stores_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["dead-stores", file, "XMLDocument::Parse"]);
assert_eq!(report["function"], "XMLDocument::Parse");
assert!(report.get("dead_stores_ssa").is_some());
assert!(report.get("count").is_some());
}
#[test]
fn agg14_3_cpp_slice_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["slice", file, "XMLDocument::Parse", "2480"]);
assert_eq!(report["function"], "XMLDocument::Parse");
let lines = report["lines"].as_array().expect("lines array");
assert!(!lines.is_empty(), "expected ≥1 sliced line");
}
#[test]
fn agg14_3_cpp_taint_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["taint", file, "XMLDocument::Parse"]);
assert_eq!(report["function"], "XMLDocument::Parse");
assert!(report.get("tainted_vars").is_some());
}
#[test]
fn agg14_3_cpp_complexity_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["complexity", file, "XMLDocument::Parse"]);
assert_eq!(report["function"], "XMLDocument::Parse");
let cyc = report["cyclomatic"].as_u64().expect("cyclomatic u64");
assert!(cyc >= 1, "expected cyclomatic ≥1, got {cyc}");
}
#[test]
fn agg14_3_cpp_contracts_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["contracts", file, "XMLDocument::Parse"]);
assert_eq!(report["function"], "XMLDocument::Parse");
assert!(report.get("preconditions").is_some());
assert!(report.get("postconditions").is_some());
}
#[test]
fn agg14_3_cpp_explain_xmldocument_parse() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["explain", file, "XMLDocument::Parse"]);
assert_eq!(report["function"], "XMLDocument::Parse");
let line = report["line_start"].as_u64().or_else(|| report["line"].as_u64());
assert!(line.is_some(), "expected line/line_start in explain");
}
#[test]
fn agg14_3_cpp_bare_parse_still_works_complexity() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["complexity", file, "Parse"]);
assert_eq!(report["function"], "Parse");
let cyc = report["cyclomatic"].as_u64().expect("cyclomatic u64");
assert!(cyc >= 1, "expected cyclomatic ≥1 for bare Parse");
}
#[test]
fn agg14_3_cpp_bare_parse_still_works_explain() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let report = run_json(&["explain", file, "Parse"]);
assert_eq!(report["function"], "Parse");
}
#[test]
fn agg14_3_cpp_complexity_xmldocument_savefile() {
let file = "/tmp/repos/cpp-tinyxml2/tinyxml2.cpp";
if skip_if_missing(file) {
return;
}
let (code, stdout, _stderr) =
run_status(&["complexity", file, "XMLDocument::SaveFile"]);
if code == 0 {
let v: Value = serde_json::from_str(&stdout).expect("parse");
assert_eq!(v["function"], "XMLDocument::SaveFile");
} else {
eprintln!(
"[note] XMLDocument::SaveFile not resolved (may not exist in this tinyxml2 version)"
);
}
}
#[test]
fn nonreg_js_context_file_func_render() {
let file = "/tmp/repos/express/lib/application.js";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:render", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "render");
let funcs = report["functions"].as_array().expect("functions array");
assert!(!funcs.is_empty(), "expected ≥1 function for render");
}
#[test]
fn nonreg_swift_context_file_func_heapify() {
let file = "/tmp/repos/swift-collections/Sources/HeapModule/Heap+UnsafeHandle.swift";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:_heapify", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "_heapify");
let funcs = report["functions"].as_array().expect("functions array");
assert!(!funcs.is_empty(), "expected ≥1 function for _heapify");
}
#[test]
fn nonreg_lua_context_file_func_m_open() {
let file = "/tmp/repos/lua-lsp/script/files.lua";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:m.open", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "m.open");
let funcs = report["functions"].as_array().expect("functions array");
assert!(!funcs.is_empty(), "expected ≥1 function for m.open");
}
#[test]
fn nonreg_python_context_file_func_wsgi_app() {
let file = "/tmp/repos/flask/src/flask/app.py";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:wsgi_app", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "wsgi_app");
}
#[test]
fn nonreg_rust_context_file_func_check_symlink_loop() {
let file = "/tmp/repos/ripgrep/crates/ignore/src/walk.rs";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:check_symlink_loop", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "check_symlink_loop");
}
#[test]
fn nonreg_go_context_file_func_servehttp() {
let file = "/tmp/repos/go-httprouter/router.go";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:ServeHTTP", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "ServeHTTP");
}
#[test]
fn nonreg_java_context_file_func_findpaginated() {
let file = "/tmp/repos/spring-petclinic/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java";
if skip_if_missing(file) {
return;
}
let arg = format!("{}:findPaginatedForOwnersLastName", file);
let report = run_json(&["context", &arg, "--format", "json"]);
assert_eq!(report["entry_point"], "findPaginatedForOwnersLastName");
}
#[test]
fn bonus_php_context_relative_file_func() {
let dir = "/tmp/repos/php-symfony-string";
let file = "/tmp/repos/php-symfony-string/ByteString.php";
if skip_if_missing(file) {
return;
}
let out = tldr_cmd()
.current_dir(dir)
.args(["context", "ByteString.php:slice", "--format", "json"])
.output()
.expect("spawn");
let stdout = String::from_utf8_lossy(&out.stdout);
let report: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| {
panic!(
"parse json: {}\nstdout: {}\nstderr: {}",
e,
stdout,
String::from_utf8_lossy(&out.stderr)
)
});
assert_eq!(report["entry_point"], "slice");
let funcs = report["functions"].as_array().expect("functions array");
assert!(!funcs.is_empty(), "expected ≥1 function for slice");
}
#[test]
fn bonus_ruby_context_relative_file_func() {
let dir = "/tmp/repos/rails-html-sanitizer";
let file = "/tmp/repos/rails-html-sanitizer/lib/rails/html/sanitizer.rb";
if skip_if_missing(file) {
return;
}
let out = tldr_cmd()
.current_dir(dir)
.args([
"context",
"lib/rails/html/sanitizer.rb:sanitize",
"--format",
"json",
])
.output()
.expect("spawn");
let stdout = String::from_utf8_lossy(&out.stdout);
let report: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| {
panic!(
"parse json: {}\nstdout: {}\nstderr: {}",
e,
stdout,
String::from_utf8_lossy(&out.stderr)
)
});
assert_eq!(report["entry_point"], "sanitize");
let funcs = report["functions"].as_array().expect("functions array");
assert!(!funcs.is_empty(), "expected ≥1 function for sanitize");
}
#[test]
fn bonus_elixir_context_relative_file_func() {
let dir = "/tmp/repos/elixir-plug";
let file = "/tmp/repos/elixir-plug/lib/plug/conn.ex";
if skip_if_missing(file) {
return;
}
let out = tldr_cmd()
.current_dir(dir)
.args([
"context",
"lib/plug/conn.ex:request_url",
"--format",
"json",
])
.output()
.expect("spawn");
let stdout = String::from_utf8_lossy(&out.stdout);
let report: Value = serde_json::from_str(&stdout).unwrap_or_else(|e| {
panic!(
"parse json: {}\nstdout: {}\nstderr: {}",
e,
stdout,
String::from_utf8_lossy(&out.stderr)
)
});
assert_eq!(report["entry_point"], "request_url");
}