diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32aa7156..8cf84948 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,15 +82,18 @@ jobs: cargo 3ds test --no-run --test layout_test --all-features --package ctru-sys cargo 3ds test --no-run --lib --all-features --package ctru-rs + - name: Run helper tests + run: cargo test --package bindgen-tests + - name: Run lib and integration tests uses: rust3ds/test-runner/run-tests@v1 with: - args: --tests --all-features --workspace + args: --tests --all-features - name: Build and run doc tests uses: rust3ds/test-runner/run-tests@v1 with: - args: --doc --workspace + args: --doc - name: Upload citra logs and capture videos uses: actions/upload-artifact@v3 diff --git a/Cargo.toml b/Cargo.toml index caaf7866..ab0bf0d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [workspace] -members = ["ctru-rs", "ctru-sys", "test-runner"] -default-members = ["ctru-rs", "ctru-sys"] +members = ["bindgen-tests", "ctru-rs", "ctru-sys", "test-runner"] +default-members = ["ctru-rs", "ctru-sys", "test-runner"] resolver = "2" [workspace.dependencies] libc = { version = "0.2.153", default-features = false } +bindgen = "0.69.4" shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" } test-runner = { git = "https://github.com/rust3ds/ctru-rs.git" } @@ -15,6 +16,7 @@ test-runner = { git = "https://github.com/rust3ds/ctru-rs.git" } test-runner = { path = "test-runner" } ctru-rs = { path = "ctru-rs" } ctru-sys = { path = "ctru-sys" } +bindgen-tests = { path = "bindgen-tests" } # This was the previous git repo for test-runner [patch."https://github.com/rust3ds/test-runner.git"] diff --git a/bindgen-tests/Cargo.toml b/bindgen-tests/Cargo.toml new file mode 100644 index 00000000..e99c0a74 --- /dev/null +++ b/bindgen-tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bindgen-tests" +version = "0.1.0" +edition = "2021" + +[features] +default = [] +generate = [ + "dep:bindgen", + "dep:proc-macro2", + "dep:quote", + "dep:regex", + "dep:rust-format", +] + +[dependencies] +bindgen = { workspace = true, features = ["experimental"], optional = true } +proc-macro2 = { version = "1.0.81", optional = true } +quote = { version = "1.0.36", optional = true } +regex = { version = "1.10.4", optional = true } +rust-format = { version = "0.3.4", features = ["token_stream"], optional = true } diff --git a/bindgen-tests/src/lib.rs b/bindgen-tests/src/lib.rs new file mode 100644 index 00000000..fc2aa2c5 --- /dev/null +++ b/bindgen-tests/src/lib.rs @@ -0,0 +1,91 @@ +#[cfg(feature = "generate")] +pub mod test_gen; +#[cfg(feature = "generate")] +pub use test_gen::*; + +pub use std::mem::offset_of; + +pub fn size_of_ret(_f: impl Fn(U) -> T) -> usize { + ::std::mem::size_of::() +} + +#[macro_export] +macro_rules! size_of { + ($ty:ident::$field:ident) => {{ + $crate::size_of_ret(|x: $ty| x.$field) + }}; + ($ty:ty) => { + ::std::mem::size_of::<$ty>() + }; + ($expr:expr) => { + ::std::mem::size_of_val(&$expr) + }; +} + +pub fn align_of_ret(_f: impl Fn(U) -> T) -> usize { + ::std::mem::align_of::() +} + +#[macro_export] +macro_rules! align_of { + ($ty:ident::$field:ident) => {{ + // This matches the semantics of C++ alignof when it is applied to a struct + // member. Packed structs may under-align fields, so we take the minimum + // of the align of the struct and the type of the field itself. + $crate::align_of_ret(|x: $ty| x.$field).min(align_of!($ty)) + }}; + ($ty:ty) => { + ::std::mem::align_of::<$ty>() + }; + ($expr:expr) => { + ::std::mem::align_of_val(&$expr) + }; +} + +#[cfg(test)] +mod tests { + macro_rules! packed_struct { + ($name:ident, $size:literal) => { + #[repr(C, packed($size))] + struct $name { + a: u8, + b: u16, + c: u32, + d: u64, + } + }; + } + + packed_struct!(PackedStruct1, 1); + packed_struct!(PackedStruct2, 2); + packed_struct!(PackedStruct4, 4); + packed_struct!(PackedStruct8, 8); + + #[test] + fn align_of_matches_cpp() { + // Expected values are based on C++: https://godbolt.org/z/dPnP7nEse + assert_eq!(align_of!(PackedStruct1), 1); + assert_eq!(align_of!(PackedStruct1::a), 1); + assert_eq!(align_of!(PackedStruct1::b), 1); + assert_eq!(align_of!(PackedStruct1::c), 1); + assert_eq!(align_of!(PackedStruct1::d), 1); + + assert_eq!(align_of!(PackedStruct2), 2); + assert_eq!(align_of!(PackedStruct2::a), 1); + assert_eq!(align_of!(PackedStruct2::b), 2); + assert_eq!(align_of!(PackedStruct2::c), 2); + assert_eq!(align_of!(PackedStruct2::d), 2); + + assert_eq!(align_of!(PackedStruct4), 4); + assert_eq!(align_of!(PackedStruct4::a), 1); + assert_eq!(align_of!(PackedStruct4::b), 2); + assert_eq!(align_of!(PackedStruct4::c), 4); + assert_eq!(align_of!(PackedStruct4::d), 4); + + assert_eq!(align_of!(PackedStruct8), 8); + assert_eq!(align_of!(PackedStruct8::a), 1); + assert_eq!(align_of!(PackedStruct8::b), 2); + assert_eq!(align_of!(PackedStruct8::c), 4); + assert_eq!(align_of!(PackedStruct8::d), 8); + } +} diff --git a/ctru-sys/build/test_gen.rs b/bindgen-tests/src/test_gen.rs similarity index 98% rename from ctru-sys/build/test_gen.rs rename to bindgen-tests/src/test_gen.rs index eb907cb0..c0c674cc 100644 --- a/ctru-sys/build/test_gen.rs +++ b/bindgen-tests/src/test_gen.rs @@ -203,9 +203,10 @@ impl LayoutTestGenerator { } } + let test_name = format_ident!("layout_test_{struct_name}"); quote! { #[test] - fn #name() { + fn #test_name() { #(#field_tests);* } } diff --git a/ctru-sys/Cargo.toml b/ctru-sys/Cargo.toml index 9253367e..4e106353 100644 --- a/ctru-sys/Cargo.toml +++ b/ctru-sys/Cargo.toml @@ -19,10 +19,7 @@ default = [] ## Downstream users of `ctru-sys` shouldn't need to use this feature. layout-tests = [ "dep:cpp_build", - "dep:proc-macro2", - "dep:quote", - "dep:regex", - "dep:rust-format", + "dep:bindgen-tests", ] [[test]] @@ -33,21 +30,19 @@ required-features = ["layout-tests"] libc = { workspace = true } [build-dependencies] -bindgen = { version = "0.69", features = ["experimental"] } +bindgen = { workspace = true, features = ["experimental"] } +bindgen-tests = { git = "https://github.com/rust3ds/ctru-rs.git", optional = true, features = ["generate"] } cc = "1.0" # Use git dependency so we can use https://github.com/mystor/rust-cpp/pull/111 cpp_build = { optional = true, git = "https://github.com/mystor/rust-cpp.git" } doxygen-rs = "0.4.2" itertools = "0.11.0" -proc-macro2 = { version = "1.0.81", optional = true } -quote = { version = "1.0.36", optional = true } -regex = { version = "1.10.4", optional = true } -rust-format = { version = "0.3.4", optional = true, features = ["token_stream"] } which = "4.4.0" [dev-dependencies] shim-3ds = { workspace = true } test-runner = { workspace = true } +bindgen-tests = { git = "https://github.com/rust3ds/ctru-rs.git" } cpp = "0.5.9" [package.metadata.docs.rs] diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index 5b1fe3c0..e42f40c0 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -7,13 +7,6 @@ use std::error::Error; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; -// This allows us to have a directory layout of build/*.rs which is a little -// cleaner than having all the submodules as siblings to build.rs. -mod build { - #[cfg(feature = "layout-tests")] - pub mod test_gen; -} - #[derive(Debug)] struct CustomCallbacks; @@ -146,7 +139,7 @@ fn main() { .parse_callbacks(Box::new(CustomCallbacks)); #[cfg(feature = "layout-tests")] - let (test_callbacks, test_generator) = build::test_gen::LayoutTestCallbacks::new(); + let (test_callbacks, test_generator) = bindgen_tests::LayoutTestCallbacks::new(); #[cfg(feature = "layout-tests")] let binding_builder = binding_builder.parse_callbacks(Box::new(test_callbacks)); @@ -275,7 +268,7 @@ fn track_libctru_files(pacman: &Path) -> Result<(), String> { #[cfg(feature = "layout-tests")] fn generate_layout_tests( output_file: &Path, - test_generator: &build::test_gen::LayoutTestGenerator, + test_generator: &bindgen_tests::LayoutTestGenerator, ) -> Result<(), Box> { // There are several bindgen-generated types/fields that we can't check: test_generator diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index 4e8e82f3..446091b2 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -14,11 +14,6 @@ )] #![doc(html_root_url = "https://rust3ds.github.io/ctru-rs/crates")] -// Prevent linking errors from the standard `test` library when running `cargo 3ds test --lib`. -// See https://github.com/rust-lang/rust-analyzer/issues/14167 for why we use `not(rust_analyzer)` -#[cfg(all(test, not(rust_analyzer)))] -extern crate shim_3ds; - pub mod result; pub use result::*; diff --git a/ctru-sys/tests/layout_test.rs b/ctru-sys/tests/layout_test.rs index a92cec4b..beee2497 100644 --- a/ctru-sys/tests/layout_test.rs +++ b/ctru-sys/tests/layout_test.rs @@ -9,99 +9,13 @@ #![feature(custom_test_frameworks)] #![test_runner(test_runner::run_gdb)] -extern crate shim_3ds; - -use std::mem::offset_of; - -fn size_of_ret(_f: impl Fn(U) -> T) -> usize { - ::std::mem::size_of::() -} - -macro_rules! size_of { - ($ty:ident::$field:ident) => {{ - $crate::size_of_ret(|x: $ty| x.$field) - }}; - ($ty:ty) => { - ::std::mem::size_of::<$ty>() - }; - ($expr:expr) => { - ::std::mem::size_of_val(&$expr) - }; -} - -fn align_of_ret(_f: impl Fn(U) -> T) -> usize { - ::std::mem::align_of::() -} - -macro_rules! align_of { - ($ty:ident::$field:ident) => {{ - // This matches the semantics of C++ alignof when it is applied to a struct - // member. Packed structs may under-align fields, so we take the minimum - // of the align of the struct and the type of the field itself. - $crate::align_of_ret(|x: $ty| x.$field).min(align_of!($ty)) - }}; - ($ty:ty) => { - ::std::mem::align_of::<$ty>() - }; - ($expr:expr) => { - ::std::mem::align_of_val(&$expr) - }; -} - #[allow(non_snake_case)] #[allow(non_upper_case_globals)] mod generated_tests { - use super::*; + use bindgen_tests::{align_of, offset_of, size_of}; use cpp::cpp; use ctru_sys::*; include!(concat!(env!("OUT_DIR"), "/generated_layout_test.rs")); } - -mod helper_tests { - macro_rules! packed_struct { - ($name:ident, $size:literal) => { - #[repr(C, packed($size))] - struct $name { - a: u8, - b: u16, - c: u32, - d: u64, - } - }; - } - - packed_struct!(PackedStruct1, 1); - packed_struct!(PackedStruct2, 2); - packed_struct!(PackedStruct4, 4); - packed_struct!(PackedStruct8, 8); - - #[test] - fn align_of_matches_cpp() { - // Expected values are based on C++: https://godbolt.org/z/dPnP7nEse - assert_eq!(align_of!(PackedStruct1), 1); - assert_eq!(align_of!(PackedStruct1::a), 1); - assert_eq!(align_of!(PackedStruct1::b), 1); - assert_eq!(align_of!(PackedStruct1::c), 1); - assert_eq!(align_of!(PackedStruct1::d), 1); - - assert_eq!(align_of!(PackedStruct2), 2); - assert_eq!(align_of!(PackedStruct2::a), 1); - assert_eq!(align_of!(PackedStruct2::b), 2); - assert_eq!(align_of!(PackedStruct2::c), 2); - assert_eq!(align_of!(PackedStruct2::d), 2); - - assert_eq!(align_of!(PackedStruct4), 4); - assert_eq!(align_of!(PackedStruct4::a), 1); - assert_eq!(align_of!(PackedStruct4::b), 2); - assert_eq!(align_of!(PackedStruct4::c), 4); - assert_eq!(align_of!(PackedStruct4::d), 4); - - assert_eq!(align_of!(PackedStruct8), 8); - assert_eq!(align_of!(PackedStruct8::a), 1); - assert_eq!(align_of!(PackedStruct8::b), 2); - assert_eq!(align_of!(PackedStruct8::c), 4); - assert_eq!(align_of!(PackedStruct8::d), 8); - } -} diff --git a/test-runner/Cargo.toml b/test-runner/Cargo.toml index c5d02d24..9cc9aa25 100644 --- a/test-runner/Cargo.toml +++ b/test-runner/Cargo.toml @@ -12,3 +12,4 @@ socket = [] ctru-rs = { git = "https://github.com/rust3ds/ctru-rs" } ctru-sys = { git = "https://github.com/rust3ds/ctru-rs" } libc = { workspace = true } +shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } diff --git a/test-runner/src/lib.rs b/test-runner/src/lib.rs index b4a3fcfe..cf642fd9 100644 --- a/test-runner/src/lib.rs +++ b/test-runner/src/lib.rs @@ -9,6 +9,7 @@ #![feature(exitcode_exit_method)] #![test_runner(run_gdb)] +extern crate shim_3ds; extern crate test; mod console;