#![allow(
clippy::expect_used,
clippy::panic,
clippy::print_stderr,
clippy::unwrap_used
)]
#[path = "support/corpus_frontend.rs"]
mod corpus_frontend;
use std::process::Command;
use zerodds_lang_cst::Language;
#[path = "support/corpus.rs"]
mod corpus_harness;
#[path = "support/generate.rs"]
mod generate_harness;
const MUST_RENDER: usize = 350;
const REFUSED: &[(&str, &str)] = &[];
const DOES_NOT_COMPILE: &[(&str, &str)] = &[];
const RUNNER: &str = "\
import sys, types, typing, inspect
path = sys.argv[1]
with open(path, encoding='utf-8') as handle:
text = handle.read()
module = types.ModuleType('zd_generated')
module.__file__ = path
sys.modules['zd_generated'] = module
exec(compile(text, path, 'exec'), module.__dict__)
def visit(namespace):
for value in list(vars(namespace).values()):
if inspect.isclass(value) and getattr(value, '__module__', None) == 'zd_generated':
typing.get_type_hints(value, include_extras=True)
visit(value)
visit(module)
";
fn python_available() -> bool {
Command::new("python3")
.arg("--version")
.output()
.is_ok_and(|output| output.status.success())
}
fn python_runs(source: &str, tag: &str) -> Result<(), String> {
let dir = std::env::temp_dir().join(format!("zd-python-corpus-{}-{tag}", std::process::id()));
std::fs::create_dir_all(&dir).map_err(|error| error.to_string())?;
let file = dir.join("zd_types.py");
std::fs::write(&file, source).map_err(|error| error.to_string())?;
let output = Command::new("python3")
.arg("-c")
.arg(RUNNER)
.arg(&file)
.output()
.map_err(|error| error.to_string())?;
let result = if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).into_owned())
};
let _ = std::fs::remove_dir_all(&dir);
result
}
#[test]
fn the_interpreter_check_catches_a_deliberate_error() {
if !python_available() {
eprintln!("SKIPPED: `python3` is not on the path.");
return;
}
let unparseable = "class S:\n def f(:\n";
let why =
python_runs(unparseable, "deliberate-syntax").expect_err("CPython accepted a bad module");
assert!(why.contains("SyntaxError"), "{why}");
let unresolvable =
"import dataclasses\n\n\n@dataclasses.dataclass\nclass S:\n x: NoSuchTypeExists\n";
let why =
python_runs(unresolvable, "deliberate-name").expect_err("CPython accepted a bad name");
assert!(why.contains("NoSuchTypeExists"), "{why}");
python_runs(
"import dataclasses\n\n\n@dataclasses.dataclass\nclass S:\n x: int\n",
"control",
)
.expect("CPython rejected a good module");
}
#[test]
#[ignore = "one CPython start per corpus case, ~1 minute; run with --include-ignored"]
fn every_corpus_case_that_renders_also_compiles() {
if !python_available() {
eprintln!("SKIPPED: `python3` is not on the path.");
return;
}
let mut failures = Vec::new();
let mut unexpectedly_fine = Vec::new();
let mut frontend = Vec::new();
let mut checked = 0_usize;
for (name, path) in corpus_harness::corpus() {
let source = std::fs::read_to_string(&path).expect("a corpus source");
if !corpus_frontend::accepts(&source, &path.display().to_string()) {
continue;
}
let refused = REFUSED.iter().find(|(case, _)| *case == name);
let text = match (
generate_harness::generate_declarations(
&source,
&path.display().to_string(),
Language::Python,
),
refused,
) {
(Ok(text), None) => text,
(Ok(_), Some((case, why))) => {
unexpectedly_fine.push(format!("{case}: renders now, but is listed as {why}"));
continue;
}
(Err(why), None) => {
if why.starts_with("parse:") || why.starts_with("lower:") {
frontend.push(format!("{name}: {why}"));
} else {
failures.push(format!("{name}: {why}"));
}
continue;
}
(Err(_), Some(_)) => continue,
};
checked += 1;
let known = DOES_NOT_COMPILE.iter().find(|(case, _)| *case == name);
match (python_runs(&text, &name), known) {
(Ok(()), None) | (Err(_), Some(_)) => {}
(Ok(()), Some((case, why))) => {
unexpectedly_fine.push(format!("{case}: runs now, but is listed as {why}"));
}
(Err(why), None) => failures.push(format!("{name}: {why}")),
}
}
eprintln!(
"{checked} corpus case(s) reached CPython; {} failed unexpectedly",
failures.len()
);
if !frontend.is_empty() {
eprintln!(
"{} corpus case(s) the frontend refuses, so Python never saw them:\n{frontend:#?}",
frontend.len()
);
}
assert!(
failures.is_empty(),
"{} of {checked} did not run:\n{failures:#?}",
failures.len()
);
assert!(unexpectedly_fine.is_empty(), "{unexpectedly_fine:#?}");
assert!(
checked >= MUST_RENDER,
"only {checked} corpus cases reached the interpreter; the gate says nothing \
below {MUST_RENDER}"
);
}