Skip to content

Revise generate function to return Result #4430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions ctest-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ edition = "2021"
ctest = { path = "../ctest" }
cc = "1.0"

[dev-dependencies]
ctest = { path = "../ctest" }

[dependencies]
cfg-if = "1.0.0"
libc = { path = ".." }
Expand Down
72 changes: 72 additions & 0 deletions ctest-test/tests/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,75 @@ fn t2_cxx() {
panic!();
}
}

#[test]
fn test_missing_out_dir() {
// Save original OUT_DIR
let orig_out_dir = env::var_os("OUT_DIR");
env::remove_var("OUT_DIR");

// Test error handling for OUT_DIR missing
let result = ctest::TestGenerator::new()
.header("t1.h")
.try_generate("src/t1.rs", "out_dir_gen.rs");

// Restore OUT_DIR
if let Some(dir) = orig_out_dir {
env::set_var("OUT_DIR", dir);
}

assert!(result.is_err(), "Expected error when OUT_DIR is missing");
}

#[test]
fn test_invalid_output_path() {
// Test error handling for invalid output path
let err = ctest::TestGenerator::new()
.header("t1.h")
.include("src")
.out_dir("/nonexistent_dir") // Should fail with permission error
.try_generate("src/t1.rs", "out_path_gen.rs");

assert!(err.is_err(), "Expected error with invalid output path");
}

#[test]
fn test_parsing_error() {
// Test parsing error
// Create a temporary file with invalid Rust syntax
let temp_dir = env::temp_dir();
let invalid_file = temp_dir.join("invalid.rs");
std::fs::write(&invalid_file, "fn invalid_syntax {").unwrap();

let err = ctest::TestGenerator::new()
.header("t1.h")
.include("src")
.target("x86_64-unknown-linux-gnu")
.try_generate(&invalid_file, "parse_gen.rs");

assert!(err.is_err(), "Expected error when parsing invalid syntax");
let _ = std::fs::remove_file(invalid_file);
}

#[test]
fn test_non_existent_header() {
// Test non-existent header
let err = ctest::TestGenerator::new()
.header("nonexistent_header.h")
.include("src")
.try_generate("src/t1.rs", "missing_header_gen.rs");

assert!(err.is_err(), "Expected error with non-existent header");
}

#[test]
fn test_invalid_include_path() {
// Test invalid include path
let err = ctest::TestGenerator::new()
.header("t1.h")
.include("nonexistent_directory")
.try_generate("src/t1.rs", "invalid_include_gen.rs");

assert!(err.is_err(), "Expected error with invalid include path");
}

1 change: 1 addition & 0 deletions ctest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ repository = "https://github.com/rust-lang/libc"
rust-version = "1.63.0"

[dependencies]
anyhow = "1.0"
garando_syntax = "0.1"
cc = "1.0.1"
rustc_version = "0.4"
Expand Down
124 changes: 87 additions & 37 deletions ctest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
#![recursion_limit = "256"]
#![deny(missing_docs)]

use anyhow::{Context, Result};
use garando_syntax as syntax;
use indoc::writedoc;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use std::rc::Rc;

use garando_syntax as syntax;
use indoc::writedoc;
use syntax::abi::Abi;
use syntax::ast;
use syntax::ast::{Attribute, Name};
Expand All @@ -41,9 +41,6 @@ use syntax::ptr::P;
use syntax::util::small_vector::SmallVector;
use syntax::visit::{self, Visitor};

type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;

/// Programming language
#[derive(Debug)]
pub enum Lang {
Expand Down Expand Up @@ -773,6 +770,17 @@ impl TestGenerator {
self
}

/// Generate all tests and panic on any errors.
///
/// This function is a convenience wrapper around `try_generate` that panics instead of returning
/// errors.
///
/// See `try_generate` for the error-handling version of this function.
pub fn generate<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) {
self.try_generate(krate, out_file)
.unwrap_or_else(|e| panic!("Failed to generate tests: {e}"));
}

/// Generate all tests.
///
/// This function is first given the path to the `*-sys` crate which is
Expand All @@ -791,16 +799,16 @@ impl TestGenerator {
/// use ctest::TestGenerator;
///
/// let mut cfg = TestGenerator::new();
/// cfg.generate("../path/to/libfoo-sys/lib.rs", "all.rs");
/// cfg.try_generate("../path/to/libfoo-sys/lib.rs", "all.rs");
/// ```
pub fn generate<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) {
pub fn try_generate<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) -> Result<PathBuf> {
let krate = krate.as_ref();
let out = self.generate_files(krate, out_file);
let out = self.generate_files(krate, out_file)?;

let target = self
.target
.clone()
.unwrap_or_else(|| env::var("TARGET").unwrap());
let target = match self.target.clone() {
Some(t) => t,
None => env::var("TARGET").context("TARGET environment variable not found")?,
};

// Compile our C shim to be linked into tests
let mut cfg = cc::Build::new();
Expand All @@ -815,19 +823,19 @@ impl TestGenerator {
if target.contains("msvc") {
cfg.flag("/W3").flag("/Wall").flag("/WX")
// ignored warnings
.flag("/wd4820") // warning about adding padding?
.flag("/wd4100") // unused parameters
.flag("/wd4996") // deprecated functions
.flag("/wd4296") // '<' being always false
.flag("/wd4255") // converting () to (void)
.flag("/wd4668") // using an undefined thing in preprocessor?
.flag("/wd4366") // taking ref to packed struct field might be unaligned
.flag("/wd4189") // local variable initialized but not referenced
.flag("/wd4710") // function not inlined
.flag("/wd5045") // compiler will insert Spectre mitigation
.flag("/wd4514") // unreferenced inline function removed
.flag("/wd4711") // function selected for automatic inline
;
.flag("/wd4820") // warning about adding padding?
.flag("/wd4100") // unused parameters
.flag("/wd4996") // deprecated functions
.flag("/wd4296") // '<' being always false
.flag("/wd4255") // converting () to (void)
.flag("/wd4668") // using an undefined thing in preprocessor?
.flag("/wd4366") // taking ref to packed struct field might be unaligned
.flag("/wd4189") // local variable initialized but not referenced
.flag("/wd4710") // function not inlined
.flag("/wd5045") // compiler will insert Spectre mitigation
.flag("/wd4514") // unreferenced inline function removed
.flag("/wd4711") // function selected for automatic inline
;
} else {
cfg.flag("-Wall")
.flag("-Wextra")
Expand All @@ -851,25 +859,58 @@ impl TestGenerator {
cfg.include(p);
}

let stem = out.file_stem().unwrap().to_str().unwrap();
cfg.target(&target)
.out_dir(out.parent().unwrap())
.compile(&format!("lib{stem}.a"));
let stem = out
.file_stem()
.context("Failed to get file stem")?
.to_str()
.context("Failed to convert to str")?;

let parent = out
.parent()
.context("Output file has no parent directory")?;

cfg.target(&target).out_dir(parent);

match cfg.try_compile(&format!("lib{stem}.a")) {
Ok(_) => Ok(out),
Err(e) => Err(anyhow::anyhow!("Compilation failed").context(format!("Error: {}", e))),
}
}

#[doc(hidden)] // TODO: needs docs
pub fn generate_files<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) -> PathBuf {
pub fn generate_files<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) -> Result<PathBuf> {
self.generate_files_impl(krate, out_file)
.expect("generation failed")
}

fn generate_files_impl<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) -> Result<PathBuf> {
let krate = krate.as_ref();

// Check if all specified header files exist in the include paths
for header in &self.headers {
// Check if header exists in any of the include paths
for include_path in &self.includes {
let header_path = include_path.join(header);
if !header_path.exists() {
return Err(anyhow::anyhow!("Header file not found"))
.with_context(|| format!("Path {} does not exist", header_path.display()));
}
}
}

// Prep the test generator
let out_dir = self
.out_dir
.clone()
.unwrap_or_else(|| PathBuf::from(env::var_os("OUT_DIR").unwrap()));
.or_else(|| env::var_os("OUT_DIR").map(PathBuf::from))
.context("Neither out_dir nor OUT_DIR environment variable is set")?;

if !out_dir.is_dir() {
return Err(anyhow::anyhow!(
"Output directory does not exist: {}",
out_dir.display()
));
}

let out_file = out_dir.join(out_file);
let ext = match self.lang {
Lang::C => "c",
Expand All @@ -878,18 +919,27 @@ impl TestGenerator {
let c_file = out_file.with_extension(ext);
let rust_out = BufWriter::new(File::create(&out_file)?);
let c_out = BufWriter::new(File::create(&c_file)?);
let mut sess = ParseSess::new(FilePathMapping::empty());

let target = self
.target
.clone()
.unwrap_or_else(|| env::var("TARGET").unwrap());
.or_else(|| env::var("TARGET").ok())
.filter(|t| !t.is_empty())
.context("TARGET environment variable not set or empty")?;

let mut sess = ParseSess::new(FilePathMapping::empty());
for (k, v) in default_cfg(&target).into_iter().chain(self.cfg.clone()) {
let s = |s: &str| Name::intern(s);
sess.config.insert((s(&k), v.as_ref().map(|n| s(n))));
}

// Parse the libc crate
let krate = parse::parse_crate_from_file(krate, &sess).ok().unwrap();
// Convert DiagnosticBuilder -> Error so the `?` works
let krate = parse::parse_crate_from_file(krate, &sess).map_err(|mut d| {
let diag_info = format!("{:?}", d);
// Emit the diagnostic to properly handle it and show error to the user
d.emit();
anyhow::anyhow!("failed to parse crate").context(format!("Diagnostic: {}", diag_info))
})?;

// Remove things like functions, impls, traits, etc, that we're not
// looking at
Expand Down
12 changes: 6 additions & 6 deletions libc-test/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4868,8 +4868,8 @@ fn test_linux_like_apis(target: &str) {
"strerror_r" => false,
_ => true,
})
.skip_const(|_| true)
.skip_struct(|_| true);
.skip_const(|_| true)
.skip_struct(|_| true);
cfg.generate(src_hotfix_dir().join("lib.rs"), "linux_strerror_r.rs");
}

Expand Down Expand Up @@ -4943,10 +4943,10 @@ fn test_linux_like_apis(target: &str) {
.skip_const(|_| true)
.skip_struct(|_| true)
.skip_const(move |name| match name {
"IPV6_FLOWINFO"
| "IPV6_FLOWLABEL_MGR"
| "IPV6_FLOWINFO_SEND"
| "IPV6_FLOWINFO_FLOWLABEL"
"IPV6_FLOWINFO"
| "IPV6_FLOWLABEL_MGR"
| "IPV6_FLOWINFO_SEND"
| "IPV6_FLOWINFO_FLOWLABEL"
| "IPV6_FLOWINFO_PRIORITY" => false,
_ => true,
})
Expand Down
Loading