Merge pull request #640 from aya-rs/lossy-conversions

integration-test: Remove integration-test-macros
reviewable/pr614/r1
Alessandro Decina 1 year ago committed by GitHub
commit ed70a47845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,12 +1,34 @@
[workspace] [workspace]
members = [ 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 # macros
"aya-bpf-macros", "aya-log-ebpf-macros", "aya-bpf-macros",
"aya-log-ebpf-macros",
# ebpf crates # 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] [profile.dev]
panic = "abort" panic = "abort"

@ -1,7 +1,7 @@
[package] [package]
name = "aya-log-parser" name = "aya-log-parser"
version = "0.1.11-dev.0" version = "0.1.11-dev.0"
edition = "2018" edition = "2021"
[dependencies] [dependencies]
aya-log-common = { path = "../aya-log-common" } aya-log-common = { path = "../aya-log-common" }

@ -1,5 +1,6 @@
[package] [package]
authors = ["Jorge Aparicio <jorge@japaric.io>"] authors = ["Jorge Aparicio <jorge@japaric.io>"]
edition = "2021"
categories = ["embedded", "external-ffi-bindings" ,"no-std"] categories = ["embedded", "external-ffi-bindings" ,"no-std"]
description = "Type aliases to C types like c_int for use with bindgen" description = "Type aliases to C types like c_int for use with bindgen"
documentation = "https://docs.rs/cty" documentation = "https://docs.rs/cty"

@ -46,7 +46,7 @@ mod ad {
target_arch = "riscv64" target_arch = "riscv64"
))] ))]
mod ad { mod ad {
pub type c_char = ::c_uchar; pub type c_char = super::c_uchar;
pub type c_int = i32; pub type c_int = i32;
pub type c_uint = u32; pub type c_uint = u32;
@ -63,7 +63,7 @@ mod ad {
target_arch = "xtensa" target_arch = "xtensa"
))] ))]
mod ad { mod ad {
pub type c_char = ::c_schar; pub type c_char = super::c_schar;
pub type c_int = i32; pub type c_int = i32;
pub type c_uint = u32; pub type c_uint = u32;
@ -71,7 +71,7 @@ mod ad {
#[cfg(target_arch = "msp430")] #[cfg(target_arch = "msp430")]
mod ad { mod ad {
pub type c_char = ::c_uchar; pub type c_char = super::c_uchar;
pub type c_int = i16; pub type c_int = i16;
pub type c_uint = u16; pub type c_uint = u16;

@ -35,19 +35,20 @@ cargo xtask integration-test --libbpf-dir /path/to/libbpf
### Virtualized ### Virtualized
``` ```
./test/run.sh /path/to/libbpf ./test/run.sh /path/to/libbpf
``` ```
### Writing a test
### Writing an integration test
Tests should follow these guidelines: Tests should follow these guidelines:
- Rust eBPF code should live in `integration-ebpf/${NAME}.rs` and included in `integration-ebpf/Cargo.toml` - Rust eBPF code should live in `integration-ebpf/${NAME}.rs` and included in
- 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`. `integration-ebpf/Cargo.toml`.
- Any bytecode should be included in the integration test binary using `include_bytes_aligned!` - C eBPF code should live in `integration-ebpf/src/bpf/${NAME}.bpf.c`. It's automatically compiled
- Tests should be added to `integration-test/src/test` and made available as `${OUT_DIR}/${NAME}.bpf.o`.
- You may add a new module, or use an existing one - Any bytecode should be included in the integration test binary using `include_bytes_aligned!`.
- Integration tests must use the `#[integration_test]` macro to be included in the build - Tests should be added to `integration-test/tests`.
- Test functions should return `anyhow::Result<()>` since this allows the use of `?` to return errors. - You may add a new module, or use an existing one.
- 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. - Test functions should not return `anyhow::Result<()>` since this produces errors without stack
traces. Prefer to `panic!` instead.

@ -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

@ -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)
}

@ -9,16 +9,10 @@ anyhow = "1"
aya = { path = "../../aya" } aya = { path = "../../aya" }
aya-log = { path = "../../aya-log" } aya-log = { path = "../../aya-log" }
aya-obj = { path = "../../aya-obj" } 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" } libc = { version = "0.2.105" }
log = "0.4" log = "0.4"
object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] } object = { version = "0.31", default-features = false, features = ["std", "read_core", "elf"] }
rbpf = "0.2.0" rbpf = "0.2.0"
regex = "1" regex = "1"
tempfile = "3.3.0" tempfile = "3.3.0"
libtest-mimic = "0.6.0"
tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] } tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "sync", "time"] }

@ -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::<IntegrationTest>
.into_iter()
.map(|test| {
Trial::test(test.name, move || {
(test.test_fn)();
Ok(())
})
})
.collect();
libtest_mimic::run(&args, tests).exit();
}

@ -6,7 +6,6 @@ use aya::{
programs::{ProgramError, UProbe}, programs::{ProgramError, UProbe},
Bpf, Bpf,
}; };
use integration_test_macros::integration_test;
const RESULT_BUF_LEN: usize = 1024; const RESULT_BUF_LEN: usize = 1024;
@ -20,13 +19,13 @@ struct TestResult {
unsafe impl aya::Pod for TestResult {} unsafe impl aya::Pod for TestResult {}
#[integration_test] #[test]
fn bpf_probe_read_user_str_bytes() { fn bpf_probe_read_user_str_bytes() {
let bpf = set_user_buffer(b"foo\0", RESULT_BUF_LEN); let bpf = set_user_buffer(b"foo\0", RESULT_BUF_LEN);
assert_eq!(result_bytes(&bpf), b"foo"); assert_eq!(result_bytes(&bpf), b"foo");
} }
#[integration_test] #[test]
fn bpf_probe_read_user_str_bytes_truncate() { fn bpf_probe_read_user_str_bytes_truncate() {
let s = vec![b'a'; RESULT_BUF_LEN]; let s = vec![b'a'; RESULT_BUF_LEN];
let bpf = set_user_buffer(&s, 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]); assert_eq!(result_bytes(&bpf), &s[..RESULT_BUF_LEN - 1]);
} }
#[integration_test] #[test]
fn bpf_probe_read_user_str_bytes_empty_string() { fn bpf_probe_read_user_str_bytes_empty_string() {
let bpf = set_user_buffer(b"\0", RESULT_BUF_LEN); let bpf = set_user_buffer(b"\0", RESULT_BUF_LEN);
assert_eq!(result_bytes(&bpf), b""); assert_eq!(result_bytes(&bpf), b"");
} }
#[integration_test] #[test]
fn bpf_probe_read_user_str_bytes_empty_dest() { fn bpf_probe_read_user_str_bytes_empty_dest() {
let bpf = set_user_buffer(b"foo\0", 0); let bpf = set_user_buffer(b"foo\0", 0);
assert_eq!(result_bytes(&bpf), b""); assert_eq!(result_bytes(&bpf), b"");
} }
#[integration_test] #[test]
fn bpf_probe_read_kernel_str_bytes() { fn bpf_probe_read_kernel_str_bytes() {
let bpf = set_kernel_buffer(b"foo\0", RESULT_BUF_LEN); let bpf = set_kernel_buffer(b"foo\0", RESULT_BUF_LEN);
assert_eq!(result_bytes(&bpf), b"foo"); assert_eq!(result_bytes(&bpf), b"foo");
} }
#[integration_test] #[test]
fn bpf_probe_read_kernel_str_bytes_truncate() { fn bpf_probe_read_kernel_str_bytes_truncate() {
let s = vec![b'a'; RESULT_BUF_LEN]; let s = vec![b'a'; RESULT_BUF_LEN];
let bpf = set_kernel_buffer(&s, 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]); assert_eq!(result_bytes(&bpf), &s[..RESULT_BUF_LEN - 1]);
} }
#[integration_test] #[test]
fn bpf_probe_read_kernel_str_bytes_empty_string() { fn bpf_probe_read_kernel_str_bytes_empty_string() {
let bpf = set_kernel_buffer(b"\0", RESULT_BUF_LEN); let bpf = set_kernel_buffer(b"\0", RESULT_BUF_LEN);
assert_eq!(result_bytes(&bpf), b""); assert_eq!(result_bytes(&bpf), b"");
} }
#[integration_test] #[test]
fn bpf_probe_read_kernel_str_bytes_empty_dest() { fn bpf_probe_read_kernel_str_bytes_empty_dest() {
let bpf = set_kernel_buffer(b"foo\0", 0); let bpf = set_kernel_buffer(b"foo\0", 0);
assert_eq!(result_bytes(&bpf), b""); 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( let bpf = load_and_attach_uprobe(
"test_bpf_probe_read_user_str_bytes", "test_bpf_probe_read_user_str_bytes",
"trigger_bpf_probe_read_user", "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); trigger_bpf_probe_read_user(bytes.as_ptr(), dest_len);
bpf bpf
@ -86,7 +85,7 @@ fn set_kernel_buffer(bytes: &[u8], dest_len: usize) -> Bpf {
let mut bpf = load_and_attach_uprobe( let mut bpf = load_and_attach_uprobe(
"test_bpf_probe_read_kernel_str_bytes", "test_bpf_probe_read_kernel_str_bytes",
"trigger_bpf_probe_read_kernel", "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); set_kernel_buffer_element(&mut bpf, bytes);
trigger_bpf_probe_read_kernel(dest_len); trigger_bpf_probe_read_kernel(dest_len);

@ -4,12 +4,10 @@ use tempfile::TempDir;
use aya::{maps::Array, programs::TracePoint, BpfLoader, Btf, Endianness}; 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 // 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. // special meaning, they just have "nice" bit patterns that can be helpful while debugging.
#[integration_test] #[test]
fn relocate_field() { fn relocate_field() {
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
@ -40,7 +38,7 @@ fn relocate_field() {
assert_eq!(test.run_no_btf().unwrap(), 3); assert_eq!(test.run_no_btf().unwrap(), 3);
} }
#[integration_test] #[test]
fn relocate_enum() { fn relocate_enum() {
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
@ -60,7 +58,7 @@ fn relocate_enum() {
assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAA); assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAA);
} }
#[integration_test] #[test]
fn relocate_enum_signed() { fn relocate_enum_signed() {
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
@ -80,7 +78,7 @@ fn relocate_enum_signed() {
assert_eq!(test.run_no_btf().unwrap() as i64, -0x7AAAAAAAi64); assert_eq!(test.run_no_btf().unwrap() as i64, -0x7AAAAAAAi64);
} }
#[integration_test] #[test]
fn relocate_enum64() { fn relocate_enum64() {
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
@ -100,7 +98,7 @@ fn relocate_enum64() {
assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAABBBBBBBB); assert_eq!(test.run_no_btf().unwrap(), 0xAAAAAAAABBBBBBBB);
} }
#[integration_test] #[test]
fn relocate_enum64_signed() { fn relocate_enum64_signed() {
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
@ -120,7 +118,7 @@ fn relocate_enum64_signed() {
assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64); assert_eq!(test.run_no_btf().unwrap() as i64, -0xAAAAAAABBBBBBBBi64);
} }
#[integration_test] #[test]
fn relocate_pointer() { fn relocate_pointer() {
let test = RelocationTest { let test = RelocationTest {
local_definition: r#" local_definition: r#"
@ -143,7 +141,7 @@ fn relocate_pointer() {
assert_eq!(test.run_no_btf().unwrap(), 42); assert_eq!(test.run_no_btf().unwrap(), 42);
} }
#[integration_test] #[test]
fn relocate_struct_flavors() { fn relocate_struct_flavors() {
let definition = r#" let definition = r#"
struct foo {}; struct foo {};

@ -3,24 +3,7 @@ use libc::{uname, utsname};
use regex::Regex; use regex::Regex;
use std::{cell::OnceCell, ffi::CStr, mem}; use std::{cell::OnceCell, ffi::CStr, mem};
pub mod bpf_probe_read; pub fn kernel_version() -> anyhow::Result<(u8, u8, u8)> {
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)> {
static mut RE: OnceCell<Regex> = OnceCell::new(); static mut RE: OnceCell<Regex> = OnceCell::new();
let re = let re =
unsafe { &mut RE }.get_or_init(|| Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)").unwrap()); 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"); bail!("no kernel version found");
} }
} }
inventory::collect!(IntegrationTest);

@ -1,11 +1,9 @@
use super::integration_test;
use aya::include_bytes_aligned; use aya::include_bytes_aligned;
use object::{Object, ObjectSymbol}; use object::{Object, ObjectSymbol};
#[integration_test] #[test]
fn test_maps() { 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(); let obj_file = object::File::parse(bytes).unwrap();
if obj_file.section_by_name("maps").is_none() { if obj_file.section_by_name("maps").is_none() {
panic!("No 'maps' ELF section"); panic!("No 'maps' ELF section");

@ -9,18 +9,16 @@ use aya::{
}, },
Bpf, Bpf,
}; };
use log::warn;
use crate::tests::kernel_version; mod common;
use common::kernel_version;
use super::integration_test;
const MAX_RETRIES: u32 = 100; const MAX_RETRIES: u32 = 100;
const RETRY_DURATION_MS: u64 = 10; const RETRY_DURATION_MS: u64 = 10;
#[integration_test] #[test]
fn long_name() { 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 mut bpf = Bpf::load(bytes).unwrap();
let name_prog: &mut Xdp = bpf let name_prog: &mut Xdp = bpf
.program_mut("ihaveaverylongname") .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. // Therefore, as long as we were able to load the program, this is good enough.
} }
#[integration_test] #[test]
fn multiple_btf_maps() { fn multiple_btf_maps() {
let bytes = 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 mut bpf = Bpf::load(bytes).unwrap();
let map_1: Array<_, u64> = bpf.take_map("map_1").unwrap().try_into().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() { 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 mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf let prog: &mut Xdp = bpf
.program_mut("test_unload_xdp") .program_mut("test_unload_xdp")
@ -103,9 +101,9 @@ fn unload_xdp() {
assert_loaded!("test_unload_xdp", false); assert_loaded!("test_unload_xdp", false);
} }
#[integration_test] #[test]
fn unload_kprobe() { 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 mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut KProbe = bpf let prog: &mut KProbe = bpf
.program_mut("test_unload_kpr") .program_mut("test_unload_kpr")
@ -133,14 +131,14 @@ fn unload_kprobe() {
assert_loaded!("test_unload_kpr", false); assert_loaded!("test_unload_kpr", false);
} }
#[integration_test] #[test]
fn pin_link() { fn pin_link() {
if kernel_version().unwrap() < (5, 9, 0) { if kernel_version().unwrap() < (5, 9, 0) {
warn!("skipping test, XDP uses netlink"); eprintln!("skipping test, XDP uses netlink");
return; 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 mut bpf = Bpf::load(bytes).unwrap();
let prog: &mut Xdp = bpf let prog: &mut Xdp = bpf
.program_mut("test_unload_xdp") .program_mut("test_unload_xdp")
@ -168,14 +166,14 @@ fn pin_link() {
assert_loaded!("test_unload_xdp", false); assert_loaded!("test_unload_xdp", false);
} }
#[integration_test] #[test]
fn pin_lifecycle() { fn pin_lifecycle() {
if kernel_version().unwrap() < (5, 9, 0) { if kernel_version().unwrap() < (5, 9, 0) {
warn!("skipping test, XDP uses netlink"); eprintln!("skipping test, XDP uses netlink");
return; 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 // 1. Load Program and Pin
{ {

@ -5,8 +5,6 @@ use aya_log::BpfLogger;
use log::{Level, Log, Record}; use log::{Level, Log, Record};
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
use super::tokio_integration_test;
const MAX_ATTEMPTS: usize = 10; const MAX_ATTEMPTS: usize = 10;
const TIMEOUT_MS: u64 = 10; const TIMEOUT_MS: u64 = 10;
@ -89,9 +87,9 @@ impl Log for TestingLogger {
} }
} }
#[tokio_integration_test] #[tokio::test]
async fn log() { 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 mut bpf = Bpf::load(bytes).unwrap();
let (logger, captured_logs) = TestingLogger::with_capacity(5); let (logger, captured_logs) = TestingLogger::with_capacity(5);

@ -4,11 +4,9 @@ use std::collections::HashMap;
use aya::include_bytes_aligned; use aya::include_bytes_aligned;
use aya_obj::{generated::bpf_insn, Object, ProgramSection}; use aya_obj::{generated::bpf_insn, Object, ProgramSection};
use super::integration_test; #[test]
#[integration_test]
fn run_with_rbpf() { 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(); let object = Object::parse(bytes).unwrap();
assert_eq!(object.programs.len(), 1); assert_eq!(object.programs.len(), 1);
@ -37,10 +35,10 @@ fn run_with_rbpf() {
static mut MULTIMAP_MAPS: [*mut Vec<u64>; 2] = [null_mut(), null_mut()]; static mut MULTIMAP_MAPS: [*mut Vec<u64>; 2] = [null_mut(), null_mut()];
#[integration_test] #[test]
fn use_map_with_rbpf() { fn use_map_with_rbpf() {
let bytes = 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(); let mut object = Object::parse(bytes).unwrap();
assert_eq!(object.programs.len(), 1); assert_eq!(object.programs.len(), 1);

@ -5,13 +5,12 @@ use aya::{
programs::{ProgramError, UProbe}, programs::{ProgramError, UProbe},
Bpf, Bpf,
}; };
use integration_test_macros::integration_test;
#[integration_test] #[test]
fn relocations() { fn relocations() {
let bpf = load_and_attach( let bpf = load_and_attach(
"test_64_32_call_relocs", "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(); trigger_relocations_program();
@ -23,11 +22,11 @@ fn relocations() {
assert_eq!(m.get(&2, 0).unwrap(), 3); assert_eq!(m.get(&2, 0).unwrap(), 3);
} }
#[integration_test] #[test]
fn text_64_64_reloc() { fn text_64_64_reloc() {
let mut bpf = load_and_attach( let mut bpf = load_and_attach(
"test_text_64_64_reloc", "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(); let mut m = aya::maps::Array::<_, u64>::try_from(bpf.map_mut("RESULTS").unwrap()).unwrap();

@ -3,24 +3,24 @@ use aya::{
programs::{Extension, Xdp, XdpFlags}, programs::{Extension, Xdp, XdpFlags},
Bpf, BpfLoader, Bpf, BpfLoader,
}; };
use log::info;
use super::{integration_test, kernel_version}; mod common;
use common::kernel_version;
#[integration_test] #[test]
fn xdp() { 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 mut bpf = Bpf::load(bytes).unwrap();
let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); let dispatcher: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
dispatcher.load().unwrap(); dispatcher.load().unwrap();
dispatcher.attach("lo", XdpFlags::default()).unwrap(); dispatcher.attach("lo", XdpFlags::default()).unwrap();
} }
#[integration_test] #[test]
fn extension() { fn extension() {
let (major, minor, _) = kernel_version().unwrap(); let (major, minor, _) = kernel_version().unwrap();
if major < 5 || (minor == 5 && minor < 9) { if major < 5 || (minor == 5 && minor < 9) {
info!( eprintln!(
"skipping as {}.{} does not meet version requirement of 5.9", "skipping as {}.{} does not meet version requirement of 5.9",
major, minor major, minor
); );
@ -28,14 +28,13 @@ fn extension() {
} }
// TODO: Check kernel version == 5.9 or later // TODO: Check kernel version == 5.9 or later
let main_bytes = 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 mut bpf = Bpf::load(main_bytes).unwrap();
let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap(); let pass: &mut Xdp = bpf.program_mut("pass").unwrap().try_into().unwrap();
pass.load().unwrap(); pass.load().unwrap();
pass.attach("lo", XdpFlags::default()).unwrap(); pass.attach("lo", XdpFlags::default()).unwrap();
let ext_bytes = let ext_bytes = include_bytes_aligned!("../../../target/bpfel-unknown-none/release/ext.bpf.o");
include_bytes_aligned!("../../../../target/bpfel-unknown-none/release/ext.bpf.o");
let mut bpf = BpfLoader::new().extension("drop").load(ext_bytes).unwrap(); let mut bpf = BpfLoader::new().extension("drop").load(ext_bytes).unwrap();
let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap(); let drop_: &mut Extension = bpf.program_mut("drop").unwrap().try_into().unwrap();
drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap(); drop_.load(pass.fd().unwrap(), "xdp_pass").unwrap();

@ -5,11 +5,12 @@ authors = ["Alessandro Decina <alessandro.d@gmail.com>"]
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1"
aya-tool = { path = "../aya-tool" } aya-tool = { path = "../aya-tool" }
cargo_metadata = "0.15.4"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
anyhow = "1"
syn = "2"
quote = "1"
proc-macro2 = "1"
indoc = "2.0" indoc = "2.0"
proc-macro2 = "1"
quote = "1"
serde_json = "1" serde_json = "1"
syn = "2"

@ -4,7 +4,7 @@ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::{Command, Output},
}; };
use anyhow::{bail, Context}; use anyhow::{bail, Context};
@ -148,16 +148,20 @@ fn compile_with_clang<P: Clone + AsRef<Path>>(
.arg("-o") .arg("-o")
.arg(out.as_ref().as_os_str()); .arg(out.as_ref().as_os_str());
let output = cmd.output().context("Failed to execute clang")?; let Output {
if !output.status.success() { status,
stdout,
stderr,
} = cmd.output().context("Failed to execute clang")?;
if !status.success() {
bail!( bail!(
"Failed to compile eBPF programs\n \ "Failed to compile eBPF programs\n \
stdout=\n \ stdout=\n \
{}\n \ {}\n \
stderr=\n \ stderr=\n \
{}\n", {}\n",
String::from_utf8(output.stdout).unwrap(), String::from_utf8(stdout).unwrap(),
String::from_utf8(output.stderr).unwrap() String::from_utf8(stderr).unwrap()
); );
} }

@ -24,15 +24,14 @@ enum Command {
} }
fn main() { fn main() {
let opts = XtaskOptions::parse(); let XtaskOptions { command } = Parser::parse();
use Command::*; let ret = match command {
let ret = match opts.command { Command::Codegen(opts) => codegen::codegen(opts),
Codegen(opts) => codegen::codegen(opts), Command::Docs => docs::docs(),
Docs => docs::docs(), Command::BuildIntegrationTest(opts) => build_test::build_test(opts),
BuildIntegrationTest(opts) => build_test::build_test(opts), Command::BuildIntegrationTestEbpf(opts) => build_ebpf::build_ebpf(opts),
BuildIntegrationTestEbpf(opts) => build_ebpf::build_ebpf(opts), Command::IntegrationTest(opts) => run::run(opts),
IntegrationTest(opts) => run::run(opts),
}; };
if let Err(e) = ret { if let Err(e) = ret {

@ -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 anyhow::Context as _;
use cargo_metadata::{Artifact, CompilerMessage, Message, Target};
use clap::Parser; use clap::Parser;
use crate::build_ebpf::{build_ebpf, Architecture, BuildEbpfOptions as BuildOptions}; use crate::build_ebpf::{build_ebpf, Architecture, BuildEbpfOptions as BuildOptions};
@ -18,59 +24,123 @@ pub struct Options {
pub runner: String, pub runner: String,
/// libbpf directory /// libbpf directory
#[clap(long, action)] #[clap(long, action)]
pub libbpf_dir: String, pub libbpf_dir: PathBuf,
/// Arguments to pass to your application /// Arguments to pass to your application
#[clap(name = "args", last = true)] #[clap(name = "args", last = true)]
pub run_args: Vec<String>, pub run_args: Vec<String>,
} }
/// Build the project /// Build the project
fn build(opts: &Options) -> Result<(), anyhow::Error> { fn build(release: bool) -> Result<Vec<(PathBuf, PathBuf)>, anyhow::Error> {
let mut args = vec!["build"]; let mut cmd = Command::new("cargo");
if opts.release { cmd.arg("build")
args.push("--release") .arg("--tests")
.arg("--message-format=json")
.arg("--package=integration-test");
if release {
cmd.arg("--release");
} }
args.push("-p"); let mut cmd = cmd
args.push("integration-test"); .stdout(Stdio::piped())
let status = Command::new("cargo") .spawn()
.args(&args) .with_context(|| format!("failed to spawn {cmd:?}"))?;
.status()
.expect("failed to build userspace"); 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() { match status.code() {
Some(code) => match code { Some(code) => match code {
0 => Ok(()), 0 => Ok(executables),
code => Err(anyhow::anyhow!("exited with status code: {code}")), 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 /// Build and run the project
pub fn run(opts: Options) -> Result<(), anyhow::Error> { 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 our ebpf program followed by our application
build_ebpf(BuildOptions { build_ebpf(BuildOptions {
target: opts.bpf_target, target: bpf_target,
libbpf_dir: PathBuf::from(&opts.libbpf_dir), libbpf_dir,
}) })
.context("Error while building eBPF program")?; .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");
// arguments to pass to the application let binaries = build(release).context("error while building userspace application")?;
let mut run_args: Vec<_> = opts.run_args.iter().map(String::as_str).collect(); let mut args = runner.trim().split_terminator(' ');
let runner = args.next().ok_or(anyhow::anyhow!("no first argument"))?;
let args = args.collect::<Vec<_>>();
// configure args let mut failures = String::new();
let mut args: Vec<_> = opts.runner.trim().split_terminator(' ').collect(); for (src_path, binary) in binaries {
args.push(bin_path.as_str()); let mut cmd = Command::new(runner);
args.append(&mut run_args); let cmd = cmd
.args(args.iter())
.arg(binary)
.args(run_args.iter())
.arg("--test-threads=1");
// spawn the command println!("{} running {cmd:?}", src_path.display());
let err = Command::new(args.first().expect("No first argument"))
.args(args.iter().skip(1))
.exec();
// we shouldn't get here unless the command failed to spawn let status = cmd
Err(anyhow::Error::from(err).context(format!("Failed to run `{}`", args.join(" ")))) .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))
}
} }

Loading…
Cancel
Save