From 5da155d493215691645a184ece6403e902a082fe Mon Sep 17 00:00:00 2001 From: mbyx Date: Fri, 18 Jul 2025 21:02:37 +0500 Subject: [PATCH] ctest: test ctest-next in ctest-test --- Cargo.lock | 1 + ctest-next/src/ast/function.rs | 6 + ctest-next/src/ast/static_variable.rs | 6 + ctest-next/src/ffi_items.rs | 30 ++++- ctest-next/templates/test.rs | 7 +- ctest-next/tests/input/hierarchy.out.rs | 5 +- ctest-next/tests/input/macro.out.rs | 3 +- .../tests/input/simple.out.with-renames.rs | 7 +- .../tests/input/simple.out.with-skips.rs | 5 +- ctest-test/Cargo.toml | 12 +- ctest-test/build.rs | 77 ++++++++++-- ctest-test/src/bin/t1.rs | 2 +- ctest-test/src/bin/t1_cxx.rs | 2 +- ctest-test/src/bin/t1_next.rs | 6 + ctest-test/src/bin/t2_next.rs | 6 + ctest-test/src/t1.rs | 10 +- ctest-test/src/t2.h | 3 +- ctest-test/src/t2.rs | 11 +- ctest-test/tests/all.rs | 112 ++++++++++++++---- 19 files changed, 247 insertions(+), 64 deletions(-) create mode 100644 ctest-test/src/bin/t1_next.rs create mode 100644 ctest-test/src/bin/t2_next.rs diff --git a/Cargo.lock b/Cargo.lock index a6eb90ac30366..33858c766027e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -149,6 +149,7 @@ dependencies = [ "cc", "cfg-if 1.0.1", "ctest", + "ctest-next", "libc 1.0.0-alpha.1", ] diff --git a/ctest-next/src/ast/function.rs b/ctest-next/src/ast/function.rs index bff21da44f5b8..8722c2b82b740 100644 --- a/ctest-next/src/ast/function.rs +++ b/ctest-next/src/ast/function.rs @@ -9,6 +9,7 @@ pub struct Fn { #[expect(unused)] pub(crate) abi: Abi, pub(crate) ident: BoxStr, + pub(crate) link_name: Option, #[expect(unused)] pub(crate) parameters: Vec, #[expect(unused)] @@ -20,4 +21,9 @@ impl Fn { pub fn ident(&self) -> &str { &self.ident } + + /// Return the name of the function to be linked C side with. + pub fn link_name(&self) -> Option<&str> { + self.link_name.as_deref() + } } diff --git a/ctest-next/src/ast/static_variable.rs b/ctest-next/src/ast/static_variable.rs index 87219cdadd130..6abc771664610 100644 --- a/ctest-next/src/ast/static_variable.rs +++ b/ctest-next/src/ast/static_variable.rs @@ -10,6 +10,7 @@ pub struct Static { #[expect(unused)] pub(crate) abi: Abi, pub(crate) ident: BoxStr, + pub(crate) link_name: Option, pub(crate) ty: syn::Type, } @@ -18,4 +19,9 @@ impl Static { pub fn ident(&self) -> &str { &self.ident } + + /// Return the name of the function to be linked C side with. + pub fn link_name(&self) -> Option<&str> { + self.link_name.as_deref() + } } diff --git a/ctest-next/src/ffi_items.rs b/ctest-next/src/ffi_items.rs index db3dd12487cde..49194f2cce280 100644 --- a/ctest-next/src/ffi_items.rs +++ b/ctest-next/src/ffi_items.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use syn::punctuated::Punctuated; use syn::visit::Visit; -use crate::{Abi, Const, Field, Fn, Parameter, Static, Struct, Type, Union}; +use crate::{Abi, BoxStr, Const, Field, Fn, Parameter, Static, Struct, Type, Union}; /// Represents a collected set of top-level Rust items relevant to FFI generation or analysis. /// @@ -95,6 +95,30 @@ fn collect_fields(fields: &Punctuated) -> Vec .collect() } +fn extract_single_link_name(attrs: &[syn::Attribute]) -> Option { + let mut link_name_iter = attrs + .iter() + .filter(|attr| attr.path().is_ident("link_name")); + + let link_name = link_name_iter.next().and_then(|attr| match &attr.meta { + syn::Meta::NameValue(nv) => { + if let syn::Expr::Lit(expr_lit) = &nv.value { + if let syn::Lit::Str(lit_str) = &expr_lit.lit { + return Some(lit_str.value().into_boxed_str()); + } + } + None + } + _ => None, + }); + + if let Some(attr) = link_name_iter.next() { + panic!("multiple `#[link_name = ...]` attributes found: {attr:?}"); + } + + link_name +} + fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi) { let public = is_visible(&i.vis); let abi = abi.clone(); @@ -122,11 +146,13 @@ fn visit_foreign_item_fn(table: &mut FfiItems, i: &syn::ForeignItemFn, abi: &Abi syn::ReturnType::Default => None, syn::ReturnType::Type(_, ty) => Some(ty.deref().clone()), }; + let link_name = extract_single_link_name(&i.attrs); table.foreign_functions.push(Fn { public, abi, ident, + link_name, parameters, return_type, }); @@ -137,11 +163,13 @@ fn visit_foreign_item_static(table: &mut FfiItems, i: &syn::ForeignItemStatic, a let abi = abi.clone(); let ident = i.ident.to_string().into_boxed_str(); let ty = i.ty.deref().clone(); + let link_name = extract_single_link_name(&i.attrs); table.foreign_statics.push(Static { public, abi, ident, + link_name, ty, }); } diff --git a/ctest-next/templates/test.rs b/ctest-next/templates/test.rs index d85da91b92c04..c18afd4a7fe08 100644 --- a/ctest-next/templates/test.rs +++ b/ctest-next/templates/test.rs @@ -9,9 +9,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + #[expect(unused_imports)] use std::{mem, ptr, slice}; use super::*; @@ -62,7 +63,7 @@ mod generated_tests { // SAFETY: FFI call returns a valid C string. let c_val = unsafe { - let c_ptr: *const c_char = unsafe { ctest_const_cstr__{{ const_cstr.id }}() }; + let c_ptr: *const c_char = ctest_const_cstr__{{ const_cstr.id }}(); CStr::from_ptr(c_ptr) }; @@ -89,7 +90,7 @@ mod generated_tests { }; let c_bytes = unsafe { - let c_ptr: *const T = unsafe { ctest_const__{{ constant.id }}() }; + let c_ptr: *const T = ctest_const__{{ constant.id }}(); slice::from_raw_parts(c_ptr.cast::(), size_of::()) }; diff --git a/ctest-next/tests/input/hierarchy.out.rs b/ctest-next/tests/input/hierarchy.out.rs index 214dfe986d7a4..371e9c73270a4 100644 --- a/ctest-next/tests/input/hierarchy.out.rs +++ b/ctest-next/tests/input/hierarchy.out.rs @@ -6,9 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + #[expect(unused_imports)] use std::{mem, ptr, slice}; use super::*; @@ -58,7 +59,7 @@ mod generated_tests { }; let c_bytes = unsafe { - let c_ptr: *const T = unsafe { ctest_const__ON() }; + let c_ptr: *const T = ctest_const__ON(); slice::from_raw_parts(c_ptr.cast::(), size_of::()) }; diff --git a/ctest-next/tests/input/macro.out.rs b/ctest-next/tests/input/macro.out.rs index 61c7b4a3a4f91..4ace05d8495b0 100644 --- a/ctest-next/tests/input/macro.out.rs +++ b/ctest-next/tests/input/macro.out.rs @@ -6,9 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + #[expect(unused_imports)] use std::{mem, ptr, slice}; use super::*; diff --git a/ctest-next/tests/input/simple.out.with-renames.rs b/ctest-next/tests/input/simple.out.with-renames.rs index f1a74e663faf8..6d4b4dbef62c3 100644 --- a/ctest-next/tests/input/simple.out.with-renames.rs +++ b/ctest-next/tests/input/simple.out.with-renames.rs @@ -6,9 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + #[expect(unused_imports)] use std::{mem, ptr, slice}; use super::*; @@ -57,7 +58,7 @@ mod generated_tests { // SAFETY: FFI call returns a valid C string. let c_val = unsafe { - let c_ptr: *const c_char = unsafe { ctest_const_cstr__A() }; + let c_ptr: *const c_char = ctest_const_cstr__A(); CStr::from_ptr(c_ptr) }; @@ -80,7 +81,7 @@ mod generated_tests { // SAFETY: FFI call returns a valid C string. let c_val = unsafe { - let c_ptr: *const c_char = unsafe { ctest_const_cstr__B() }; + let c_ptr: *const c_char = ctest_const_cstr__B(); CStr::from_ptr(c_ptr) }; diff --git a/ctest-next/tests/input/simple.out.with-skips.rs b/ctest-next/tests/input/simple.out.with-skips.rs index 25168dcd6b970..98823180d7167 100644 --- a/ctest-next/tests/input/simple.out.with-skips.rs +++ b/ctest-next/tests/input/simple.out.with-skips.rs @@ -6,9 +6,10 @@ mod generated_tests { #![allow(non_snake_case)] #![deny(improper_ctypes_definitions)] - use std::ffi::CStr; + use std::ffi::{CStr, c_char}; use std::fmt::{Debug, LowerHex}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; + #[expect(unused_imports)] use std::{mem, ptr, slice}; use super::*; @@ -57,7 +58,7 @@ mod generated_tests { // SAFETY: FFI call returns a valid C string. let c_val = unsafe { - let c_ptr: *const c_char = unsafe { ctest_const_cstr__A() }; + let c_ptr: *const c_char = ctest_const_cstr__A(); CStr::from_ptr(c_ptr) }; diff --git a/ctest-test/Cargo.toml b/ctest-test/Cargo.toml index 64a12e7835040..0f80498473793 100644 --- a/ctest-test/Cargo.toml +++ b/ctest-test/Cargo.toml @@ -3,14 +3,16 @@ name = "ctest-test" version = "0.1.0" authors = ["Alex Crichton "] publish = false -edition = "2021" +edition = "2024" [build-dependencies] ctest = { path = "../ctest" } +ctest-next = { path = "../ctest-next" } cc = "1.2" [dev-dependencies] ctest = { path = "../ctest" } +ctest-next = { path = "../ctest-next" } [dependencies] cfg-if = "1.0.1" @@ -24,6 +26,14 @@ test = false name = "t2" test = false +[[bin]] +name = "t1_next" +test = false + +[[bin]] +name = "t2_next" +test = false + [[bin]] name = "t1_cxx" test = false diff --git a/ctest-test/build.rs b/ctest-test/build.rs index b67c2eaaa4639..bb394873d5729 100644 --- a/ctest-test/build.rs +++ b/ctest-test/build.rs @@ -10,19 +10,14 @@ fn main() { if profile == "release" || opt_level >= 2 { println!("cargo:rustc-cfg=optimized"); } - cc::Build::new() - .include("src") - .warnings(false) - .file("src/t1.c") - .compile("libt1.a"); - println!("cargo:rerun-if-changed=src/t1.c"); - println!("cargo:rerun-if-changed=src/t1.h"); - cc::Build::new() - .warnings(false) - .file("src/t2.c") - .compile("libt2.a"); - println!("cargo:rerun-if-changed=src/t2.c"); - println!("cargo:rerun-if-changed=src/t2.h"); + + do_cc(); + test_ctest_c(); + test_ctest_cpp(); + test_ctest_next(); +} + +fn test_ctest_c() { ctest::TestGenerator::new() .header("t1.h") .include("src") @@ -37,10 +32,13 @@ fn main() { .volatile_item(t1_volatile) .array_arg(t1_arrays) .skip_roundtrip(|n| n == "Arr") + .skip_const(|name| name == "T1S") .generate("src/t1.rs", "t1gen.rs"); + ctest::TestGenerator::new() .header("t2.h") .include("src") + .skip_const(|name| name == "T2S") .type_name(move |ty, is_struct, is_union| match ty { "T2Union" => ty.to_string(), t if is_struct => format!("struct {t}"), @@ -49,7 +47,9 @@ fn main() { }) .skip_roundtrip(|_| true) .generate("src/t2.rs", "t2gen.rs"); +} +fn test_ctest_cpp() { println!("cargo::rustc-check-cfg=cfg(has_cxx)"); if !cfg!(unix) || Command::new("c++").arg("v").output().is_ok() { // A C compiler is always available, but these are only run if a C++ compiler is @@ -68,14 +68,17 @@ fn main() { t if is_union => format!("union {t}"), t => t.to_string(), }) + .skip_const(|name| name == "T1S") .volatile_item(t1_volatile) .array_arg(t1_arrays) .skip_roundtrip(|n| n == "Arr") .generate("src/t1.rs", "t1gen_cxx.rs"); + ctest::TestGenerator::new() .header("t2.h") .language(ctest::Lang::CXX) .include("src") + .skip_const(|name| name == "T2S") .type_name(move |ty, is_struct, is_union| match ty { "T2Union" => ty.to_string(), t if is_struct => format!("struct {t}"), @@ -89,6 +92,54 @@ fn main() { } } +fn test_ctest_next() { + let mut t1gen = ctest_next::TestGenerator::new(); + t1gen + .header("t1.h") + .include("src") + .skip_private(true) + .rename_fn(|f| f.link_name().unwrap_or(f.ident()).to_string().into()) + .rename_union_ty(|ty| (ty == "T1Union").then_some(ty.to_string())) + .rename_struct_ty(|ty| (ty == "Transparent").then_some(ty.to_string())) + .volatile_struct_field(|s, f| s.ident() == "V" && f.ident() == "v") + .volatile_static(|s| s.ident() == "vol_ptr") + .volatile_static(|s| s.ident() == "T1_fn_ptr_vol") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol0" && p.ident() == "arg0") + .volatile_fn_arg(|f, p| f.ident() == "T1_vol2" && p.ident() == "arg1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol1") + .volatile_fn_return_type(|f| f.ident() == "T1_vol2") + // The parameter `a` of the functions `T1r`, `T1s`, `T1t`, `T1v` is an array. + .array_arg(|f, p| matches!(f.ident(), "T1r" | "T1s" | "T1t" | "T1v") && p.ident() == "a") + .skip_roundtrip(|n| n == "Arr"); + ctest_next::generate_test(&mut t1gen, "src/t1.rs", "t1nextgen.rs").unwrap(); + + let mut t2gen = ctest_next::TestGenerator::new(); + t2gen + .header("t2.h") + .include("src") + // public C typedefs have to manually be specified because they are identical to normal + // structs on the Rust side. + .rename_union_ty(|ty| (ty == "T2Union").then_some(ty.to_string())) + .skip_roundtrip(|_| true); + ctest_next::generate_test(&mut t2gen, "src/t2.rs", "t2nextgen.rs").unwrap(); +} + +fn do_cc() { + cc::Build::new() + .include("src") + .warnings(false) + .file("src/t1.c") + .compile("libt1.a"); + println!("cargo:rerun-if-changed=src/t1.c"); + println!("cargo:rerun-if-changed=src/t1.h"); + cc::Build::new() + .warnings(false) + .file("src/t2.c") + .compile("libt2.a"); + println!("cargo:rerun-if-changed=src/t2.c"); + println!("cargo:rerun-if-changed=src/t2.h"); +} + fn t1_volatile(i: ctest::VolatileItemKind) -> bool { use ctest::VolatileItemKind::*; match i { diff --git a/ctest-test/src/bin/t1.rs b/ctest-test/src/bin/t1.rs index cbe9090eecd4b..664dce9b90c73 100644 --- a/ctest-test/src/bin/t1.rs +++ b/ctest-test/src/bin/t1.rs @@ -2,6 +2,6 @@ #![deny(warnings)] use ctest_test::t1::*; -use libc::*; +use std::ffi::c_uint; include!(concat!(env!("OUT_DIR"), "/t1gen.rs")); diff --git a/ctest-test/src/bin/t1_cxx.rs b/ctest-test/src/bin/t1_cxx.rs index 2e1e192a1e210..7d83b54a49f51 100644 --- a/ctest-test/src/bin/t1_cxx.rs +++ b/ctest-test/src/bin/t1_cxx.rs @@ -3,7 +3,7 @@ cfg_if::cfg_if! { if #[cfg(has_cxx)] { use ctest_test::t1::*; - use libc::*; + use std::ffi::c_uint; include!(concat!(env!("OUT_DIR"), "/t1gen_cxx.rs")); } else { diff --git a/ctest-test/src/bin/t1_next.rs b/ctest-test/src/bin/t1_next.rs new file mode 100644 index 0000000000000..4d01a642054e8 --- /dev/null +++ b/ctest-test/src/bin/t1_next.rs @@ -0,0 +1,6 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use ctest_test::t1::*; + +include!(concat!(env!("OUT_DIR"), "/t1nextgen.rs")); diff --git a/ctest-test/src/bin/t2_next.rs b/ctest-test/src/bin/t2_next.rs new file mode 100644 index 0000000000000..64ef07811ab0c --- /dev/null +++ b/ctest-test/src/bin/t2_next.rs @@ -0,0 +1,6 @@ +#![cfg(not(test))] +#![deny(warnings)] + +use ctest_test::t2::*; + +include!(concat!(env!("OUT_DIR"), "/t2nextgen.rs")); diff --git a/ctest-test/src/t1.rs b/ctest-test/src/t1.rs index 77a2873204c5f..2e04439d835a8 100644 --- a/ctest-test/src/t1.rs +++ b/ctest-test/src/t1.rs @@ -1,9 +1,9 @@ #![allow(dead_code)] -use libc::*; +use std::ffi::{c_char, c_double, c_int, c_long, c_uint, c_void}; pub type T1Foo = i32; -pub const T1S: &str = "foo"; +pub const T1S: *const c_char = b"foo\0".as_ptr().cast(); pub const T1N: i32 = 5; @@ -61,7 +61,7 @@ const NOT_PRESENT: u32 = 5; pub type Arr = [i32; 4]; -extern "C" { +unsafe extern "C" { pub fn T1a(); pub fn T1b() -> *mut c_void; pub fn T1c(a: *mut c_void) -> *mut c_void; @@ -91,7 +91,7 @@ pub fn foo() { assert_eq!(1, 1); } -extern "C" { +unsafe extern "C" { pub static T1_static_u8: u8; /* FIXME(#4365): duplicate symbol errors when enabled // pub static mut T1_static_mut_u8: u8; @@ -177,7 +177,7 @@ pub struct V { pub v: *mut u8, } -extern "C" { +unsafe extern "C" { pub static mut vol_ptr: *mut u8; pub fn T1_vol0(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; pub fn T1_vol1(arg0: *mut c_void, arg1: *mut c_void) -> *mut c_void; diff --git a/ctest-test/src/t2.h b/ctest-test/src/t2.h index 9f99e11a1e79d..2f0b644bd485a 100644 --- a/ctest-test/src/t2.h +++ b/ctest-test/src/t2.h @@ -17,7 +17,8 @@ typedef struct { int64_t b; } T2Union; -static void T2a(void) {} +// FIXME(ctest): Cannot be uncommented until tests for functions are implemented in ctest-next. +// static void T2a(void) {} #define T2C 4 #define T2S "a" diff --git a/ctest-test/src/t2.rs b/ctest-test/src/t2.rs index bafeaef7cd897..d6b93a021df4d 100644 --- a/ctest-test/src/t2.rs +++ b/ctest-test/src/t2.rs @@ -1,4 +1,4 @@ -use libc::*; +use std::ffi::{c_char, c_int}; pub type T2Foo = u32; pub type T2Bar = u32; @@ -28,9 +28,10 @@ pub union T2Union { pub const T2C: i32 = 5; i! { - pub const T2S: &str = "b"; + pub const T2S: *const c_char = b"b\0".as_ptr().cast(); } -extern "C" { - pub fn T2a(); -} +// FIXME(ctest): Cannot be uncommented until tests for functions are implemented in ctest-next. +// extern "C" { +// pub fn T2a(); +// } diff --git a/ctest-test/tests/all.rs b/ctest-test/tests/all.rs index b8f29e6799737..d46f05afd22ae 100644 --- a/ctest-test/tests/all.rs +++ b/ctest-test/tests/all.rs @@ -5,16 +5,18 @@ use std::collections::HashSet; use std::env; use std::process::{Command, ExitStatus}; +/// Create a command that starts in the `target/debug` or `target/release` directory. fn cmd(name: &str) -> Command { - let mut p = env::current_exe().unwrap(); - p.pop(); - if p.file_name().unwrap().to_str() == Some("deps") { - p.pop(); + let mut path = env::current_exe().unwrap(); + path.pop(); + if path.file_name().unwrap().to_str() == Some("deps") { + path.pop(); } - p.push(name); - Command::new(p) + path.push(name); + Command::new(path) } +/// Executes a command, returning stdout and stderr combined and it's status. fn output(cmd: &mut Command) -> (String, ExitStatus) { eprintln!("command: {cmd:?}"); let output = cmd.output().unwrap(); @@ -26,24 +28,35 @@ fn output(cmd: &mut Command) -> (String, ExitStatus) { #[test] fn t1() { - let (o, status) = output(&mut cmd("t1")); - assert!(status.success(), "output: {o}"); - assert!(!o.contains("bad "), "{o}"); - eprintln!("o: {o}"); + let (output, status) = output(&mut cmd("t1")); + assert!(status.success(), "output: {output}"); + assert!(!output.contains("bad "), "{output}"); + eprintln!("output: {output}"); } #[test] #[cfg(has_cxx)] fn t1_cxx() { - let (o, status) = output(&mut cmd("t1_cxx")); - assert!(status.success(), "output: {o}"); - assert!(!o.contains("bad "), "{o}"); + let (output, status) = output(&mut cmd("t1_cxx")); + assert!(status.success(), "output: {output}"); + assert!(!output.contains("bad "), "{output}"); } +#[test] +fn t1_next() { + // t1 must run to completion without any errors. + let (output, status) = output(&mut cmd("t1_next")); + assert!(status.success(), "output: {output}"); + assert!(!output.contains("bad "), "{output}"); + eprintln!("output: {output}"); +} + +// FIXME(ctest): Errors currently commented out are not tested. + #[test] fn t2() { - let (o, status) = output(&mut cmd("t2")); - assert!(!status.success(), "output: {o}"); + let (output, status) = output(&mut cmd("t2")); + assert!(!status.success(), "output: {output}"); let errors = [ "bad T2Foo signed", "bad T2TypedefFoo signed", @@ -56,9 +69,9 @@ fn t2() { "bad field type a of T2Baz", "bad field offset b of T2Baz", "bad field type b of T2Baz", - "bad T2a function pointer", + // "bad T2a function pointer", "bad T2C value at byte 0", - "bad T2S string", + // "bad T2S string", "bad T2Union size", "bad field type b of T2Union", "bad field offset b of T2Union", @@ -66,7 +79,7 @@ fn t2() { let mut errors = errors.iter().cloned().collect::>(); let mut bad = false; - for line in o.lines().filter(|l| l.starts_with("bad ")) { + for line in output.lines().filter(|l| l.starts_with("bad ")) { let msg = &line[..line.find(":").unwrap()]; if !errors.remove(&msg) { println!("unknown error: {msg}"); @@ -79,7 +92,7 @@ fn t2() { bad = true; } if bad { - println!("output was:\n\n{o}"); + println!("output was:\n\n{output}"); panic!(); } } @@ -87,8 +100,8 @@ fn t2() { #[test] #[cfg(has_cxx)] fn t2_cxx() { - let (o, status) = output(&mut cmd("t2_cxx")); - assert!(!status.success(), "output: {o}"); + let (output, status) = output(&mut cmd("t2_cxx")); + assert!(!status.success(), "output: {output}"); let errors = [ "bad T2Foo signed", "bad T2TypedefFoo signed", @@ -101,9 +114,9 @@ fn t2_cxx() { "bad field type a of T2Baz", "bad field offset b of T2Baz", "bad field type b of T2Baz", - "bad T2a function pointer", + // "bad T2a function pointer", "bad T2C value at byte 0", - "bad T2S string", + // "bad T2S string", "bad T2Union size", "bad field type b of T2Union", "bad field offset b of T2Union", @@ -111,7 +124,53 @@ fn t2_cxx() { let mut errors = errors.iter().cloned().collect::>(); let mut bad = false; - for line in o.lines().filter(|l| l.starts_with("bad ")) { + for line in output.lines().filter(|l| l.starts_with("bad ")) { + let msg = &line[..line.find(":").unwrap()]; + if !errors.remove(&msg) { + println!("unknown error: {msg}"); + bad = true; + } + } + + for error in errors { + println!("didn't find error: {error}"); + bad = true; + } + if bad { + println!("output was:\n\n{output}"); + panic!(); + } +} + +#[test] +fn t2_next() { + // t2 must fail to run to completion, and only have the errors we expect it to have. + let (output, status) = output(&mut cmd("t2_next")); + assert!(!status.success(), "output: {output}"); + let errors = [ + // "bad T2Foo signed", + // "bad T2TypedefFoo signed", + // "bad T2TypedefInt signed", + // "bad T2Bar size", + // "bad T2Bar align", + // "bad T2Bar signed", + // "bad T2Baz size", + // "bad field offset a of T2Baz", + // "bad field type a of T2Baz", + // "bad field offset b of T2Baz", + // "bad field type b of T2Baz", + // "bad T2a function pointer", + "bad T2C value at byte 0", + "bad const T2S string", + // "bad T2Union size", + // "bad field type b of T2Union", + // "bad field offset b of T2Union", + ]; + let mut errors = errors.iter().cloned().collect::>(); + + // Extract any errors that are not contained within the known error set. + let mut bad = false; + for line in output.lines().filter(|l| l.starts_with("bad ")) { let msg = &line[..line.find(":").unwrap()]; if !errors.remove(&msg) { println!("unknown error: {msg}"); @@ -119,16 +178,19 @@ fn t2_cxx() { } } + // If any errors are left over, t2 did not run properly. for error in errors { println!("didn't find error: {error}"); bad = true; } if bad { - println!("output was:\n\n{o}"); + println!("output was:\n\n{output}"); panic!(); } } +// FIXME(ctest): If needed, maybe add similar tests for ctest-next but as integration tests? + #[test] fn test_missing_out_dir() { // Save original OUT_DIR