use jsslint_core::config::ToolConfig;
use jsslint_core::engine::{self, ParsedDocument};
use jsslint_core::json_output;
use std::path::{Path, PathBuf};
use std::process::Command;
mod common;
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.to_path_buf()
}
const PAPERS: &[(&str, &[&str], &[&str])] = &[
(
"eval/recall-corpus/opentsne",
&["main.tex", "references.bib"],
&[],
),
(
"eval/recall-corpus/trueskill",
&["article.tex", "gaming.bib", "journalsAbbr.bib"],
&[],
),
];
const RNW_PAPERS: &[&str] = &[
"eval/recall-corpus/CARBayesST/CARBayesST.Rnw",
"eval/recall-corpus/clifford/clifford.Rnw",
"eval/recall-corpus/CUB/CUBvignette-knitr.Rnw",
"eval/recall-corpus/cusp/Cusp-JSS.Rnw",
"eval/recall-corpus/DBR/DBR.Rnw",
"eval/recall-corpus/deSolve/deSolve.Rnw",
"eval/recall-corpus/HardyWeinberg/HardyWeinberg.Rnw",
"eval/recall-corpus/mlt.docreg/mlt.Rnw",
"eval/recall-corpus/pmclust/pmclust-guide.Rnw",
"eval/recall-corpus/robustlmm/simulationStudies.Rnw",
"eval/recall-corpus/rstpm2/multistate.Rnw",
"eval/recall-corpus/SightabilityModel/a-SightabilityModel.Rnw",
"eval/recall-corpus/spacetime/jss816.Rnw",
];
const RMD_FIXTURES: &[&str] = &[
"tests/fixtures/compliant/minimal.Rmd",
"tests/fixtures/compliant/minimal-bom.Rmd",
"tests/fixtures/violations/rmd/JSS-MARKUP-002-bad.Rmd",
"tests/fixtures/violations/rmd/unterminated-frontmatter.Rmd",
"tests/fixtures/violations/rmd/unterminated-fence.Rmd",
];
fn python_oracle_json(
jss_lint: &Path,
dir: &Path,
files: &[&str],
ignore_rules: &[&str],
) -> String {
let mut cmd = Command::new(jss_lint);
cmd.arg("--no-resolve");
cmd.arg("--output").arg("json");
if !ignore_rules.is_empty() {
cmd.arg("--ignore-rules").arg(ignore_rules.join(","));
}
let output = cmd
.args(files)
.current_dir(dir)
.output()
.expect("failed to run jss-lint");
assert!(
!output.stdout.is_empty(),
"jss-lint produced no stdout (exit {:?}): {}",
output.status.code(),
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).expect("oracle output must be valid UTF-8")
}
#[test]
fn engine_run_matches_python_cli_json() {
let root = repo_root();
let jss_lint = root.join(".venv/bin/jss-lint");
if !jss_lint.exists() {
eprintln!(
"SKIP: {} not found (Python venv not set up)",
jss_lint.display()
);
return;
}
if let Some(msg) = common::corpus_missing(
&root,
&[
"eval/recall-corpus/opentsne/main.tex",
"eval/recall-corpus/trueskill/article.tex",
],
) {
eprintln!("{msg}");
return;
}
let mut mismatches = Vec::new();
for &(paper_dir, files, ignore_rules) in PAPERS {
let dir = root.join(paper_dir);
let expected = python_oracle_json(&jss_lint, &dir, files, ignore_rules);
let sources: Vec<(String, String)> = files
.iter()
.map(|f| {
let path = dir.join(f);
let source = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display()));
(f.to_string(), source)
})
.collect();
let document = ParsedDocument::from_sources(&sources)
.unwrap_or_else(|e| panic!("{paper_dir}: failed to build ParsedDocument: {e}"));
let config = ToolConfig {
ignore_rules: ignore_rules.iter().map(|s| s.to_string()).collect(),
..ToolConfig::default()
};
let report = engine::run(&config, &document);
let actual = json_output::render(&report);
if actual != expected {
mismatches.push(format!(
"{paper_dir}\n expected:\n{expected}\n actual:\n{actual}"
));
}
}
assert!(
mismatches.is_empty(),
"{} paper mismatches:\n{}",
mismatches.len(),
mismatches.join("\n---\n")
);
}
#[test]
fn engine_run_matches_python_cli_json_rnw_rmd() {
let root = repo_root();
let jss_lint = root.join(".venv/bin/jss-lint");
if !jss_lint.exists() {
eprintln!(
"SKIP: {} not found (Python venv not set up)",
jss_lint.display()
);
return;
}
if let Some(msg) = common::corpus_missing(&root, RNW_PAPERS) {
eprintln!("{msg}");
return;
}
let mut mismatches = Vec::new();
for &rel_path in RNW_PAPERS.iter().chain(RMD_FIXTURES) {
let full_path = root.join(rel_path);
let dir = full_path.parent().unwrap();
let file_name = full_path.file_name().unwrap().to_str().unwrap();
let files = &[file_name];
let expected = python_oracle_json(&jss_lint, dir, files, &[]);
let source = std::fs::read_to_string(&full_path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", full_path.display()));
let document = ParsedDocument::from_sources(&[(file_name.to_string(), source)])
.unwrap_or_else(|e| panic!("{rel_path}: failed to build ParsedDocument: {e}"));
let report = engine::run(&ToolConfig::default(), &document);
let actual = json_output::render(&report);
if actual != expected {
mismatches.push(format!(
"{rel_path}\n expected:\n{expected}\n actual:\n{actual}"
));
}
}
assert!(
mismatches.is_empty(),
"{} paper mismatches:\n{}",
mismatches.len(),
mismatches.join("\n---\n")
);
}