Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Steps toward hermetic integration tests #644

Merged
merged 9 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
7 changes: 1 addition & 6 deletions .github/workflows/gen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ jobs:
steps:
- uses: actions/checkout@v3

- uses: actions/checkout@v3
with:
repository: libbpf/libbpf
path: libbpf

- name: libbpf-version
working-directory: libbpf
run: echo "LIBBPF_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV
Expand All @@ -32,7 +27,7 @@ jobs:

- name: Run codegen
run: |
cargo xtask codegen --libbpf-dir ./libbpf
cargo xtask codegen

- name: Check for changes
run: |
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ jobs:
runs-on: macos-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
with:
repository: libbpf/libbpf
path: libbpf
submodules: recursive

- name: Install Pre-requisites
run: |
Expand All @@ -40,4 +38,4 @@ jobs:
key: tmp-files-${{ hashFiles('test/run.sh') }}

- name: Run integration tests
run: test/run.sh ./libbpf
run: test/run.sh
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: cargo fmt --all -- --check

- name: Run clippy
run: cargo clippy --all-targets --workspace --exclude integration-test -- --deny warnings
run: cargo clippy --all-targets --workspace -- --deny warnings

- name: Run miri
run: cargo miri test --all-targets
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
Cargo.lock
target/
libbpf/
.vscode/
!.vscode/settings.json
site/
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "libbpf"]
path = libbpf
url = https://github.com/libbpf/libbpf
4 changes: 2 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.command": "clippy"
"rust-analyzer.check.allTargets": true,
"rust-analyzer.check.command": "clippy"
}
2 changes: 1 addition & 1 deletion aya/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ macro_rules! include_bytes_aligned {
pub bytes: Bytes,
}

static ALIGNED: &Aligned<[u8]> = &Aligned {
const ALIGNED: &Aligned<[u8]> = &Aligned {
_align: [],
bytes: *include_bytes!($path),
};
Expand Down
6 changes: 0 additions & 6 deletions bpf/.cargo/config.toml

This file was deleted.

1 change: 1 addition & 0 deletions libbpf
Submodule libbpf added at a22580
13 changes: 7 additions & 6 deletions test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,25 @@ From the root of this repository:
### Native

```
cargo xtask integration-test --libbpf-dir /path/to/libbpf
cargo xtask integration-test
```

### Virtualized

```
./test/run.sh /path/to/libbpf
./test/run.sh
```

### 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-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!`.
`integration-ebpf/Cargo.toml` and `integration-test/src/lib.rs` using
`include_bytes_aligned!`.
- C eBPF code should live in `integration-test/bpf/${NAME}.bpf.c`. It should be
added to the list of files in `integration-test/build.rs` and the list of
constants in `integration-test/src/lib.rs` 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
Expand Down
6 changes: 0 additions & 6 deletions test/integration-ebpf/.cargo/config.toml

This file was deleted.

2 changes: 1 addition & 1 deletion test/integration-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ path = "src/relocations.rs"

[[bin]]
name = "bpf_probe_read"
path = "src/bpf_probe_read.rs"
path = "src/bpf_probe_read.rs"
2 changes: 0 additions & 2 deletions test/integration-ebpf/rust-toolchain.toml

This file was deleted.

1 change: 0 additions & 1 deletion test/integration-ebpf/rustfmt.toml

This file was deleted.

4 changes: 3 additions & 1 deletion test/integration-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ object = { version = "0.31", default-features = false, features = [
"elf",
] }
rbpf = "0.2.0"
tempfile = "3.3.0"
tokio = { version = "1.24", default-features = false, features = ["time"] }

[build-dependencies]
cargo_metadata = "0.15.4"
196 changes: 196 additions & 0 deletions test/integration-test/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use std::{
env,
ffi::OsString,
fmt::Write as _,
fs,
io::BufReader,
path::PathBuf,
process::{Child, Command, Stdio},
};

use cargo_metadata::{
Artifact, CompilerMessage, Message, Metadata, MetadataCommand, Package, Target,
};

fn main() {
const AYA_BUILD_INTEGRATION_BPF: &str = "AYA_BUILD_INTEGRATION_BPF";

println!("cargo:rerun-if-env-changed={}", AYA_BUILD_INTEGRATION_BPF);

let build_integration_bpf = match env::var_os(AYA_BUILD_INTEGRATION_BPF) {
None => false,
Some(s) => {
let s = s.to_str().unwrap();
s.parse::<bool>().unwrap()
}
};

let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap();
let manifest_dir = PathBuf::from(manifest_dir);
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir = PathBuf::from(out_dir);

let endian = env::var_os("CARGO_CFG_TARGET_ENDIAN").unwrap();
let target = if endian == "big" {
"bpfeb"
} else if endian == "little" {
"bpfel"
} else {
panic!("unsupported endian={:?}", endian)
};

const C_BPF_PROBES: &[(&str, &str)] = &[
("ext.bpf.c", "ext.bpf.o"),
("main.bpf.c", "main.bpf.o"),
("multimap-btf.bpf.c", "multimap-btf.bpf.o"),
("text_64_64_reloc.c", "text_64_64_reloc.o"),
];

let c_bpf_probes = C_BPF_PROBES
.iter()
.map(|(src, dst)| (src, out_dir.join(dst)));

if build_integration_bpf {
let libbpf_dir = manifest_dir
.parent()
.unwrap()
.parent()
.unwrap()
.join("libbpf");

let libbpf_headers_dir = out_dir.join("libbpf_headers");

let mut includedir = OsString::new();
includedir.push("INCLUDEDIR=");
includedir.push(&libbpf_headers_dir);

let mut cmd = Command::new("make");
cmd.arg("-C")
.arg(libbpf_dir.join("src"))
.arg(includedir)
.arg("install_headers");
let status = cmd
.status()
.unwrap_or_else(|err| panic!("failed to run {cmd:?}: {err}"));
match status.code() {
Some(code) => match code {
0 => {}
code => panic!("{cmd:?} exited with code {code}"),
},
None => panic!("{cmd:?} terminated by signal"),
}

let bpf_dir = manifest_dir.join("bpf");

let mut target_arch = OsString::new();
target_arch.push("-D__TARGET_ARCH_");

let arch = env::var_os("CARGO_CFG_TARGET_ARCH").unwrap();
if arch == "x86_64" {
target_arch.push("x86");
} else if arch == "aarch64" {
target_arch.push("arm64");
} else {
target_arch.push(arch);
};

for (src, dst) in c_bpf_probes {
let src = bpf_dir.join(src);
let mut cmd = Command::new("clang");
cmd.arg("-I")
.arg(&libbpf_headers_dir)
.args(["-g", "-O2", "-target", target, "-c"])
.arg(&target_arch)
.arg(src)
.arg("-o")
.arg(dst);
let status = cmd
.status()
.unwrap_or_else(|err| panic!("failed to run {cmd:?}: {err}"));
match status.code() {
Some(code) => match code {
0 => {}
code => panic!("{cmd:?} exited with code {code}"),
},
None => panic!("{cmd:?} terminated by signal"),
}
}

let ebpf_dir = manifest_dir.parent().unwrap().join("integration-ebpf");
let target = format!("{target}-unknown-none");

let mut cmd = Command::new("cargo");
cmd.current_dir(&ebpf_dir).args([
"build",
"-Z",
"build-std=core",
"--release",
"--message-format=json",
"--target",
&target,
]);
let mut child = cmd
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|err| panic!("failed to spawn {cmd:?}: {err}"));
let Child { stdout, .. } = &mut child;
let stdout = stdout.take().unwrap();
let reader = BufReader::new(stdout);
let mut executables = Vec::new();
let mut compiler_messages = String::new();
for message in Message::parse_stream(reader) {
#[allow(clippy::collapsible_match)]
match message.expect("valid JSON") {
Message::CompilerArtifact(Artifact {
executable,
target: Target { name, .. },
..
}) => {
if let Some(executable) = executable {
executables.push((name, executable.into_std_path_buf()));
}
}
Message::CompilerMessage(CompilerMessage { message, .. }) => {
writeln!(&mut compiler_messages, "{message}").unwrap()
}
_ => {}
}
}

let status = child
.wait()
.unwrap_or_else(|err| panic!("failed to wait for {cmd:?}: {err}"));

match status.code() {
Some(code) => match code {
0 => {}
code => panic!("{cmd:?} exited with status code {code}:\n{compiler_messages}"),
},
None => panic!("{cmd:?} terminated by signal"),
}

for (name, binary) in executables {
let dst = out_dir.join(name);
let _: u64 = fs::copy(&binary, &dst)
.unwrap_or_else(|err| panic!("failed to copy {binary:?} to {dst:?}: {err}"));
}
} else {
for (_src, dst) in c_bpf_probes {
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
}

let Metadata { packages, .. } = MetadataCommand::new().no_deps().exec().unwrap();
for Package { name, targets, .. } in packages {
if name != "integration-ebpf" {
continue;
}
for Target { name, kind, .. } in targets {
if kind != ["bin"] {
continue;
}
let dst = out_dir.join(name);
fs::write(&dst, []).unwrap_or_else(|err| panic!("failed to create {dst:?}: {err}"));
}
}
}
}
19 changes: 19 additions & 0 deletions test/integration-test/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
use aya::include_bytes_aligned;

pub const EXT: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/ext.bpf.o"));
pub const MAIN: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/main.bpf.o"));
pub const MULTIMAP_BTF: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/multimap-btf.bpf.o"));
pub const TEXT_64_64_RELOC: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/text_64_64_reloc.o"));

pub const LOG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/log"));
pub const MAP_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/map_test"));
pub const NAME_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/name_test"));
pub const PASS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/pass"));
pub const TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/test"));
pub const RELOCATIONS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/relocations"));
pub const BPF_PROBE_READ: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/bpf_probe_read"));

#[cfg(test)]
mod tests;
8 changes: 8 additions & 0 deletions test/integration-test/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mod bpf_probe_read;
mod btf_relocations;
mod elf;
mod load;
mod log;
mod rbpf;
mod relocations;
mod smoke;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use aya::{include_bytes_aligned, maps::Array, programs::UProbe, Bpf};
use aya::{maps::Array, programs::UProbe, Bpf};

const RESULT_BUF_LEN: usize = 1024;

Expand Down Expand Up @@ -68,7 +68,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"),
crate::BPF_PROBE_READ,
);
trigger_bpf_probe_read_user(bytes.as_ptr(), dest_len);
bpf
Expand All @@ -78,7 +78,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"),
crate::BPF_PROBE_READ,
);
set_kernel_buffer_element(&mut bpf, bytes);
trigger_bpf_probe_read_kernel(dest_len);
Expand Down
Loading