Skip to content

Commit 76696a2

Browse files
authored
Merge pull request #4430 from Owen-CH-Leung/add_try_generate_signature
ctest: Revise generate function to return Result
2 parents 6073bdf + c54f1b9 commit 76696a2

File tree

4 files changed

+144
-37
lines changed

4 files changed

+144
-37
lines changed

ctest-test/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ edition = "2021"
99
ctest = { path = "../ctest" }
1010
cc = "1.0"
1111

12+
[dev-dependencies]
13+
ctest = { path = "../ctest" }
14+
1215
[dependencies]
1316
cfg-if = "1.0.0"
1417
libc = { path = ".." }

ctest-test/tests/all.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,75 @@ fn t2_cxx() {
128128
panic!();
129129
}
130130
}
131+
132+
#[test]
133+
fn test_missing_out_dir() {
134+
// Save original OUT_DIR
135+
let orig_out_dir = env::var_os("OUT_DIR");
136+
env::remove_var("OUT_DIR");
137+
138+
// Test error handling for OUT_DIR missing
139+
let result = ctest::TestGenerator::new()
140+
.header("t1.h")
141+
.try_generate("src/t1.rs", "out_dir_gen.rs");
142+
143+
// Restore OUT_DIR
144+
if let Some(dir) = orig_out_dir {
145+
env::set_var("OUT_DIR", dir);
146+
}
147+
148+
assert!(result.is_err(), "Expected error when OUT_DIR is missing");
149+
}
150+
151+
#[test]
152+
fn test_invalid_output_path() {
153+
// Test error handling for invalid output path
154+
let err = ctest::TestGenerator::new()
155+
.header("t1.h")
156+
.include("src")
157+
.out_dir("/nonexistent_dir") // Should fail with permission error
158+
.try_generate("src/t1.rs", "out_path_gen.rs");
159+
160+
assert!(err.is_err(), "Expected error with invalid output path");
161+
}
162+
163+
#[test]
164+
fn test_parsing_error() {
165+
// Test parsing error
166+
// Create a temporary file with invalid Rust syntax
167+
let temp_dir = env::temp_dir();
168+
let invalid_file = temp_dir.join("invalid.rs");
169+
std::fs::write(&invalid_file, "fn invalid_syntax {").unwrap();
170+
171+
let err = ctest::TestGenerator::new()
172+
.header("t1.h")
173+
.include("src")
174+
.target("x86_64-unknown-linux-gnu")
175+
.try_generate(&invalid_file, "parse_gen.rs");
176+
177+
assert!(err.is_err(), "Expected error when parsing invalid syntax");
178+
let _ = std::fs::remove_file(invalid_file);
179+
}
180+
181+
#[test]
182+
fn test_non_existent_header() {
183+
// Test non-existent header
184+
let err = ctest::TestGenerator::new()
185+
.header("nonexistent_header.h")
186+
.include("src")
187+
.try_generate("src/t1.rs", "missing_header_gen.rs");
188+
189+
assert!(err.is_err(), "Expected error with non-existent header");
190+
}
191+
192+
#[test]
193+
fn test_invalid_include_path() {
194+
// Test invalid include path
195+
let err = ctest::TestGenerator::new()
196+
.header("t1.h")
197+
.include("nonexistent_directory")
198+
.try_generate("src/t1.rs", "invalid_include_gen.rs");
199+
200+
assert!(err.is_err(), "Expected error with invalid include path");
201+
}
202+

ctest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ repository = "https://github.com/rust-lang/libc"
99
rust-version = "1.63.0"
1010

1111
[dependencies]
12+
anyhow = "1.0"
1213
garando_syntax = "0.1"
1314
cc = "1.0.1"
1415
rustc_version = "0.4"

ctest/src/lib.rs

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@
1313
#![recursion_limit = "256"]
1414
#![deny(missing_docs)]
1515

16+
use anyhow::{anyhow, Context, Result};
17+
use garando_syntax as syntax;
18+
use indoc::writedoc;
1619
use std::collections::{HashMap, HashSet};
1720
use std::env;
1821
use std::fs::File;
1922
use std::io::prelude::*;
2023
use std::io::BufWriter;
2124
use std::path::{Path, PathBuf};
2225
use std::rc::Rc;
23-
24-
use garando_syntax as syntax;
25-
use indoc::writedoc;
2626
use syntax::abi::Abi;
2727
use syntax::ast;
2828
use syntax::ast::{Attribute, Name};
@@ -41,9 +41,6 @@ use syntax::ptr::P;
4141
use syntax::util::small_vector::SmallVector;
4242
use syntax::visit::{self, Visitor};
4343

44-
type Error = Box<dyn std::error::Error>;
45-
type Result<T, E = Error> = std::result::Result<T, E>;
46-
4744
/// Programming language
4845
#[derive(Debug)]
4946
pub enum Lang {
@@ -773,6 +770,17 @@ impl TestGenerator {
773770
self
774771
}
775772

773+
/// Generate all tests and panic on any errors.
774+
///
775+
/// This function is a convenience wrapper around `try_generate` that panics instead of returning
776+
/// errors.
777+
///
778+
/// See `try_generate` for the error-handling version of this function.
779+
pub fn generate<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) {
780+
self.try_generate(krate, out_file)
781+
.unwrap_or_else(|e| panic!("Failed to generate tests: {e}"));
782+
}
783+
776784
/// Generate all tests.
777785
///
778786
/// This function is first given the path to the `*-sys` crate which is
@@ -791,16 +799,16 @@ impl TestGenerator {
791799
/// use ctest::TestGenerator;
792800
///
793801
/// let mut cfg = TestGenerator::new();
794-
/// cfg.generate("../path/to/libfoo-sys/lib.rs", "all.rs");
802+
/// cfg.try_generate("../path/to/libfoo-sys/lib.rs", "all.rs");
795803
/// ```
796-
pub fn generate<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) {
804+
pub fn try_generate<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) -> Result<PathBuf> {
797805
let krate = krate.as_ref();
798-
let out = self.generate_files(krate, out_file);
806+
let out = self.generate_files(krate, out_file)?;
799807

800-
let target = self
801-
.target
802-
.clone()
803-
.unwrap_or_else(|| env::var("TARGET").unwrap());
808+
let target = match self.target.clone() {
809+
Some(t) => t,
810+
None => env::var("TARGET").context("TARGET environment variable not found")?,
811+
};
804812

805813
// Compile our C shim to be linked into tests
806814
let mut cfg = cc::Build::new();
@@ -815,19 +823,19 @@ impl TestGenerator {
815823
if target.contains("msvc") {
816824
cfg.flag("/W3").flag("/Wall").flag("/WX")
817825
// ignored warnings
818-
.flag("/wd4820") // warning about adding padding?
819-
.flag("/wd4100") // unused parameters
820-
.flag("/wd4996") // deprecated functions
821-
.flag("/wd4296") // '<' being always false
822-
.flag("/wd4255") // converting () to (void)
823-
.flag("/wd4668") // using an undefined thing in preprocessor?
824-
.flag("/wd4366") // taking ref to packed struct field might be unaligned
825-
.flag("/wd4189") // local variable initialized but not referenced
826-
.flag("/wd4710") // function not inlined
827-
.flag("/wd5045") // compiler will insert Spectre mitigation
828-
.flag("/wd4514") // unreferenced inline function removed
829-
.flag("/wd4711") // function selected for automatic inline
830-
;
826+
.flag("/wd4820") // warning about adding padding?
827+
.flag("/wd4100") // unused parameters
828+
.flag("/wd4996") // deprecated functions
829+
.flag("/wd4296") // '<' being always false
830+
.flag("/wd4255") // converting () to (void)
831+
.flag("/wd4668") // using an undefined thing in preprocessor?
832+
.flag("/wd4366") // taking ref to packed struct field might be unaligned
833+
.flag("/wd4189") // local variable initialized but not referenced
834+
.flag("/wd4710") // function not inlined
835+
.flag("/wd5045") // compiler will insert Spectre mitigation
836+
.flag("/wd4514") // unreferenced inline function removed
837+
.flag("/wd4711") // function selected for automatic inline
838+
;
831839
} else {
832840
cfg.flag("-Wall")
833841
.flag("-Wextra")
@@ -851,25 +859,40 @@ impl TestGenerator {
851859
cfg.include(p);
852860
}
853861

854-
let stem = out.file_stem().unwrap().to_str().unwrap();
855-
cfg.target(&target)
856-
.out_dir(out.parent().unwrap())
857-
.compile(&format!("lib{stem}.a"));
862+
let stem = out
863+
.file_stem()
864+
.context("Failed to get file stem")?
865+
.to_str()
866+
.context("Failed to convert to str")?;
867+
868+
let parent = out
869+
.parent()
870+
.context("Output file has no parent directory")?;
871+
872+
cfg.target(&target).out_dir(parent);
873+
874+
let name = format!("lib{stem}.a");
875+
876+
cfg.try_compile(&name)
877+
.context(format!("failed to compile `{}`", name))
878+
.map(|_| out)
858879
}
859880

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

866886
fn generate_files_impl<P: AsRef<Path>>(&mut self, krate: P, out_file: &str) -> Result<PathBuf> {
867887
let krate = krate.as_ref();
888+
868889
// Prep the test generator
869890
let out_dir = self
870891
.out_dir
871892
.clone()
872-
.unwrap_or_else(|| PathBuf::from(env::var_os("OUT_DIR").unwrap()));
893+
.or_else(|| env::var_os("OUT_DIR").map(PathBuf::from))
894+
.context("Neither out_dir nor OUT_DIR environment variable is set")?;
895+
873896
let out_file = out_dir.join(out_file);
874897
let ext = match self.lang {
875898
Lang::C => "c",
@@ -878,18 +901,26 @@ impl TestGenerator {
878901
let c_file = out_file.with_extension(ext);
879902
let rust_out = BufWriter::new(File::create(&out_file)?);
880903
let c_out = BufWriter::new(File::create(&c_file)?);
881-
let mut sess = ParseSess::new(FilePathMapping::empty());
904+
882905
let target = self
883906
.target
884907
.clone()
885-
.unwrap_or_else(|| env::var("TARGET").unwrap());
908+
.or_else(|| env::var("TARGET").ok())
909+
.filter(|t| !t.is_empty())
910+
.context("TARGET environment variable not set or empty")?;
911+
912+
let mut sess = ParseSess::new(FilePathMapping::empty());
886913
for (k, v) in default_cfg(&target).into_iter().chain(self.cfg.clone()) {
887914
let s = |s: &str| Name::intern(s);
888915
sess.config.insert((s(&k), v.as_ref().map(|n| s(n))));
889916
}
890917

891-
// Parse the libc crate
892-
let krate = parse::parse_crate_from_file(krate, &sess).ok().unwrap();
918+
// Convert DiagnosticBuilder -> Error so the `?` works
919+
let krate = parse::parse_crate_from_file(krate, &sess).map_err(|mut d| {
920+
// Emit the diagnostic to properly handle it and show error to the user
921+
d.emit();
922+
anyhow!("failed to parse crate: {:?}", d)
923+
})?;
893924

894925
// Remove things like functions, impls, traits, etc, that we're not
895926
// looking at

0 commit comments

Comments
 (0)