From f1262e6826118748b29174577704724571d9c59e Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Wed, 1 Apr 2026 12:11:06 -0700 Subject: [PATCH 1/3] rust-tools: intercept `rustc` output --- c2rust-rust-tools/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/c2rust-rust-tools/src/lib.rs b/c2rust-rust-tools/src/lib.rs index 6222c8ed38..94c2e46a65 100644 --- a/c2rust-rust-tools/src/lib.rs +++ b/c2rust-rust-tools/src/lib.rs @@ -3,6 +3,8 @@ use log::warn; 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::FromStr; @@ -206,10 +208,13 @@ 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(); if expect_error { assert!( !status.success(), From b6de811c27d135caa1cf483b110efa6ed393d303 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Wed, 1 Apr 2026 12:37:58 -0700 Subject: [PATCH 2/3] transpile: tests: add `.expect_unresolved_import` instead of hardcoding `libc` Also, we check that the error messages are as expected, too (meaning we actually run `rustc`). This also makes the `libc` tests more explicit. --- c2rust-rust-tools/src/lib.rs | 70 ++++++++++++++++++++++++++++- c2rust-transpile/tests/snapshots.rs | 38 ++++++++++------ 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/c2rust-rust-tools/src/lib.rs b/c2rust-rust-tools/src/lib.rs index 94c2e46a65..c819420289 100644 --- a/c2rust-rust-tools/src/lib.rs +++ b/c2rust-rust-tools/src/lib.rs @@ -1,5 +1,6 @@ use itertools::Itertools; use log::warn; +use std::collections::HashSet; use std::fmt; use std::fmt::Display; use std::fmt::Formatter; @@ -7,6 +8,7 @@ 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; @@ -148,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 { @@ -156,6 +159,7 @@ pub fn rustc(rs_path: &Path) -> Rustc { edition: Default::default(), crate_name: None, expect_error: false, + imported_crates: Default::default(), } } @@ -178,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")); @@ -215,6 +238,49 @@ fn run_rustc(rs_path: &Path, edition: RustEdition, crate_name: &str, expect_erro } 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 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); + + assert!( + error_lines.contains(absolute_path) + || error_lines.contains(absolute_use_path) + || error_lines.contains(relative_path) + ); + } + if !imported_crates.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 From becc6858281d9ba07b35ac6b5567ef14cbb2fc21 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Wed, 1 Apr 2026 12:58:30 -0700 Subject: [PATCH 3/3] transpile: tests: check that there are no errors left after removing the expected unresolved import errors --- c2rust-rust-tools/src/lib.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/c2rust-rust-tools/src/lib.rs b/c2rust-rust-tools/src/lib.rs index c819420289..9bbb3b1ea1 100644 --- a/c2rust-rust-tools/src/lib.rs +++ b/c2rust-rust-tools/src/lib.rs @@ -244,7 +244,7 @@ fn run_rustc( // 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 error_lines = stderr + let mut error_lines = stderr .split('\n') // .split(|&b| b == b'\n') .filter(|line| line.starts_with("error[E")) @@ -271,13 +271,16 @@ fn run_rustc( dbg!(absolute_use_path); dbg!(relative_path); - assert!( - error_lines.contains(absolute_path) - || error_lines.contains(absolute_use_path) - || error_lines.contains(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; }