use std::path::{Path, PathBuf};
use crate::compat::cleanup::CleanupGuard;
use crate::compat::discovery::{DiscoveredFixture, FixtureKind};
use crate::error::Error;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ConvertedFixture {
pub src_path: PathBuf,
pub dest_path: PathBuf,
pub dest_stderr: Option<PathBuf>,
pub kind: FixtureKind,
}
pub fn convert_fixtures(
compat_root: &Path,
fixtures: &[DiscoveredFixture],
cleanup: &CleanupGuard,
) -> Result<Vec<ConvertedFixture>, Error> {
let converted_root = compat_root.join("target").join("lihaaf-compat-converted");
let compile_pass_dir = converted_root.join("compile_pass");
let compile_fail_dir = converted_root.join("compile_fail");
match std::fs::remove_dir_all(&converted_root) {
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => {
return Err(Error::io(
e,
"clearing prior compat-converted tree before re-conversion",
Some(converted_root.clone()),
));
}
}
std::fs::create_dir_all(&compile_pass_dir).map_err(|e| {
Error::io(
e,
"creating compat-converted compile_pass directory",
Some(compile_pass_dir.clone()),
)
})?;
cleanup.track(compile_pass_dir.clone(), compat_root);
std::fs::create_dir_all(&compile_fail_dir).map_err(|e| {
Error::io(
e,
"creating compat-converted compile_fail directory",
Some(compile_fail_dir.clone()),
)
})?;
cleanup.track(compile_fail_dir.clone(), compat_root);
cleanup.track(converted_root.clone(), compat_root);
let mut out: Vec<ConvertedFixture> = Vec::with_capacity(fixtures.len());
for fixture in fixtures {
let subdir = match fixture.kind {
FixtureKind::Pass => &compile_pass_dir,
FixtureKind::CompileFail => &compile_fail_dir,
};
let basename = fixture.fixture_path.file_name().ok_or_else(|| {
Error::io(
std::io::Error::other("fixture path has no file name"),
"deriving fixture basename for compat conversion",
Some(fixture.fixture_path.clone()),
)
})?;
let dest_path = subdir.join(basename);
std::fs::copy(&fixture.fixture_path, &dest_path).map_err(|e| {
Error::io(
e,
"copying trybuild fixture to compat-converted tree",
Some(fixture.fixture_path.clone()),
)
})?;
cleanup.track(dest_path.clone(), compat_root);
let mut dest_stderr: Option<PathBuf> = None;
let src_stderr = fixture.fixture_path.with_extension("stderr");
let dest_stderr_path = dest_path.with_extension("stderr");
match std::fs::copy(&src_stderr, &dest_stderr_path) {
Ok(_) => {
cleanup.track(dest_stderr_path.clone(), compat_root);
dest_stderr = Some(dest_stderr_path);
}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => {
return Err(Error::io(
e,
"copying trybuild .stderr snapshot to compat-converted tree",
Some(src_stderr.clone()),
));
}
}
out.push(ConvertedFixture {
src_path: fixture.fixture_path.clone(),
dest_path,
dest_stderr,
kind: fixture.kind,
});
}
out.sort_by(|a, b| a.dest_path.cmp(&b.dest_path));
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::discovery::CallSite;
fn dummy_call_site(file: PathBuf) -> CallSite {
CallSite {
file,
line: 1,
enclosing_test_fn: None,
}
}
#[test]
fn convert_copies_pass_fixture_into_compile_pass_subdir() {
let tmp = tempfile::tempdir().expect("tempdir");
let compat_root = tmp.path();
let src_dir = compat_root.join("tests").join("ui");
std::fs::create_dir_all(&src_dir).expect("create src dir");
let src = src_dir.join("good.rs");
std::fs::write(&src, b"fn main() {}").expect("write fixture");
let cleanup = CleanupGuard::new(false);
let fixtures = vec![DiscoveredFixture {
fixture_path: src.clone(),
relative_path: "tests/ui/good.rs".into(),
kind: FixtureKind::Pass,
call_site: dummy_call_site(src_dir.join("trybuild.rs")),
}];
let out = convert_fixtures(compat_root, &fixtures, &cleanup).expect("convert");
assert_eq!(out.len(), 1);
let dest = compat_root
.join("target")
.join("lihaaf-compat-converted")
.join("compile_pass")
.join("good.rs");
assert!(
dest.exists(),
"compile_pass fixture must be copied to {dest:?}"
);
assert_eq!(out[0].dest_path, dest);
assert_eq!(out[0].dest_stderr, None);
}
#[test]
fn convert_copies_compile_fail_with_stderr_snapshot() {
let tmp = tempfile::tempdir().expect("tempdir");
let compat_root = tmp.path();
let src_dir = compat_root.join("tests").join("ui");
std::fs::create_dir_all(&src_dir).expect("create src dir");
let src_rs = src_dir.join("bad.rs");
let src_stderr = src_dir.join("bad.stderr");
std::fs::write(&src_rs, b"compile error here").expect("write rs");
std::fs::write(&src_stderr, b"expected: error[E0...]").expect("write stderr");
let cleanup = CleanupGuard::new(false);
let fixtures = vec![DiscoveredFixture {
fixture_path: src_rs.clone(),
relative_path: "tests/ui/bad.rs".into(),
kind: FixtureKind::CompileFail,
call_site: dummy_call_site(src_dir.join("trybuild.rs")),
}];
let out = convert_fixtures(compat_root, &fixtures, &cleanup).expect("convert");
assert_eq!(out.len(), 1);
let dest_rs = compat_root
.join("target")
.join("lihaaf-compat-converted")
.join("compile_fail")
.join("bad.rs");
let dest_stderr = dest_rs.with_extension("stderr");
assert!(
dest_rs.exists(),
"compile_fail fixture must be copied to {dest_rs:?}"
);
assert!(
dest_stderr.exists(),
"stderr snapshot must be copied to {dest_stderr:?}"
);
assert_eq!(out[0].dest_path, dest_rs);
assert_eq!(out[0].dest_stderr.as_ref(), Some(&dest_stderr));
}
#[test]
fn convert_sorts_output_by_dest_path() {
let tmp = tempfile::tempdir().expect("tempdir");
let compat_root = tmp.path();
let src_dir = compat_root.join("tests").join("ui");
std::fs::create_dir_all(&src_dir).expect("create src dir");
let src_z = src_dir.join("z.rs");
let src_a = src_dir.join("a.rs");
std::fs::write(&src_z, b"").expect("write z");
std::fs::write(&src_a, b"").expect("write a");
let cleanup = CleanupGuard::new(false);
let fixtures = vec![
DiscoveredFixture {
fixture_path: src_z.clone(),
relative_path: "tests/ui/z.rs".into(),
kind: FixtureKind::Pass,
call_site: dummy_call_site(src_dir.join("trybuild.rs")),
},
DiscoveredFixture {
fixture_path: src_a.clone(),
relative_path: "tests/ui/a.rs".into(),
kind: FixtureKind::Pass,
call_site: dummy_call_site(src_dir.join("trybuild.rs")),
},
];
let out = convert_fixtures(compat_root, &fixtures, &cleanup).expect("convert");
assert_eq!(out.len(), 2);
assert!(
out[0].dest_path < out[1].dest_path,
"output must be sorted; got {:?} then {:?}",
out[0].dest_path,
out[1].dest_path,
);
}
#[test]
fn convert_removes_stale_stderr_when_source_disappears() {
let tmp = tempfile::tempdir().expect("tempdir");
let compat_root = tmp.path();
let src_dir = compat_root.join("tests").join("ui");
std::fs::create_dir_all(&src_dir).expect("create src dir");
let src_rs = src_dir.join("bad.rs");
let src_stderr = src_dir.join("bad.stderr");
std::fs::write(&src_rs, b"compile error here").expect("write rs");
std::fs::write(&src_stderr, b"expected: error[E0...]").expect("write stderr");
let cleanup1 = CleanupGuard::new(false);
let fixtures = vec![DiscoveredFixture {
fixture_path: src_rs.clone(),
relative_path: "tests/ui/bad.rs".into(),
kind: FixtureKind::CompileFail,
call_site: dummy_call_site(src_dir.join("trybuild.rs")),
}];
let out1 = convert_fixtures(compat_root, &fixtures, &cleanup1).expect("first convert");
let dest_stderr = compat_root
.join("target")
.join("lihaaf-compat-converted")
.join("compile_fail")
.join("bad.stderr");
assert!(
dest_stderr.exists(),
"first run must produce converted stderr at {dest_stderr:?}",
);
assert_eq!(out1[0].dest_stderr.as_ref(), Some(&dest_stderr));
std::fs::remove_file(&src_stderr).expect("delete upstream stderr");
let cleanup2 = CleanupGuard::new(false);
let out2 = convert_fixtures(compat_root, &fixtures, &cleanup2).expect("second convert");
assert_eq!(out2[0].dest_stderr, None, "no stderr should be tracked");
assert!(
!dest_stderr.exists(),
"stale converted stderr must be removed when upstream stderr is gone; \
{dest_stderr:?} still exists",
);
}
}