diff --git a/Cargo.toml b/Cargo.toml index 2f1efab4..03ac2810 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,34 @@ [workspace] members = [ - "aya", "aya-obj", "aya-tool", "aya-log", "aya-log-common", "aya-log-parser", "test/integration-test", "test/integration-test-macros", "xtask", + "aya", + "aya-obj", + "aya-tool", + "aya-log", + "aya-log-common", + "aya-log-parser", + "test/integration-test", + "xtask", + # macros - "aya-bpf-macros", "aya-log-ebpf-macros", + "aya-bpf-macros", + "aya-log-ebpf-macros", + # ebpf crates - "bpf/aya-bpf", "bpf/aya-bpf-bindings", "bpf/aya-log-ebpf", "test/integration-ebpf" + "bpf/aya-bpf", + "bpf/aya-bpf-bindings", + "bpf/aya-log-ebpf", + "test/integration-ebpf", +] +resolver = "2" + +default-members = [ + "aya", + "aya-obj", + "aya-tool", + "aya-log", + "aya-bpf-macros", + "aya-log-ebpf-macros", ] -default-members = ["aya", "aya-obj", "aya-tool", "aya-log", "aya-bpf-macros", "aya-log-ebpf-macros"] [profile.dev] panic = "abort" diff --git a/aya-log-parser/Cargo.toml b/aya-log-parser/Cargo.toml index d76ae6b0..f47cb9f3 100644 --- a/aya-log-parser/Cargo.toml +++ b/aya-log-parser/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "aya-log-parser" version = "0.1.11-dev.0" -edition = "2018" +edition = "2021" [dependencies] aya-log-common = { path = "../aya-log-common" } diff --git a/bpf/aya-bpf-cty/Cargo.toml b/bpf/aya-bpf-cty/Cargo.toml index 8c849222..5cd8d69c 100644 --- a/bpf/aya-bpf-cty/Cargo.toml +++ b/bpf/aya-bpf-cty/Cargo.toml @@ -1,5 +1,6 @@ [package] authors = ["Jorge Aparicio "] +edition = "2021" categories = ["embedded", "external-ffi-bindings" ,"no-std"] description = "Type aliases to C types like c_int for use with bindgen" documentation = "https://docs.rs/cty" diff --git a/bpf/aya-bpf-cty/src/lib.rs b/bpf/aya-bpf-cty/src/lib.rs index 7828e0ee..aff438d3 100644 --- a/bpf/aya-bpf-cty/src/lib.rs +++ b/bpf/aya-bpf-cty/src/lib.rs @@ -46,7 +46,7 @@ mod ad { target_arch = "riscv64" ))] mod ad { - pub type c_char = ::c_uchar; + pub type c_char = super::c_uchar; pub type c_int = i32; pub type c_uint = u32; @@ -63,7 +63,7 @@ mod ad { target_arch = "xtensa" ))] mod ad { - pub type c_char = ::c_schar; + pub type c_char = super::c_schar; pub type c_int = i32; pub type c_uint = u32; @@ -71,7 +71,7 @@ mod ad { #[cfg(target_arch = "msp430")] mod ad { - pub type c_char = ::c_uchar; + pub type c_char = super::c_uchar; pub type c_int = i16; pub type c_uint = u16; diff --git a/test/README.md b/test/README.md index f58b20cb..09a7c49d 100644 --- a/test/README.md +++ b/test/README.md @@ -35,19 +35,20 @@ cargo xtask integration-test --libbpf-dir /path/to/libbpf ### Virtualized - ``` ./test/run.sh /path/to/libbpf ``` -### Writing a test + +### Writing an integration test Tests should follow these guidelines: -- Rust eBPF code should live in `integration-ebpf/${NAME}.rs` and included in `integration-ebpf/Cargo.toml` -- C eBPF code should live in `integration-test/src/bpf/${NAME}.bpf.c`. It's automatically compiled and made available as `${OUT_DIR}/${NAME}.bpf.o`. -- Any bytecode should be included in the integration test binary using `include_bytes_aligned!` -- Tests should be added to `integration-test/src/test` -- You may add a new module, or use an existing one -- Integration tests must use the `#[integration_test]` macro to be included in the build -- Test functions should return `anyhow::Result<()>` since this allows the use of `?` to return errors. -- You may either `panic!` when an assertion fails or `bail!`. The former is preferred since the stack trace will point directly to the failed line. +- Rust eBPF code should live in `integration-ebpf/${NAME}.rs` and included in + `integration-ebpf/Cargo.toml`. +- C eBPF code should live in `integration-ebpf/src/bpf/${NAME}.bpf.c`. It's automatically compiled + and made available as `${OUT_DIR}/${NAME}.bpf.o`. +- Any bytecode should be included in the integration test binary using `include_bytes_aligned!`. +- Tests should be added to `integration-test/tests`. +- You may add a new module, or use an existing one. +- Test functions should not return `anyhow::Result<()>` since this produces errors without stack + traces. Prefer to `panic!` instead. diff --git a/test/integration-test-macros/Cargo.toml b/test/integration-test-macros/Cargo.toml deleted file mode 100644 index f66b75a8..00000000 --- a/test/integration-test-macros/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "integration-test-macros" -version = "0.1.0" -edition = "2021" -publish = false - -[dependencies] -quote = "1" -proc-macro2 = "1.0" -syn = {version = "2.0", features = ["full"]} - -[lib] -proc-macro = true diff --git a/test/integration-test-macros/src/lib.rs b/test/integration-test-macros/src/lib.rs deleted file mode 100644 index 9818b7f5..00000000 --- a/test/integration-test-macros/src/lib.rs +++ /dev/null @@ -1,46 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::quote; -use syn::{parse_macro_input, Ident, ItemFn}; - -#[proc_macro_attribute] -pub fn integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream { - let item = parse_macro_input!(item as ItemFn); - let name = &item.sig.ident; - let name_str = &item.sig.ident.to_string(); - let expanded = quote! { - #item - - inventory::submit!(crate::IntegrationTest { - name: concat!(module_path!(), "::", #name_str), - test_fn: #name, - }); - }; - TokenStream::from(expanded) -} - -#[proc_macro_attribute] -pub fn tokio_integration_test(_attr: TokenStream, item: TokenStream) -> TokenStream { - let item = parse_macro_input!(item as ItemFn); - let name = &item.sig.ident; - let name_str = &item.sig.ident.to_string(); - let sync_name_str = format!("sync_{name_str}"); - let sync_name = Ident::new(&sync_name_str, Span::call_site()); - let expanded = quote! { - #item - - fn #sync_name() { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .unwrap(); - rt.block_on(#name()); - } - - inventory::submit!(crate::IntegrationTest { - name: concat!(module_path!(), "::", #sync_name_str), - test_fn: #sync_name, - }); - }; - TokenStream::from(expanded) -} diff --git a/test/integration-test/Cargo.toml b/test/integration-test/Cargo.toml index 174d66fb..07eaa927 100644 --- a/test/integration-test/Cargo.toml +++ b/test/integration-test/Cargo.toml @@ -9,16 +9,10 @@ anyhow = "1" aya = { path = "../../aya" } aya-log = { path = "../../aya-log" } aya-obj = { path = "../../aya-obj" } -clap = { version = "4", features = ["derive"] } -env_logger = "0.10" -futures-core = "0.3" -inventory = "0.3" -integration-test-macros = { path = "../integration-test-macros" } libc = { version = "0.2.105" } log = "0.4" object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] } rbpf = "0.2.0" regex = "1" tempfile = "3.3.0" -libtest-mimic = "0.6.0" tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] } diff --git a/test/integration-test/src/lib.rs b/test/integration-test/src/lib.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/integration-test/src/lib.rs @@ -0,0 +1 @@ + diff --git a/test/integration-test/src/main.rs b/test/integration-test/src/main.rs deleted file mode 100644 index b081e645..00000000 --- a/test/integration-test/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use libtest_mimic::{Arguments, Trial}; - -mod tests; -use tests::IntegrationTest; - -fn main() { - env_logger::init(); - let mut args = Arguments::from_args(); - // Force to run single-threaded - args.test_threads = Some(1); - let tests = inventory::iter:: - .into_iter() - .map(|test| { - Trial::test(test.name, move || { - (test.test_fn)(); - Ok(()) - }) - }) - .collect(); - libtest_mimic::run(&args, tests).exit(); -} diff --git a/test/integration-test/src/tests/bpf_probe_read.rs b/test/integration-test/tests/bpf_probe_read.rs similarity index 90% rename from test/integration-test/src/tests/bpf_probe_read.rs rename to test/integration-test/tests/bpf_probe_read.rs index 175ed114..b8d7aedd 100644 --- a/test/integration-test/src/tests/bpf_probe_read.rs +++ b/test/integration-test/tests/bpf_probe_read.rs @@ -6,7 +6,6 @@ use aya::{ programs::{ProgramError, UProbe}, Bpf, }; -use integration_test_macros::integration_test; const RESULT_BUF_LEN: usize = 1024; @@ -20,13 +19,13 @@ struct TestResult { unsafe impl aya::Pod for TestResult {} -#[integration_test] +#[test] fn bpf_probe_read_user_str_bytes() { let bpf = set_user_buffer(b"foo\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b"foo"); } -#[integration_test] +#[test] fn bpf_probe_read_user_str_bytes_truncate() { let s = vec![b'a'; RESULT_BUF_LEN]; let bpf = set_user_buffer(&s, RESULT_BUF_LEN); @@ -34,25 +33,25 @@ fn bpf_probe_read_user_str_bytes_truncate() { assert_eq!(result_bytes(&bpf), &s[..RESULT_BUF_LEN - 1]); } -#[integration_test] +#[test] fn bpf_probe_read_user_str_bytes_empty_string() { let bpf = set_user_buffer(b"\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b""); } -#[integration_test] +#[test] fn bpf_probe_read_user_str_bytes_empty_dest() { let bpf = set_user_buffer(b"foo\0", 0); assert_eq!(result_bytes(&bpf), b""); } -#[integration_test] +#[test] fn bpf_probe_read_kernel_str_bytes() { let bpf = set_kernel_buffer(b"foo\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b"foo"); } -#[integration_test] +#[test] fn bpf_probe_read_kernel_str_bytes_truncate() { let s = vec![b'a'; RESULT_BUF_LEN]; let bpf = set_kernel_buffer(&s, RESULT_BUF_LEN); @@ -60,13 +59,13 @@ fn bpf_probe_read_kernel_str_bytes_truncate() { assert_eq!(result_bytes(&bpf), &s[..RESULT_BUF_LEN - 1]); } -#[integration_test] +#[test] fn bpf_probe_read_kernel_str_bytes_empty_string() { let bpf = set_kernel_buffer(b"\0", RESULT_BUF_LEN); assert_eq!(result_bytes(&bpf), b""); } -#[integration_test] +#[test] fn bpf_probe_read_kernel_str_bytes_empty_dest() { let bpf = set_kernel_buffer(b"foo\0", 0); assert_eq!(result_bytes(&bpf), b""); @@ -76,7 +75,7 @@ fn set_user_buffer(bytes: &[u8], dest_len: usize) -> Bpf { let bpf = load_and_attach_uprobe( "test_bpf_probe_read_user_str_bytes", "trigger_bpf_probe_read_user", - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/bpf_probe_read"), + include_bytes_aligned!("../../../target/bpfel-unknown-none/release/bpf_probe_read"), ); trigger_bpf_probe_read_user(bytes.as_ptr(), dest_len); bpf @@ -86,7 +85,7 @@ fn set_kernel_buffer(bytes: &[u8], dest_len: usize) -> Bpf { let mut bpf = load_and_attach_uprobe( "test_bpf_probe_read_kernel_str_bytes", "trigger_bpf_probe_read_kernel", - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/bpf_probe_read"), + include_bytes_aligned!("../../../target/bpfel-unknown-none/release/bpf_probe_read"), ); set_kernel_buffer_element(&mut bpf, bytes); trigger_bpf_probe_read_kernel(dest_len); diff --git a/test/integration-test/src/tests/btf_relocations.rs b/test/integration-test/tests/btf_relocations.rs similarity index 98% rename from test/integration-test/src/tests/btf_relocations.rs rename to test/integration-test/tests/btf_relocations.rs index fccc7ba6..2811f74a 100644 --- a/test/integration-test/src/tests/btf_relocations.rs +++ b/test/integration-test/tests/btf_relocations.rs @@ -4,12 +4,10 @@ use tempfile::TempDir; use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness}; -use super::integration_test; - // In the tests below we often use values like 0xAAAAAAAA or -0x7AAAAAAA. Those values have no // special meaning, they just have "nice" bit patterns that can be helpful while debugging. -#[integration_test] +#[test] fn relocate_field() { let test = RelocationTest { local_definition: r#" @@ -40,7 +38,7 @@ fn relocate_field() { assert_eq!(test.run_no_btf().unwrap(), 3); } -#[integration_test] +#[test] fn relocate_enum() { let test = RelocationTest { local_definition: r#" @@ -60,7 +58,7 @@ fn relocate_enum() { assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAA); } -#[integration_test] +#[test] fn relocate_enum_signed() { let test = RelocationTest { local_definition: r#" @@ -80,7 +78,7 @@ fn relocate_enum_signed() { assert_eq!(test.run_no_btf().unwrap() as i64, -0x7AAAAAAAi64); } -#[integration_test] +#[test] fn relocate_enum64() { let test = RelocationTest { local_definition: r#" @@ -100,7 +98,7 @@ fn relocate_enum64() { assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAABBBBBBBB); } -#[integration_test] +#[test] fn relocate_enum64_signed() { let test = RelocationTest { local_definition: r#" @@ -120,7 +118,7 @@ fn relocate_enum64_signed() { assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64); } -#[integration_test] +#[test] fn relocate_pointer() { let test = RelocationTest { local_definition: r#" @@ -143,7 +141,7 @@ fn relocate_pointer() { assert_eq!(test.run_no_btf().unwrap(), 42); } -#[integration_test] +#[test] fn relocate_struct_flavors() { let definition = r#" struct foo {}; diff --git a/test/integration-test/src/tests/mod.rs b/test/integration-test/tests/common.rs similarity index 67% rename from test/integration-test/src/tests/mod.rs rename to test/integration-test/tests/common.rs index 50f92820..3af8a597 100644 --- a/test/integration-test/src/tests/mod.rs +++ b/test/integration-test/tests/common.rs @@ -3,24 +3,7 @@ use libc::{uname, utsname}; use regex::Regex; use std::{cell::OnceCell, ffi::CStr, mem}; -pub mod bpf_probe_read; -pub mod btf_relocations; -pub mod elf; -pub mod load; -pub mod log; -pub mod rbpf; -pub mod relocations; -pub mod smoke; - -pub use integration_test_macros::{integration_test, tokio_integration_test}; - -#[derive(Debug)] -pub struct IntegrationTest { - pub name: &'static str, - pub test_fn: fn(), -} - -pub(crate) fn kernel_version() -> anyhow::Result<(u8, u8, u8)> { +pub fn kernel_version() -> anyhow::Result<(u8, u8, u8)> { static mut RE: OnceCell = OnceCell::new(); let re = unsafe { &mut RE }.get_or_init(|| Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap()); @@ -38,5 +21,3 @@ pub(crate) fn kernel_version() -> anyhow::Result<(u8, u8, u8)> { bail!("no kernel version found"); } } - -inventory::collect!(IntegrationTest); diff --git a/test/integration-test/src/tests/elf.rs b/test/integration-test/tests/elf.rs similarity index 78% rename from test/integration-test/src/tests/elf.rs rename to test/integration-test/tests/elf.rs index 623c9362..3d37ddb4 100644 --- a/test/integration-test/src/tests/elf.rs +++ b/test/integration-test/tests/elf.rs @@ -1,11 +1,9 @@ -use super::integration_test; - use aya::include_bytes_aligned; use object::{Object, ObjectSymbol}; -#[integration_test] +#[test] fn test_maps() { - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/map_test"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/map_test"); let obj_file = object::File::parse(bytes).unwrap(); if obj_file.section_by_name("maps").is_none() { panic!("No 'maps' ELF section"); diff --git a/test/integration-test/src/tests/load.rs b/test/integration-test/tests/load.rs similarity index 87% rename from test/integration-test/src/tests/load.rs rename to test/integration-test/tests/load.rs index f78f3f73..110025b2 100644 --- a/test/integration-test/src/tests/load.rs +++ b/test/integration-test/tests/load.rs @@ -9,18 +9,16 @@ use aya::{ }, Bpf, }; -use log::warn; -use crate::tests::kernel_version; - -use super::integration_test; +mod common; +use common::kernel_version; const MAX_RETRIES: u32 = 100; const RETRY_DURATION_MS: u64 = 10; -#[integration_test] +#[test] fn long_name() { - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/name_test"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/name_test"); let mut bpf = Bpf::load(bytes).unwrap(); let name_prog: &mut Xdp = bpf .program_mut("ihaveaverylongname") @@ -35,10 +33,10 @@ fn long_name() { // Therefore, as long as we were able to load the program, this is good enough. } -#[integration_test] +#[test] fn multiple_btf_maps() { let bytes = - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/multimap-btf.bpf.o"); + include_bytes_aligned!("../../../target/bpfel-unknown-none/release/multimap-btf.bpf.o"); let mut bpf = Bpf::load(bytes).unwrap(); let map_1: Array<_, u64> = bpf.take_map("map_1").unwrap().try_into().unwrap(); @@ -73,9 +71,9 @@ macro_rules! assert_loaded { }; } -#[integration_test] +#[test] fn unload_xdp() { - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/test"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/test"); let mut bpf = Bpf::load(bytes).unwrap(); let prog: &mut Xdp = bpf .program_mut("test_unload_xdp") @@ -103,9 +101,9 @@ fn unload_xdp() { assert_loaded!("test_unload_xdp", false); } -#[integration_test] +#[test] fn unload_kprobe() { - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/test"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/test"); let mut bpf = Bpf::load(bytes).unwrap(); let prog: &mut KProbe = bpf .program_mut("test_unload_kpr") @@ -133,14 +131,14 @@ fn unload_kprobe() { assert_loaded!("test_unload_kpr", false); } -#[integration_test] +#[test] fn pin_link() { if kernel_version().unwrap() < (5, 9, 0) { - warn!("skipping test, XDP uses netlink"); + eprintln!("skipping test, XDP uses netlink"); return; } - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/test"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/test"); let mut bpf = Bpf::load(bytes).unwrap(); let prog: &mut Xdp = bpf .program_mut("test_unload_xdp") @@ -168,14 +166,14 @@ fn pin_link() { assert_loaded!("test_unload_xdp", false); } -#[integration_test] +#[test] fn pin_lifecycle() { if kernel_version().unwrap() < (5, 9, 0) { - warn!("skipping test, XDP uses netlink"); + eprintln!("skipping test, XDP uses netlink"); return; } - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/pass"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass"); // 1. Load Program and Pin { diff --git a/test/integration-test/src/tests/log.rs b/test/integration-test/tests/log.rs similarity index 96% rename from test/integration-test/src/tests/log.rs rename to test/integration-test/tests/log.rs index 811952a3..1c4251db 100644 --- a/test/integration-test/src/tests/log.rs +++ b/test/integration-test/tests/log.rs @@ -5,8 +5,6 @@ use aya_log::BpfLogger; use log::{Level, Log, Record}; use tokio::time::{sleep, Duration}; -use super::tokio_integration_test; - const MAX_ATTEMPTS: usize = 10; const TIMEOUT_MS: u64 = 10; @@ -89,9 +87,9 @@ impl Log for TestingLogger { } } -#[tokio_integration_test] +#[tokio::test] async fn log() { - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/log"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/log"); let mut bpf = Bpf::load(bytes).unwrap(); let (logger, captured_logs) = TestingLogger::with_capacity(5); diff --git a/test/integration-test/src/tests/rbpf.rs b/test/integration-test/tests/rbpf.rs similarity index 93% rename from test/integration-test/src/tests/rbpf.rs rename to test/integration-test/tests/rbpf.rs index 2f46381d..f45ba865 100644 --- a/test/integration-test/src/tests/rbpf.rs +++ b/test/integration-test/tests/rbpf.rs @@ -4,11 +4,9 @@ use std::collections::HashMap; use aya::include_bytes_aligned; use aya_obj::{generated::bpf_insn, Object, ProgramSection}; -use super::integration_test; - -#[integration_test] +#[test] fn run_with_rbpf() { - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/pass"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass"); let object = Object::parse(bytes).unwrap(); assert_eq!(object.programs.len(), 1); @@ -37,10 +35,10 @@ fn run_with_rbpf() { static mut MULTIMAP_MAPS: [*mut Vec; 2] = [null_mut(), null_mut()]; -#[integration_test] +#[test] fn use_map_with_rbpf() { let bytes = - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/multimap-btf.bpf.o"); + include_bytes_aligned!("../../../target/bpfel-unknown-none/release/multimap-btf.bpf.o"); let mut object = Object::parse(bytes).unwrap(); assert_eq!(object.programs.len(), 1); diff --git a/test/integration-test/src/tests/relocations.rs b/test/integration-test/tests/relocations.rs similarity index 85% rename from test/integration-test/src/tests/relocations.rs rename to test/integration-test/tests/relocations.rs index 8a97a822..ae95222e 100644 --- a/test/integration-test/src/tests/relocations.rs +++ b/test/integration-test/tests/relocations.rs @@ -5,13 +5,12 @@ use aya::{ programs::{ProgramError, UProbe}, Bpf, }; -use integration_test_macros::integration_test; -#[integration_test] +#[test] fn relocations() { let bpf = load_and_attach( "test_64_32_call_relocs", - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/relocations"), + include_bytes_aligned!("../../../target/bpfel-unknown-none/release/relocations"), ); trigger_relocations_program(); @@ -23,11 +22,11 @@ fn relocations() { assert_eq!(m.get(&2, 0).unwrap(), 3); } -#[integration_test] +#[test] fn text_64_64_reloc() { let mut bpf = load_and_attach( "test_text_64_64_reloc", - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/text_64_64_reloc.o"), + include_bytes_aligned!("../../../target/bpfel-unknown-none/release/text_64_64_reloc.o"), ); let mut m = aya::maps::Array::<_, u64>::try_from(bpf.map_mut("RESULTS").unwrap()).unwrap(); diff --git a/test/integration-test/src/tests/smoke.rs b/test/integration-test/tests/smoke.rs similarity index 72% rename from test/integration-test/src/tests/smoke.rs rename to test/integration-test/tests/smoke.rs index 17abd79b..daeed03a 100644 --- a/test/integration-test/src/tests/smoke.rs +++ b/test/integration-test/tests/smoke.rs @@ -3,24 +3,24 @@ use aya::{ programs::{Extension, Xdp, XdpFlags}, Bpf, BpfLoader, }; -use log::info; -use super::{integration_test, kernel_version}; +mod common; +use common::kernel_version; -#[integration_test] +#[test] fn xdp() { - let bytes = include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/pass"); + let bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/pass"); let mut bpf = Bpf::load(bytes).unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); dispatcher.load().unwrap(); dispatcher.attach("lo", XdpFlags::default()).unwrap(); } -#[integration_test] +#[test] fn extension() { let (major, minor, _) = kernel_version().unwrap(); if major < 5 || (minor == 5 && minor < 9) { - info!( + eprintln!( "skipping as {}.{} does not meet version requirement of 5.9", major, minor ); @@ -28,14 +28,13 @@ fn extension() { } // TODO: Check kernel version == 5.9 or later let main_bytes = - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/main.bpf.o"); + include_bytes_aligned!("../../../target/bpfel-unknown-none/release/main.bpf.o"); let mut bpf = Bpf::load(main_bytes).unwrap(); let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); pass.load().unwrap(); pass.attach("lo", XdpFlags::default()).unwrap(); - let ext_bytes = - include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/ext.bpf.o"); + let ext_bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/ext.bpf.o"); let mut bpf = BpfLoader::new().extension("drop").load(ext_bytes).unwrap(); let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 592534e6..2f8a74d5 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -5,11 +5,12 @@ authors = ["Alessandro Decina "] edition = "2021" [dependencies] +anyhow = "1" aya-tool = { path = "../aya-tool" } +cargo_metadata = "0.15.4" clap = { version = "4", features = ["derive"] } -anyhow = "1" -syn = "2" -quote = "1" -proc-macro2 = "1" indoc = "2.0" +proc-macro2 = "1" +quote = "1" serde_json = "1" +syn = "2" diff --git a/xtask/src/build_ebpf.rs b/xtask/src/build_ebpf.rs index 6f389ed4..91e57e72 100644 --- a/xtask/src/build_ebpf.rs +++ b/xtask/src/build_ebpf.rs @@ -4,7 +4,7 @@ use std::{ ffi::{OsStr, OsString}, fs, path::{Path, PathBuf}, - process::Command, + process::{Command, Output}, }; use anyhow::{bail, Context}; @@ -148,16 +148,20 @@ fn compile_with_clang>( .arg("-o") .arg(out.as_ref().as_os_str()); - let output = cmd.output().context("Failed to execute clang")?; - if !output.status.success() { + let Output { + status, + stdout, + stderr, + } = cmd.output().context("Failed to execute clang")?; + if !status.success() { bail!( "Failed to compile eBPF programs\n \ stdout=\n \ {}\n \ stderr=\n \ {}\n", - String::from_utf8(output.stdout).unwrap(), - String::from_utf8(output.stderr).unwrap() + String::from_utf8(stdout).unwrap(), + String::from_utf8(stderr).unwrap() ); } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 81f3e658..d0a8e333 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -24,15 +24,14 @@ enum Command { } fn main() { - let opts = XtaskOptions::parse(); + let XtaskOptions { command } = Parser::parse(); - use Command::*; - let ret = match opts.command { - Codegen(opts) => codegen::codegen(opts), - Docs => docs::docs(), - BuildIntegrationTest(opts) => build_test::build_test(opts), - BuildIntegrationTestEbpf(opts) => build_ebpf::build_ebpf(opts), - IntegrationTest(opts) => run::run(opts), + let ret = match command { + Command::Codegen(opts) => codegen::codegen(opts), + Command::Docs => docs::docs(), + Command::BuildIntegrationTest(opts) => build_test::build_test(opts), + Command::BuildIntegrationTestEbpf(opts) => build_ebpf::build_ebpf(opts), + Command::IntegrationTest(opts) => run::run(opts), }; if let Err(e) = ret { diff --git a/xtask/src/run.rs b/xtask/src/run.rs index eb48f7ad..42ec51b9 100644 --- a/xtask/src/run.rs +++ b/xtask/src/run.rs @@ -1,6 +1,12 @@ -use std::{os::unix::process::CommandExt, path::PathBuf, process::Command}; +use std::{ + fmt::Write as _, + io::BufReader, + path::PathBuf, + process::{Command, Stdio}, +}; use anyhow::Context as _; +use cargo_metadata::{Artifact, CompilerMessage, Message, Target}; use clap::Parser; use crate::build_ebpf::{build_ebpf, Architecture, BuildEbpfOptions as BuildOptions}; @@ -18,59 +24,123 @@ pub struct Options { pub runner: String, /// libbpf directory #[clap(long, action)] - pub libbpf_dir: String, + pub libbpf_dir: PathBuf, /// Arguments to pass to your application #[clap(name = "args", last = true)] pub run_args: Vec, } /// Build the project -fn build(opts: &Options) -> Result<(), anyhow::Error> { - let mut args = vec!["build"]; - if opts.release { - args.push("--release") +fn build(release: bool) -> Result, anyhow::Error> { + let mut cmd = Command::new("cargo"); + cmd.arg("build") + .arg("--tests") + .arg("--message-format=json") + .arg("--package=integration-test"); + if release { + cmd.arg("--release"); } - args.push("-p"); - args.push("integration-test"); - let status = Command::new("cargo") - .args(&args) - .status() - .expect("failed to build userspace"); + let mut cmd = cmd + .stdout(Stdio::piped()) + .spawn() + .with_context(|| format!("failed to spawn {cmd:?}"))?; + + let reader = BufReader::new(cmd.stdout.take().unwrap()); + let mut executables = Vec::new(); + let mut compiler_messages = String::new(); + for message in Message::parse_stream(reader) { + #[allow(clippy::collapsible_match)] + match message.context("valid JSON")? { + Message::CompilerArtifact(Artifact { + executable, + target: Target { src_path, .. }, + .. + }) => { + if let Some(executable) = executable { + executables.push((src_path.into(), executable.into())); + } + } + Message::CompilerMessage(CompilerMessage { message, .. }) => { + assert_eq!(writeln!(&mut compiler_messages, "{message}"), Ok(())); + } + _ => {} + } + } + + let status = cmd + .wait() + .with_context(|| format!("failed to wait for {cmd:?}"))?; + match status.code() { Some(code) => match code { - 0 => Ok(()), - code => Err(anyhow::anyhow!("exited with status code: {code}")), + 0 => Ok(executables), + code => Err(anyhow::anyhow!( + "{cmd:?} exited with status code {code}:\n{compiler_messages}" + )), }, - None => Err(anyhow::anyhow!("process terminated by signal")), + None => Err(anyhow::anyhow!("{cmd:?} terminated by signal")), } } /// Build and run the project pub fn run(opts: Options) -> Result<(), anyhow::Error> { + let Options { + bpf_target, + release, + runner, + libbpf_dir, + run_args, + } = opts; + // build our ebpf program followed by our application build_ebpf(BuildOptions { - target: opts.bpf_target, - libbpf_dir: PathBuf::from(&opts.libbpf_dir), + target: bpf_target, + libbpf_dir, }) - .context("Error while building eBPF program")?; - build(&opts).context("Error while building userspace application")?; - // profile we are building (release or debug) - let profile = if opts.release { "release" } else { "debug" }; - let bin_path = format!("target/{profile}/integration-test"); + .context("error while building eBPF program")?; - // arguments to pass to the application - let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect(); + let binaries = build(release).context("error while building userspace application")?; + let mut args = runner.trim().split_terminator(' '); + let runner = args.next().ok_or(anyhow::anyhow!("no first argument"))?; + let args = args.collect::>(); - // configure args - let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect(); - args.push(bin_path.as_str()); - args.append(&mut run_args); + let mut failures = String::new(); + for (src_path, binary) in binaries { + let mut cmd = Command::new(runner); + let cmd = cmd + .args(args.iter()) + .arg(binary) + .args(run_args.iter()) + .arg("--test-threads=1"); - // spawn the command - let err = Command::new(args.first().expect("No first argument")) - .args(args.iter().skip(1)) - .exec(); + println!("{} running {cmd:?}", src_path.display()); - // we shouldn't get here unless the command failed to spawn - Err(anyhow::Error::from(err).context(format!("Failed to run `{}`", args.join(" ")))) + let status = cmd + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .context("failed to run {cmd:?}")?; + match status.code() { + Some(code) => match code { + 0 => {} + code => assert_eq!( + writeln!( + &mut failures, + "{} exited with status code {code}", + src_path.display() + ), + Ok(()) + ), + }, + None => assert_eq!( + writeln!(&mut failures, "{} terminated by signal", src_path.display()), + Ok(()) + ), + } + } + if failures.is_empty() { + Ok(()) + } else { + Err(anyhow::anyhow!("failures:\n{}", failures)) + } }