diff --git a/c2rust-rust-tools/src/lib.rs b/c2rust-rust-tools/src/lib.rs index 6222c8ed38..9bbb3b1ea1 100644 --- a/c2rust-rust-tools/src/lib.rs +++ b/c2rust-rust-tools/src/lib.rs @@ -1,10 +1,14 @@ use itertools::Itertools; use log::warn; +use std::collections::HashSet; use std::fmt; use std::fmt::Display; use std::fmt::Formatter; +use std::io; +use std::io::Write; use std::path::Path; use std::process::Command; +use std::str; use std::str::FromStr; use crate::RustEdition::Edition2021; @@ -146,6 +150,7 @@ pub struct Rustc<'a> { edition: RustEdition, crate_name: Option<&'a str>, expect_error: bool, + imported_crates: &'a [&'a str], } pub fn rustc(rs_path: &Path) -> Rustc { @@ -154,6 +159,7 @@ pub fn rustc(rs_path: &Path) -> Rustc { edition: Default::default(), crate_name: None, expect_error: false, + imported_crates: Default::default(), } } @@ -176,20 +182,39 @@ impl<'a> Rustc<'a> { } } + pub fn expect_unresolved_imports(self, imported_crates: &'a [&'a str]) -> Self { + Self { + imported_crates, + ..self + } + } + pub fn run(self) { let Self { rs_path, edition, crate_name, expect_error, + imported_crates, } = self; let crate_name = crate_name.unwrap_or_else(|| rs_path.file_stem().unwrap().to_str().unwrap()); - run_rustc(rs_path, edition, crate_name, expect_error); + run_rustc(rs_path, edition, crate_name, expect_error, &imported_crates); } } -fn run_rustc(rs_path: &Path, edition: RustEdition, crate_name: &str, expect_error: bool) { +fn run_rustc( + rs_path: &Path, + edition: RustEdition, + crate_name: &str, + expect_error: bool, + imported_crates: &[&str], +) { + let rs = fs_err::read_to_string(rs_path).unwrap(); + for imported_crate in imported_crates { + assert!(rs.contains(&format!("::{imported_crate}"))); + } + // There's no good way to not create an output with `rustc`, // so just create an `.rlib` and then delete it immediately. let rlib_path = rs_path.with_file_name(format!("lib{crate_name}.rlib")); @@ -206,10 +231,59 @@ fn run_rustc(rs_path: &Path, edition: RustEdition, crate_name: &str, expect_erro "-o", ]); cmd.args([&rlib_path, rs_path]); - let status = cmd.status().unwrap(); + let output = cmd.output().unwrap(); + let status = output.status; if status.success() { fs_err::remove_file(&rlib_path).unwrap(); } + io::stdout().write_all(&output.stdout).unwrap(); + io::stderr().write_all(&output.stderr).unwrap(); + + // Using rustc itself to build snapshots that reference crates (like libc) is difficult because we don't know + // the appropriate --extern libc=/path/to/liblibc-XXXXXXXXXXXXXXXX.rlib to pass. + // Skip for now, as we've already compared the literal text, + // and we're checking the error messages here. + let stderr = str::from_utf8(&output.stderr).unwrap(); + let mut error_lines = stderr + .split('\n') + // .split(|&b| b == b'\n') + .filter(|line| line.starts_with("error[E")) + .collect::>(); + dbg!(&error_lines); + for imported_crate in imported_crates { + // For `::{imported_crate}::*`. + let absolute_path = match edition { + Edition2021 => format!("error[E0433]: failed to resolve: could not find `{imported_crate}` in the list of imported crates"), + Edition2024 => format!("error[E0433]: cannot find `{imported_crate}` in the crate root"), + }; + // For `use ::{imported_crate}*`. + let absolute_use_path = format!("error[E0432]: unresolved import `{imported_crate}`"); + // For `{imported_crate}::*`. + let relative_path = match edition { + Edition2021 => format!("error[E0433]: failed to resolve: use of undeclared crate or module `{imported_crate}`"), + Edition2024 => format!("error[E0433]: cannot find module or crate `{imported_crate}` in this scope"), + }; + + let absolute_path = absolute_path.as_str(); + let absolute_use_path = absolute_use_path.as_str(); + let relative_path = relative_path.as_str(); + dbg!(absolute_path); + dbg!(absolute_use_path); + dbg!(relative_path); + + // Pre-compute to avoid `||` short-circuiting. + let absolute_path = error_lines.remove(absolute_path); + let absolute_use_path = error_lines.remove(absolute_use_path); + let relative_path = error_lines.remove(relative_path); + + assert!(absolute_path || absolute_use_path || relative_path); + } + if !imported_crates.is_empty() { + dbg!(&error_lines); + assert!(error_lines.is_empty()); + return; + } + if expect_error { assert!( !status.success(), diff --git a/c2rust-transpile/tests/snapshots.rs b/c2rust-transpile/tests/snapshots.rs index b6a4295450..3a608fe532 100644 --- a/c2rust-transpile/tests/snapshots.rs +++ b/c2rust-transpile/tests/snapshots.rs @@ -101,6 +101,7 @@ fn transpile_snapshot( c_path: &Path, edition: RustEdition, expect_compile_error: bool, + imported_crates: &[&str], ) { let cfg = config(edition); compile_and_transpile_file(c_path, cfg); @@ -133,21 +134,11 @@ fn transpile_snapshot( insta::assert_snapshot!(snapshot_name, &rs, &debug_expr); - // Using rustc itself to build snapshots that reference libc is difficult because we don't know - // the appropriate --extern libc=/path/to/liblibc-XXXXXXXXXXXXXXXX.rlib to pass. Skip for now, - // as we've already compared the literal text. - if rs.contains("libc::") { - eprintln!( - "warning: skipping compiling {} with rustc since it depends on libc", - rs_path.display() - ); - return; - } - rustc(&rs_path) .edition(edition) .crate_name(crate_name) .expect_error(expect_compile_error) + .expect_unresolved_imports(imported_crates) .run(); } @@ -158,6 +149,7 @@ struct TranspileTest<'a> { os_specific: bool, expect_compile_error_edition_2021: bool, expect_compile_error_edition_2024: bool, + imported_crates: Vec<&'a str>, } fn transpile(c_file_name: &str) -> TranspileTest { @@ -167,6 +159,7 @@ fn transpile(c_file_name: &str) -> TranspileTest { os_specific: false, expect_compile_error_edition_2021: false, expect_compile_error_edition_2024: false, + imported_crates: Default::default(), } } @@ -211,6 +204,11 @@ impl<'a> TranspileTest<'a> { .expect_compile_error_edition_2024(expect_error) } + pub fn expect_unresolved_import(mut self, imported_crate: &'a str) -> Self { + self.imported_crates.push(imported_crate); + self + } + pub fn run(self) { let Self { c_file_name, @@ -218,6 +216,7 @@ impl<'a> TranspileTest<'a> { os_specific, expect_compile_error_edition_2021, expect_compile_error_edition_2024, + imported_crates, } = self; let specific_dir_prefix = [arch_specific.then_some("arch"), os_specific.then_some("os")] @@ -268,12 +267,14 @@ impl<'a> TranspileTest<'a> { &c_path, Edition2021, expect_compile_error_edition_2021, + &imported_crates, ); transpile_snapshot( &platform, &c_path, Edition2024, expect_compile_error_edition_2024, + &imported_crates, ); } } @@ -451,7 +452,10 @@ fn test_rotate_os_specific() { #[test] fn test_sigign() { - transpile("sigign.c").os_specific(true).run(); + transpile("sigign.c") + .os_specific(true) + .expect_unresolved_import("libc") + .run(); } #[test] @@ -461,12 +465,18 @@ fn test_typedefidx() { #[test] fn test_types() { - transpile("types.c").os_specific(true).run(); + transpile("types.c") + .os_specific(true) + .expect_unresolved_import("libc") + .run(); } #[test] fn test_wide_strings() { - transpile("wide_strings.c").os_specific(true).run(); + transpile("wide_strings.c") + .os_specific(true) + .expect_unresolved_import("libc") + .run(); } // arch-os-specific