Skip to content

Commit 48bc424

Browse files
committed
libbpf-cargo: make: skel: Add tests for gen and make
This commit adds a couple tests for skeleton generation and the `cargo libbpf make` subcommand. We test the skeleton by: * generating a skeleton for basic progs * generate a main.rs that uses the exposed APIs * cargo-build the project Note these tests don't require root b/c we're only compiling, not running. This should be sufficient to test skeleton correctness.
1 parent 919a3e4 commit 48bc424

File tree

5 files changed

+130617
-2
lines changed

5 files changed

+130617
-2
lines changed

.gitattributes

+3-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
example/src/bpf/vmlinux*.h linguist-generated
1+
vmlinux*.h linguist-generated
2+
bpf_helper_defs.h linguist-generated
3+
bpf_helpers.h linguist-generated

libbpf-cargo/src/test.rs

+316-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ use std::process::Command;
66
use goblin::Object;
77
use tempfile::{tempdir, TempDir};
88

9-
use crate::build::build;
9+
use crate::{build::build, make::make};
10+
11+
static VMLINUX: &'static str = include_str!("../test_data/vmlinux.h");
12+
static BPF_HELPERS: &'static str = include_str!("../test_data/bpf_helpers.h");
13+
static BPF_HELPER_DEFS: &'static str = include_str!("../test_data/bpf_helper_defs.h");
1014

1115
/// Creates a temporary directory and initializes a default cargo project inside.
1216
///
@@ -89,6 +93,22 @@ fn validate_bpf_o(path: &Path) {
8993
}
9094
}
9195

96+
/// Returns the path to the local libbpf-rs
97+
///
98+
/// Warning: hacky! But necessary to run tests. We assume that the current working directory is
99+
/// libbpf-cargo project root. Hopefully this is a cargo-provided invariant. I tried using the
100+
/// file!() macro but it returns a relative path and seems even hackier to make work.
101+
fn get_libbpf_rs_path() -> PathBuf {
102+
let cwd = std::env::current_dir().expect("failed to get cwd");
103+
104+
Path::new(&cwd)
105+
.parent()
106+
.expect("failed to get parent of cwd")
107+
.join("libbpf-rs")
108+
.canonicalize()
109+
.expect("failed to canonicalize libbpf-rs")
110+
}
111+
92112
#[test]
93113
fn test_build_default() {
94114
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
@@ -263,3 +283,298 @@ fn test_build_workspace_collision() {
263283
0
264284
);
265285
}
286+
287+
#[test]
288+
fn test_make_basic() {
289+
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
290+
291+
// Add prog dir
292+
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
293+
294+
// Add a prog
295+
let _prog_file =
296+
File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");
297+
298+
assert_eq!(
299+
make(
300+
true,
301+
Some(&cargo_toml),
302+
Path::new("/bin/clang"),
303+
true,
304+
true,
305+
Vec::new()
306+
),
307+
0
308+
);
309+
310+
// Validate generated object file
311+
validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path());
312+
313+
// Check that skeleton exists (other tests will check for skeleton validity)
314+
assert!(proj_dir
315+
.as_path()
316+
.join("src/bpf/prog.skel.rs")
317+
.as_path()
318+
.exists());
319+
}
320+
321+
#[test]
322+
fn test_make_workspace() {
323+
let (_dir, workspace_dir, workspace_cargo_toml, proj_one_dir, proj_two_dir) =
324+
setup_temp_workspace();
325+
326+
// Create bpf prog for project one
327+
create_dir(proj_one_dir.join("src/bpf")).expect("failed to create prog dir");
328+
let _prog_file_1 = File::create(proj_one_dir.join("src/bpf/prog1.bpf.c"))
329+
.expect("failed to create prog file 1");
330+
331+
// Create bpf prog for project two, same name as project one
332+
create_dir(proj_two_dir.join("src/bpf")).expect("failed to create prog dir");
333+
let _prog_file_2 = File::create(proj_two_dir.join("src/bpf/prog2.bpf.c"))
334+
.expect("failed to create prog file 2");
335+
336+
assert_eq!(
337+
make(
338+
true,
339+
Some(&workspace_cargo_toml),
340+
Path::new("/bin/clang"),
341+
true,
342+
true,
343+
Vec::new()
344+
),
345+
0
346+
);
347+
348+
// Validate generated object files
349+
validate_bpf_o(
350+
workspace_dir
351+
.as_path()
352+
.join("target/bpf/prog1.bpf.o")
353+
.as_path(),
354+
);
355+
validate_bpf_o(
356+
workspace_dir
357+
.as_path()
358+
.join("target/bpf/prog2.bpf.o")
359+
.as_path(),
360+
);
361+
362+
// Check that skeleton exists (other tests will check for skeleton validity)
363+
assert!(proj_one_dir
364+
.as_path()
365+
.join("src/bpf/prog1.skel.rs")
366+
.as_path()
367+
.exists());
368+
assert!(proj_two_dir
369+
.as_path()
370+
.join("src/bpf/prog2.skel.rs")
371+
.as_path()
372+
.exists());
373+
}
374+
375+
#[test]
376+
fn test_skeleton_empty_source() {
377+
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
378+
379+
// Add prog dir
380+
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
381+
382+
// Add a prog
383+
let _prog_file =
384+
File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");
385+
386+
assert_eq!(
387+
make(
388+
true,
389+
Some(&cargo_toml),
390+
Path::new("/bin/clang"),
391+
true,
392+
true,
393+
Vec::new()
394+
),
395+
0
396+
);
397+
398+
let mut cargo = OpenOptions::new()
399+
.append(true)
400+
.open(&cargo_toml)
401+
.expect("failed to open Cargo.toml");
402+
403+
// Make test project use our development libbpf-rs version
404+
writeln!(
405+
cargo,
406+
r#"
407+
libbpf-rs = {{ path = "{}" }}
408+
"#,
409+
get_libbpf_rs_path().as_path().display()
410+
)
411+
.expect("failed to write to Cargo.toml");
412+
413+
let mut source = OpenOptions::new()
414+
.write(true)
415+
.truncate(true)
416+
.open(proj_dir.join("src/main.rs"))
417+
.expect("failed to open main.rs");
418+
419+
write!(
420+
source,
421+
r#"
422+
mod bpf;
423+
use bpf::*;
424+
425+
fn main() {{
426+
let mut builder = ProgSkelBuilder::default();
427+
let _skel = builder
428+
.open()
429+
.expect("failed to open skel")
430+
.load()
431+
.expect("failed to load skel");
432+
}}
433+
"#,
434+
)
435+
.expect("failed to write to main.rs");
436+
437+
let status = Command::new("cargo")
438+
.arg("build")
439+
.arg("--quiet")
440+
.arg("--manifest-path")
441+
.arg(cargo_toml.into_os_string())
442+
.status()
443+
.expect("failed to spawn cargo-build");
444+
assert!(status.success());
445+
}
446+
447+
#[test]
448+
fn test_skeleton_basic() {
449+
let (_dir, proj_dir, cargo_toml) = setup_temp_project();
450+
451+
// Add prog dir
452+
create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
453+
454+
// Add a prog
455+
let mut prog = OpenOptions::new()
456+
.write(true)
457+
.create(true)
458+
.open(proj_dir.join("src/bpf/prog.bpf.c"))
459+
.expect("failed to open prog.bpf.c");
460+
461+
write!(
462+
prog,
463+
r#"
464+
#include "vmlinux.h"
465+
#include "bpf_helpers.h"
466+
467+
struct {{
468+
__uint(type, BPF_MAP_TYPE_HASH);
469+
__uint(max_entries, 1024);
470+
__type(key, u32);
471+
__type(value, u64);
472+
}} mymap SEC(".maps");
473+
474+
SEC("kprobe/foo")
475+
int this_is_my_prog(u64 *ctx)
476+
{{
477+
return 0;
478+
}}
479+
"#,
480+
)
481+
.expect("failed to write prog.bpf.c");
482+
483+
// Lay down the necessary header files
484+
let mut vmlinux = OpenOptions::new()
485+
.create(true)
486+
.write(true)
487+
.open(proj_dir.join("src/bpf/vmlinux.h"))
488+
.expect("failed to open vmlinux.h");
489+
write!(vmlinux, "{}", VMLINUX).expect("failed to write vmlinux.h");
490+
491+
let mut bpf_helpers = OpenOptions::new()
492+
.create(true)
493+
.write(true)
494+
.open(proj_dir.join("src/bpf/bpf_helpers.h"))
495+
.expect("failed to open bpf_helpers.h");
496+
write!(bpf_helpers, "{}", BPF_HELPERS).expect("failed to write bpf_helpers.h");
497+
498+
let mut bpf_helper_defs = OpenOptions::new()
499+
.create(true)
500+
.write(true)
501+
.open(proj_dir.join("src/bpf/bpf_helper_defs.h"))
502+
.expect("failed to open bpf_helper_defs.h");
503+
write!(bpf_helper_defs, "{}", BPF_HELPER_DEFS).expect("failed to write bpf_helper_defs.h");
504+
505+
assert_eq!(
506+
make(
507+
true,
508+
Some(&cargo_toml),
509+
Path::new("/bin/clang"),
510+
true,
511+
true,
512+
Vec::new()
513+
),
514+
0
515+
);
516+
517+
let mut cargo = OpenOptions::new()
518+
.append(true)
519+
.open(&cargo_toml)
520+
.expect("failed to open Cargo.toml");
521+
522+
// Make test project use our development libbpf-rs version
523+
writeln!(
524+
cargo,
525+
r#"
526+
libbpf-rs = {{ path = "{}" }}
527+
"#,
528+
get_libbpf_rs_path().as_path().display()
529+
)
530+
.expect("failed to write to Cargo.toml");
531+
532+
let mut source = OpenOptions::new()
533+
.write(true)
534+
.truncate(true)
535+
.open(proj_dir.join("src/main.rs"))
536+
.expect("failed to open main.rs");
537+
538+
write!(
539+
source,
540+
r#"
541+
mod bpf;
542+
use bpf::*;
543+
544+
fn main() {{
545+
let mut builder = ProgSkelBuilder::default();
546+
let mut open_skel = builder
547+
.open()
548+
.expect("failed to open skel");
549+
550+
// Check that we can grab handles to open maps/progs
551+
let _open_map = open_skel.maps().mymap();
552+
let _open_prog = open_skel.progs().this_is_my_prog();
553+
554+
let mut skel = open_skel
555+
.load()
556+
.expect("failed to load skel");
557+
558+
// Check that we can grab handles to loaded maps/progs
559+
let _map = skel.maps().mymap();
560+
let _prog = skel.progs().this_is_my_prog();
561+
562+
// Check that attach() is generated
563+
skel.attach().expect("failed to attach progs");
564+
565+
// Check that Option<Link> field is generated
566+
let _mylink = skel.links.this_is_my_prog.unwrap();
567+
}}
568+
"#,
569+
)
570+
.expect("failed to write to main.rs");
571+
572+
let status = Command::new("cargo")
573+
.arg("build")
574+
.arg("--quiet")
575+
.arg("--manifest-path")
576+
.arg(cargo_toml.into_os_string())
577+
.status()
578+
.expect("failed to spawn cargo-build");
579+
assert!(status.success());
580+
}

0 commit comments

Comments
 (0)