use std::collections::HashSet;
use std::fs::create_dir;
use std::fs::read;
use std::fs::read_to_string;
use std::fs::write;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;

use goblin::Object;
use indoc::indoc;
use libbpf_rs::btf::types;
use libbpf_rs::btf::BtfType;
use libbpf_rs::Btf;
use memmap2::Mmap;
use tempfile::tempdir;
use tempfile::NamedTempFile;
use tempfile::TempDir;
use test_log::test;

use crate::build::build_project;
use crate::build::BpfObjBuilder;
use crate::make::make;
use crate::r#gen::GenBtf;
use crate::r#gen::GenStructOps;
use crate::SkeletonBuilder;

/// Creates a temporary directory and initializes a default cargo project inside.
///
/// Returns temp directory object to hold directory open, the path to the cargo
/// project directory, and the path to the project Cargo.toml
fn setup_temp_project() -> (TempDir, PathBuf, PathBuf) {
    let dir = tempdir().expect("failed to create tempdir");
    let proj_dir = dir.path().join("proj");

    // Create default rust project
    let status = Command::new("cargo")
        .arg("new")
        .arg("--quiet")
        .arg("--bin")
        .arg(proj_dir.into_os_string())
        .status()
        .expect("failed to create new cargo project");
    assert!(status.success());

    let proj_dir = dir.path().join("proj");
    let mut cargo_toml = proj_dir.clone();
    cargo_toml.push("Cargo.toml");

    (dir, proj_dir, cargo_toml)
}

/// Creates a temporary directory and initializes a cargo workspace with two projects
/// inside. Similar to `setup_temp_project`, just that here there's 2 projects>
///
///
/// Returns temp directory object to hold directory open, the path to the cargo
/// workspace directory, path to first project, and path to second project.
fn setup_temp_workspace() -> (TempDir, PathBuf, PathBuf, PathBuf, PathBuf) {
    let dir = tempdir().expect("failed to create tempdir");
    let workspace_cargo_toml = dir.path().join("Cargo.toml");

    // Create first project
    let path_one = dir.path().join("one");
    let status_one = Command::new("cargo")
        .arg("new")
        .arg("--quiet")
        .arg("--bin")
        .arg(path_one.clone().into_os_string())
        .status()
        .expect("failed to create new cargo project 1");
    assert!(status_one.success());

    // Create second project
    let path_two = dir.path().join("two");
    let status_two = Command::new("cargo")
        .arg("new")
        .arg("--quiet")
        .arg("--bin")
        .arg(path_two.clone().into_os_string())
        .status()
        .expect("failed to create new cargo project 2");
    assert!(status_two.success());

    // Populate workspace Cargo.toml
    let mut cargo_toml_file = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(&workspace_cargo_toml)
        .expect("failed to open workspace Cargo.toml");
    writeln!(cargo_toml_file, r#"[workspace]"#).expect("write to workspace Cargo.toml failed");
    writeln!(cargo_toml_file, r#"members = ["one", "two"]"#)
        .expect("write to workspace Cargo.toml failed");

    let dir_pathbuf = dir.path().to_path_buf();
    (dir, dir_pathbuf, workspace_cargo_toml, path_one, path_two)
}

/// Validate if bpf object file at `path` is a valid bpf object file
fn validate_bpf_o(path: &Path) {
    let buffer = read(path)
        .unwrap_or_else(|_| panic!("failed to read object file at path={}", path.display()));
    match Object::parse(&buffer).expect("failed to parse object file") {
        Object::Elf(_) => (),
        _ => panic!("wrong object file format"),
    }
}

/// Returns the path to the local libbpf-rs
fn get_libbpf_rs_path() -> PathBuf {
    // The `CARGO_MANIFEST_DIR` environment variable points to the
    // libbpf-cargo directory, at build time.
    let libcargo_dir = env!("CARGO_MANIFEST_DIR");

    Path::new(&libcargo_dir)
        .parent()
        .expect("failed to get parent of libbpf-cargo directory")
        .join("libbpf-rs")
        .canonicalize()
        .expect("failed to canonicalize libbpf-rs")
}

fn write_vmlinux_header(dir: &Path) {
    let mut vmlinux = OpenOptions::new()
        .create(true)
        .truncate(true)
        .write(true)
        .open(dir.join("vmlinux.h"))
        .expect("failed to open vmlinux.h");
    let () = vmlinux
        .write_all(vmlinux::VMLINUX)
        .expect("failed to write vmlinux.h");
}

/// Add vmlinux header into `project`'s src/bpf dir
fn add_vmlinux_header(project: &Path) {
    write_vmlinux_header(&project.join("src/bpf"))
}

#[test]
fn test_build_default() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    // No bpf progs yet
    build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
    build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();

    // Add a prog
    let _prog_file =
        File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");

    build_project(Some(&cargo_toml), None, Vec::new()).unwrap();

    // Validate generated object file
    validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path());
}

#[test]
fn test_build_invalid_prog() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");

    // Add a prog
    let mut prog_file =
        File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");
    writeln!(prog_file, "1").expect("write to prog file failed");

    build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();
}

#[test]
fn test_build_custom() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    // Add custom build rules
    let mut cargo_toml_file = OpenOptions::new()
        .append(true)
        .open(&cargo_toml)
        .expect("failed to open Cargo.toml");
    writeln!(cargo_toml_file, "[package.metadata.libbpf]").expect("write to Cargo.toml failed");
    writeln!(cargo_toml_file, r#"prog_dir = "src/other_bpf_dir""#)
        .expect("write to Cargo.toml failed");
    writeln!(cargo_toml_file, r#"target_dir = "other_target_dir""#)
        .expect("write to Cargo.toml failed");

    // No bpf progs yet
    build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();

    // Add a prog
    create_dir(proj_dir.join("src/other_bpf_dir")).expect("failed to create prog dir");
    let _prog_file = File::create(proj_dir.join("src/other_bpf_dir/prog.bpf.c"))
        .expect("failed to create prog file");

    build_project(Some(&cargo_toml), None, Vec::new()).unwrap();

    // Validate generated object file
    validate_bpf_o(
        proj_dir
            .as_path()
            .join("target/other_target_dir/prog.bpf.o")
            .as_path(),
    );
}

#[test]
fn test_unknown_metadata_section() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    // Add a metadata section that isn't for libbpf.
    let mut cargo_toml_file = OpenOptions::new()
        .append(true)
        .open(&cargo_toml)
        .expect("failed to open Cargo.toml");
    let deb_metadata = indoc! {r#"
        [package.metadata.deb]
        prog_dir = "some value that should be ignored"
        some_other_val = true
    "#};
    cargo_toml_file
        .write_all(deb_metadata.as_bytes())
        .expect("write to Cargo.toml failed");

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
    build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();

    // Add a prog
    let _prog_file =
        File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");

    build_project(Some(&cargo_toml), None, Vec::new()).unwrap();

    // Validate generated object file
    validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path());
}

#[test]
fn test_enforce_file_extension() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
    build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();

    let _prog_file = File::create(proj_dir.join("src/bpf/prog_BAD_EXTENSION.c"))
        .expect("failed to create prog file");
    build_project(Some(&cargo_toml), None, Vec::new()).unwrap_err();

    let _prog_file_again = File::create(proj_dir.join("src/bpf/prog_GOOD_EXTENSION.bpf.c"))
        .expect("failed to create prog file");
    build_project(Some(&cargo_toml), None, Vec::new()).unwrap();
}

#[test]
fn test_build_workspace() {
    let (_dir, _, workspace_cargo_toml, proj_one_dir, proj_two_dir) = setup_temp_workspace();

    // No bpf progs yet
    build_project(Some(&workspace_cargo_toml), None, Vec::new()).unwrap_err();

    // Create bpf prog for project one
    create_dir(proj_one_dir.join("src/bpf")).expect("failed to create prog dir");
    let _prog_file_1 = File::create(proj_one_dir.join("src/bpf/prog1.bpf.c"))
        .expect("failed to create prog file 1");

    // Create bpf prog for project two
    create_dir(proj_two_dir.join("src/bpf")).expect("failed to create prog dir");
    let _prog_file_2 = File::create(proj_two_dir.join("src/bpf/prog2.bpf.c"))
        .expect("failed to create prog file 2");

    build_project(Some(&workspace_cargo_toml), None, Vec::new()).unwrap();
}

#[test]
fn test_build_workspace_collision() {
    let (_dir, _, workspace_cargo_toml, proj_one_dir, proj_two_dir) = setup_temp_workspace();

    // Create bpf prog for project one
    create_dir(proj_one_dir.join("src/bpf")).expect("failed to create prog dir");
    let _prog_file_1 = File::create(proj_one_dir.join("src/bpf/prog.bpf.c"))
        .expect("failed to create prog file 1");

    // Create bpf prog for project two, same name as project one
    create_dir(proj_two_dir.join("src/bpf")).expect("failed to create prog dir");
    let _prog_file_2 = File::create(proj_two_dir.join("src/bpf/prog.bpf.c"))
        .expect("failed to create prog file 2");

    build_project(Some(&workspace_cargo_toml), None, Vec::new()).unwrap_err();
}

#[test]
fn test_make_basic() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");

    // Add a prog
    let _prog_file =
        File::create(proj_dir.join("src/bpf/prog.bpf.c")).expect("failed to create prog file");

    make(Some(&cargo_toml), None, Vec::new(), Vec::new(), None).unwrap();

    // Validate generated object file
    validate_bpf_o(proj_dir.as_path().join("target/bpf/prog.bpf.o").as_path());

    // Check that skeleton exists (other tests will check for skeleton validity)
    assert!(proj_dir
        .as_path()
        .join("src/bpf/prog.skel.rs")
        .as_path()
        .exists());
}

#[test]
fn test_make_workspace() {
    let (_dir, workspace_dir, workspace_cargo_toml, proj_one_dir, proj_two_dir) =
        setup_temp_workspace();

    // Create bpf prog for project one
    create_dir(proj_one_dir.join("src/bpf")).expect("failed to create prog dir");
    let _prog_file_1 = File::create(proj_one_dir.join("src/bpf/prog1.bpf.c"))
        .expect("failed to create prog file 1");

    // Create bpf prog for project two, same name as project one
    create_dir(proj_two_dir.join("src/bpf")).expect("failed to create prog dir");
    let _prog_file_2 = File::create(proj_two_dir.join("src/bpf/prog2.bpf.c"))
        .expect("failed to create prog file 2");

    make(
        Some(&workspace_cargo_toml),
        None,
        Vec::new(),
        Vec::new(),
        None,
    )
    .unwrap();

    // Validate generated object files
    validate_bpf_o(
        workspace_dir
            .as_path()
            .join("target/bpf/prog1.bpf.o")
            .as_path(),
    );
    validate_bpf_o(
        workspace_dir
            .as_path()
            .join("target/bpf/prog2.bpf.o")
            .as_path(),
    );

    // Check that skeleton exists (other tests will check for skeleton validity)
    assert!(proj_one_dir
        .as_path()
        .join("src/bpf/prog1.skel.rs")
        .as_path()
        .exists());
    assert!(proj_two_dir
        .as_path()
        .join("src/bpf/prog2.skel.rs")
        .as_path()
        .exists());
}

fn build_rust_project_from_bpf_c_impl(bpf_c: &str, rust: &str, run: bool) -> (TempDir, PathBuf) {
    let (dir, proj_dir, cargo_toml) = setup_temp_project();

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");

    // Add a prog
    let mut prog = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(proj_dir.join("src/bpf/prog.bpf.c"))
        .expect("failed to open prog.bpf.c");
    prog.write_all(bpf_c.as_bytes())
        .expect("failed to write prog.bpf.c");

    // Lay down the necessary header files
    add_vmlinux_header(&proj_dir);

    make(Some(&cargo_toml), None, Vec::new(), Vec::new(), None).unwrap();

    let mut cargo = OpenOptions::new()
        .append(true)
        .open(&cargo_toml)
        .expect("failed to open Cargo.toml");

    // Make test project use our development libbpf-rs version
    writeln!(
        cargo,
        indoc! {r#"
            libbpf-rs = {{ path = "{}" }}
        "#},
        get_libbpf_rs_path().as_path().display()
    )
    .expect("failed to write to Cargo.toml");

    let mut source = OpenOptions::new()
        .write(true)
        .truncate(true)
        .open(proj_dir.join("src/main.rs"))
        .expect("failed to open main.rs");

    source
        .write_all(rust.as_bytes())
        .expect("failed to write to main.rs");

    let status = Command::new("cargo")
        .arg(if run { "run" } else { "build" })
        .arg("--quiet")
        .arg("--manifest-path")
        .arg(cargo_toml.into_os_string())
        .env("RUSTFLAGS", "-Dwarnings")
        .status()
        .expect("failed to run cargo");
    assert!(status.success());

    (dir, proj_dir.join("target/debug/proj"))
}

fn build_rust_project_from_bpf_c(bpf_c: &str, rust: &str) -> (TempDir, PathBuf) {
    let run = false;
    build_rust_project_from_bpf_c_impl(bpf_c, rust, run)
}

fn run_rust_project_from_bpf_c(bpf_c: &str, rust: &str) {
    let run = true;
    build_rust_project_from_bpf_c_impl(bpf_c, rust, run);
}

#[test]
fn test_skeleton_empty_source() {
    let bpf_c = String::new();
    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;
        use libbpf_rs::skel::OpenSkel;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let _skel = builder
                .open(&mut open_object)
                .expect("failed to open skel")
                .load()
                .expect("failed to load skel");
        }
    "#}
    .to_string();
    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

#[test]
fn test_skeleton_basic() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct {
            __uint(type, BPF_MAP_TYPE_HASH);
            __uint(max_entries, 1024);
            __type(key, u32);
            __type(value, u64);
        } mymap SEC(".maps");

        SEC("kprobe/foo")
        int this_is_my_prog(u64 *ctx)
        {
                return 0;
        }
    "#}
    .to_string();
    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;
        use libbpf_rs::skel::OpenSkel;
        use libbpf_rs::skel::Skel;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");

            // Check that we can grab handles to open maps/progs
            let _open_map = &open_skel.maps.mymap;
            let _open_prog = &open_skel.progs.this_is_my_prog;

            let mut skel = open_skel.load().expect("failed to load skel");

            // Check that we can grab handles to loaded maps/progs
            let _map = &skel.maps.mymap;
            let _prog = &skel.progs.this_is_my_prog;

            // Check that attach() is generated
            skel.attach().expect("failed to attach progs");

            // Check that Option<Link> field is generated
            let _mylink = skel.links.this_is_my_prog.unwrap();
        }
    "#}
    .to_string();

    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

/// Validate that `.bpf.objs` section contents are as expected.
#[test]
fn test_skeleton_generate_bpf_objs_section() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        SEC("kprobe/foo")
        int this_is_my_prog(u64 *ctx)
        {
            return 0;
        }
    "#}
    .to_string();
    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;
        use libbpf_rs::skel::OpenSkel;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");
            let _skel = open_skel.load().expect("failed to load skel");
        }
    "#}
    .to_string();

    let (_dir, bin) = build_rust_project_from_bpf_c(&bpf_c, &rust);

    let buffer = read(&bin)
        .unwrap_or_else(|_| panic!("failed to read binary file at path={}", &bin.display()));

    let elf = goblin::elf::Elf::parse(&buffer).unwrap();

    let (section_idx, _) = elf
        .section_headers
        .iter()
        .enumerate()
        .find(|(_i, sh)| &elf.shdr_strtab[sh.sh_name] == ".bpf.objs")
        .unwrap();

    let syms: Vec<_> = elf
        .syms
        .iter()
        .filter(|sym| sym.st_shndx == section_idx && sym.st_size > 0)
        .collect();

    assert_eq!(syms.len(), 1, "expected one symbol in .bpf.objs");
    let sym = syms.first().unwrap();

    let mut bpf_o = NamedTempFile::new().unwrap();
    let sym_start = sym.st_value as usize;
    let sym_end = sym_start + (sym.st_size as usize);
    bpf_o
        .as_file_mut()
        .write_all(&buffer[sym_start..sym_end])
        .unwrap();

    validate_bpf_o(bpf_o.path());
}

#[test]
fn test_skeleton_generate_datasec_static() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        SEC("kprobe/foo")
        int this_is_my_prog(u64 *ctx)
        {
                bpf_printk("this should not cause an error");
                return 0;
        }
    "#}
    .to_string();

    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use bpf::*;

        fn main() {
            let _builder = ProgSkelBuilder::default();
        }
    "#}
    .to_string();
    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

#[test]
fn test_skeleton_datasec() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        int myglobal = 0;
        void * const myconst = 0;
        int mycustomdata SEC(".data.custom");
        int mycustombss SEC(".bss.custom");
        const int mycustomrodata SEC(".rodata.custom.1") = 43;

        SEC("kprobe/foo")
        int this_is_my_prog(u64 *ctx)
        {
                return 0;
        }
    "#}
    .to_string();

    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;
        use libbpf_rs::skel::OpenSkel;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let mut open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");
            // Check that we set rodata vars before load
            open_skel.maps.rodata_data.as_deref_mut().unwrap().myconst = std::ptr::null_mut();

            // We can always set bss vars
            open_skel.maps.bss_data.as_deref_mut().unwrap().myglobal = 42;
            open_skel.maps.data_custom_data.as_deref_mut().unwrap().mycustomdata = 1337;
            open_skel.maps.bss_custom_data.as_deref_mut().unwrap().mycustombss = 12;
            assert_eq!(open_skel.maps.rodata_custom_1_data.as_deref_mut().unwrap().mycustomrodata, 43);

            let mut skel = open_skel.load().expect("failed to load skel");

            // We can always set bss vars
            skel.maps.bss_data.as_deref_mut().unwrap().myglobal = 24;
            skel.maps.data_custom_data.as_deref_mut().unwrap().mycustomdata += 1;
            skel.maps.bss_custom_data.as_deref_mut().unwrap().mycustombss += 1;
            assert_eq!(skel.maps.rodata_custom_1_data.as_deref().unwrap().mycustomrodata, 43);

            // Read only for rodata after load
            let _rodata: &types::rodata = skel.maps.rodata_data.as_deref().unwrap();
        }
    "#}
    .to_string();
    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

#[test]
fn test_skeleton_builder_basic() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");

    // Add a prog
    let mut prog = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(proj_dir.join("src/bpf/prog.bpf.c"))
        .expect("failed to open prog.bpf.c");

    write!(
        prog,
        indoc! {r#"
            #include "vmlinux.h"
            #include <bpf/bpf_helpers.h>

            struct unique_key {{
                int cap;
                u32 tgid;
                u64 cgroupid;
            }};

            struct {{
                __uint(type, BPF_MAP_TYPE_HASH);
                __uint(max_entries, 1024);
                __type(key, struct unique_key);
                __type(value, u64);
            }} mymap SEC(".maps");

            struct {{
                __uint(type, BPF_MAP_TYPE_HASH);
                __uint(max_entries, 1024);
                __uint(key_size, 8);
                __type(value, u64);
            }} mymap2 SEC(".maps");

            SEC("kprobe/foo")
            int this_is_my_prog(u64 *ctx)
            {{
                return 0;
            }}
        "#}
    )
    .expect("failed to write prog.bpf.c");

    // Lay down the necessary header files
    add_vmlinux_header(&proj_dir);

    // Generate skeleton file
    let skel = proj_dir.join("src/bpf/skel.rs");
    SkeletonBuilder::new()
        .source(proj_dir.join("src/bpf/prog.bpf.c"))
        .build_and_generate(&skel)
        .unwrap();

    let mut cargo = OpenOptions::new()
        .append(true)
        .open(&cargo_toml)
        .expect("failed to open Cargo.toml");

    // Make test project use our development libbpf-rs version
    writeln!(
        cargo,
        indoc! {r#"
            libbpf-rs = {{ path = "{}" }}
        "#},
        get_libbpf_rs_path().as_path().display()
    )
    .expect("failed to write to Cargo.toml");

    let mut source = OpenOptions::new()
        .write(true)
        .truncate(true)
        .open(proj_dir.join("src/main.rs"))
        .expect("failed to open main.rs");

    write!(
        source,
        r#"
        #[path = "{skel_path}"]
        mod skel;
        use std::mem::MaybeUninit;
        use skel::*;
        use libbpf_rs::skel::SkelBuilder;
        use libbpf_rs::skel::OpenSkel;
        use libbpf_rs::skel::Skel;

        fn main() {{
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");

            // Check that we can grab handles to open maps/progs
            let _open_map = &open_skel.maps.mymap;
            let _open_map2 = &open_skel.maps.mymap2;
            let _open_prog = &open_skel.progs.this_is_my_prog;

            let mut skel = open_skel.load().expect("failed to load skel");

            // Check that we can grab handles to loaded maps/progs
            let _map = &skel.maps.mymap;
            let _map2 = &skel.maps.mymap2;
            let _prog = &skel.progs.this_is_my_prog;

            // Check that attach() is generated
            skel.attach().expect("failed to attach progs");

            // Check that Option<Link> field is generated
            let _mylink = skel.links.this_is_my_prog.unwrap();

            let _key = types::unique_key::default();
        }}
        "#,
        skel_path = skel.display(),
    )
    .expect("failed to write to main.rs");

    let status = Command::new("cargo")
        .arg("build")
        .arg("--quiet")
        .arg("--manifest-path")
        .arg(cargo_toml.into_os_string())
        .env("RUSTFLAGS", "-Dwarnings")
        .status()
        .expect("failed to spawn cargo-build");
    assert!(status.success());
}

#[test]
fn test_skeleton_builder_clang_opts() {
    let (_dir, proj_dir, _cargo_toml) = setup_temp_project();

    // Add prog dir
    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");

    // Add a prog
    let mut prog = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(proj_dir.join("src/bpf/prog.bpf.c"))
        .expect("failed to open prog.bpf.c");

    write!(
        prog,
        indoc! {r#"
            #ifndef PURPOSE
            #error "what is my purpose?"
            #endif
        "#},
    )
    .expect("failed to write prog.bpf.c");

    let skel = proj_dir.join("src/bpf/skel.rs");
    // Should fail b/c `PURPOSE` not defined
    SkeletonBuilder::new()
        .source(proj_dir.join("src/bpf/prog.bpf.c"))
        .build_and_generate(&skel)
        .unwrap_err();

    // Should succeed b/c we defined the macro
    SkeletonBuilder::new()
        .source(proj_dir.join("src/bpf/prog.bpf.c"))
        .clang_args(["-DPURPOSE=you_pass_the_butter"])
        .build_and_generate(&skel)
        .unwrap();
}

#[test]
fn test_skeleton_builder_arrays_ptrs() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct inner {
            int a;
        };

        struct mystruct {
            int x;
            struct {
                int b;
            } y[2];
            struct inner z[2];
        };

        const volatile struct mystruct my_array[1] = { {0} };
        struct mystruct * const my_ptr = NULL;
    "#}
    .to_string();

    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");
            let rodata = open_skel.maps.rodata_data.as_deref().unwrap();

            // That everything exists and compiled okay
            let _ = rodata.my_array[0].x;
            let _ = rodata.my_array[0].y[1].b;
            let _ = rodata.my_array[0].z[0].a;
            let _ = rodata.my_ptr;
        }
    "#}
    .to_string();
    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

/// Check that we can correctly work with C enums that contain multiple
/// variants with the same value.
#[test]
fn test_skeleton_enum_with_same_value_variants() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        enum Foo {
            Zero = 0,
            ZeroDup = Zero,
            One,
        };

        enum Foo foo;
    "#}
    .to_string();

    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use bpf::*;

        fn main() {
            let zero1 = types::Foo::Zero;
            let zero2 = types::Foo::ZeroDup;

            assert_eq!(zero1, zero2);
            assert_ne!(types::Foo::Zero, types::Foo::One);
        }
    "#}
    .to_string();
    run_rust_project_from_bpf_c(&bpf_c, &rust);
}

#[test]
fn test_skeleton_generate_struct_with_pointer() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct list {
            struct list *next;
        };

        struct list l;
    "#}
    .to_string();

    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let _open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");
        }
    "#}
    .to_string();
    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

/// Check that we generate valid Rust code for an array of pointers.
#[test]
fn test_skeleton_generate_struct_with_pointer_array() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct vm_area_struct;

        struct vmacache {
            struct vm_area_struct *vmas[4];
        };

        struct vmacache c;
    "#}
    .to_string();

    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let _open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");
        }
    "#}
    .to_string();
    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

/// Generate a skeleton that includes multiple "anon" type definitions.
#[test]
fn test_skeleton_builder_multiple_anon() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct unique_key {
            struct {
                u32 foo;
                u64 bar;
            } foobar;
        };

        struct {
            __uint(type, BPF_MAP_TYPE_HASH);
            __uint(max_entries, 1024);
            __type(key, struct unique_key);
            __type(value, u64);
        } mymap SEC(".maps");

        struct Foo {
            int x;
            struct {
                u8 y[10];
                u16 z[16];
            } bar;
            struct {
                u32 w;
                u64 *u;
            } baz;
            int w;
        };

        struct Foo foo;

        SEC("kprobe/foo")
        int this_is_my_prog(u64 *ctx)
        {
                return 0;
        }
    "#}
    .to_string();

    let rust = indoc! {r#"
        mod bpf;
        use std::mem::MaybeUninit;
        use bpf::*;
        use libbpf_rs::skel::SkelBuilder;
        use libbpf_rs::skel::OpenSkel;

        fn main() {
            let builder = ProgSkelBuilder::default();
            let mut open_object = MaybeUninit::uninit();
            let open_skel = builder
                .open(&mut open_object)
                .expect("failed to open skel");

            let _skel = open_skel.load().expect("failed to load skel");
            let _key = types::unique_key::default();
        }
    "#}
    .to_string();

    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

/// Test that we generate a valid skeleton when multiple kfuncs are being used.
#[test]
fn test_skeleton_multipl_kfuncs() {
    let bpf_c = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        extern int bpf_dynptr_from_xdp(
            struct xdp_md* xdp,
            __u64 flags,
            struct bpf_dynptr* ptr__uninit) __ksym;

        extern void* bpf_dynptr_slice(
            const struct bpf_dynptr* ptr,
            __u32 offset,
            void* buffer,
            __u32 buffer__szk) __ksym;

        SEC("xdp")
        int steering(struct xdp_md* xdp) {
            u8 ethb[sizeof(struct ethhdr)];
            struct bpf_dynptr ptr;
            struct ethhdr* eth;

            if (bpf_dynptr_from_xdp(xdp, 0, &ptr)) {
              return XDP_PASS;
            }
            eth = bpf_dynptr_slice(&ptr, 0, ethb, sizeof(ethb));
            (void)eth;
        }
    "#}
    .to_string();

    let rust = indoc! {r#"
        #![warn(elided_lifetimes_in_paths)]
        mod bpf;
        use bpf::*;

        fn main() {
            let _builder = ProgSkelBuilder::default();
        }
    "#}
    .to_string();
    build_rust_project_from_bpf_c(&bpf_c, &rust);
}

/// Check that skeleton creation is deterministic, i.e., that no temporary paths
/// change the output between invocations.
#[test]
#[ignore = "may fail on some systems; depends on kernel headers that have been seen to be broken"]
fn test_skeleton_builder_deterministic() {
    let (_dir, proj_dir, _cargo_toml) = setup_temp_project();

    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
    write(
        proj_dir.join("src/bpf/prog.bpf.c"),
        r#"
            #include "vmlinux.h"
            #include <bpf/bpf_helpers.h>

            struct {
                __uint(type, BPF_MAP_TYPE_REUSEPORT_SOCKARRAY);
            } sock_map SEC(".maps");

            SEC("sk_reuseport")
            long prog_select_sk(struct sk_reuseport_md *reuse_md)
            {
                unsigned int index = 0;
                bpf_sk_select_reuseport(reuse_md, &sock_map, &index, 0);
                return SK_PASS;
            }
        "#,
    )
    .expect("failed to write prog.bpf.c");

    add_vmlinux_header(&proj_dir);

    let skel1 = proj_dir.join("src/bpf/skel1.rs");
    SkeletonBuilder::new()
        .source(proj_dir.join("src/bpf/prog.bpf.c"))
        .build_and_generate(&skel1)
        .unwrap();
    let skel1 = read_to_string(skel1).unwrap();

    let skel2 = proj_dir.join("src/bpf/skel2.rs");
    SkeletonBuilder::new()
        .source(proj_dir.join("src/bpf/prog.bpf.c"))
        .build_and_generate(&skel2)
        .unwrap();
    let skel2 = read_to_string(skel2).unwrap();

    assert_eq!(skel1, skel2);
}

/// Check that we generate a valid skeleton in the presence of duplicate
/// structs in the BTF.
#[test]
fn test_skeleton_duplicate_struct() {
    let (_dir, proj_dir, cargo_toml) = setup_temp_project();

    create_dir(proj_dir.join("src/bpf")).expect("failed to create prog dir");
    let prog1 = proj_dir.join("src/bpf/prog1.bpf.c");
    write(
        &prog1,
        r#"
            #include "vmlinux.h"

            struct foobar {
                int bar;
            } foo;
        "#,
    )
    .expect("failed to write prog.bpf.c");
    let prog2 = proj_dir.join("src/bpf/prog2.bpf.c");
    write(
        &prog2,
        r#"
            #include "vmlinux.h"

            typedef int nonsensicaltypedeftoint;

            struct foobar {
                nonsensicaltypedeftoint bar;
            } baz;
        "#,
    )
    .expect("failed to write prog.bpf.c");

    add_vmlinux_header(&proj_dir);

    let bpf_o = proj_dir.join("prog.bpf.o");
    let _output = BpfObjBuilder::default()
        .build_many([&prog1, &prog2], &bpf_o)
        .unwrap();

    let skel = proj_dir.join("src/bpf/skel.rs");
    let () = SkeletonBuilder::new().obj(&bpf_o).generate(&skel).unwrap();

    let mut cargo = OpenOptions::new()
        .append(true)
        .open(&cargo_toml)
        .expect("failed to open Cargo.toml");

    // Make test project use our development libbpf-rs version
    writeln!(
        cargo,
        indoc! {r#"
            libbpf-rs = {{ path = "{}" }}
        "#},
        get_libbpf_rs_path().as_path().display()
    )
    .expect("failed to write to Cargo.toml");

    let mut source = OpenOptions::new()
        .write(true)
        .truncate(true)
        .open(proj_dir.join("src/main.rs"))
        .expect("failed to open main.rs");

    write!(
        source,
        r#"
        #[path = "{skel_path}"]
        mod skel;
        use skel::*;

        fn main() {{
            let _foobar1 = types::foobar::default();
            let _foobar2 = types::foobar_2::default();
        }}
        "#,
        skel_path = skel.display(),
    )
    .expect("failed to write to main.rs");

    let status = Command::new("cargo")
        .arg("build")
        .arg("--quiet")
        .arg("--manifest-path")
        .arg(cargo_toml.into_os_string())
        .env("RUSTFLAGS", "-Dwarnings")
        .status()
        .expect("failed to spawn cargo-build");
    assert!(status.success());
}

// -- TEST RUST GENERATION OF BTF PROGRAMS --

/// Searches the Btf struct for a `BtfType`
/// returns type identifier <u32> if found
/// fails calling test if not found, or if duplicates exist
///
/// ```
/// usage: -- search for basic struct/union/enum with exact match to name
///        find_type_in_btf!(<Btf to search in>,
///                          <BtfType to search for>,
///                          <&str search name>);
///        eg:
///        let my_type = find_type_in_btf!(btf, Struct, "name");
///    or: -- search for basic struct/union/enum/var containing name substring
///        find_type_in_btf!(<Btf to search in>,
///                          <BtfType to search for>,
///                          <&str search name>,
///                          true);
///        eg:
///        let my_type = find_type_in_btf!(btf, Datasec, "bss", true);
///    or: -- search for a Variable name in a Datasec -- exact match
///        find_type_in_btf!(<Btf to search in>,
///                          Var,
///                          <&str search name>);
///        eg
///        let let my_type = find_type_in_btf!(btf, Var, "name");
/// ```
macro_rules! find_type_in_btf {
    // match for a named BtfType::Var inside all vars in a Datasec
    ($btf:ident, Var, $name:literal) => {{
        let mut asserted_type: Option<BtfType<'_>> = None;
        for ty in $btf.type_by_kind::<types::DataSec<'_>>() {
            for var in ty.iter() {
                let var_ty = $btf
                    .type_by_id(var.ty)
                    .expect("Failed to lookup datasec var");
                let t = types::Var::try_from(var_ty).unwrap_or_else(|_| {
                    panic!("Datasec var didn't point to a var. Instead: {}", var.ty)
                });
                if t.name().map(|o| o.to_str().unwrap()) == Some($name) {
                    assert!(asserted_type.is_none()); // No duplicates
                    asserted_type = Some(var_ty);
                }
            }
        }
        asserted_type.unwrap()
    }};

    // match for a named BtfType.
    ($btf:ident, $btf_type:path, $name:literal) => {{
        find_type_in_btf!($btf, $btf_type, $name, false)
    }};

    // match for a named BtfType.
    // If substr == true then test for substring rather than exact match
    ($btf:ident, $btf_type:path, $name:literal, $substr:expr) => {{
        let mut asserted_type: Option<$btf_type> = None;

        for ty in $btf.type_by_kind::<$btf_type>() {
            let name = ty.name().map(|o| o.to_str().unwrap());
            let found = if $substr {
                name.map(|n| n.contains($name)).unwrap_or(false)
            } else {
                name == Some($name)
            };

            if found {
                assert!(asserted_type.is_none()); // No duplicates
                asserted_type = Some(ty);
            }
        }
        asserted_type.unwrap()
    }};
}

/// Boiler plate code to build a struct Btf from a raw string
/// returns struct Btf if able to compile
/// fails calling test if unable to compile
fn build_btf_mmap(prog_text: &str) -> Mmap {
    let dir = tempdir().expect("failed to create tempdir");
    let dir = dir.path();
    let bpf_c = dir.join("prog.bpf.c");
    let mut file = OpenOptions::new()
        .write(true)
        .create(true)
        .truncate(true)
        .open(&bpf_c)
        .expect("failed to open prog.bpf.c");

    let () = file
        .write_all(prog_text.as_bytes())
        .expect("failed to write prog.bpf.c");
    // Lay down the necessary header files
    let () = write_vmlinux_header(dir);

    let bpf_o = dir.join("prog.bpf.o");
    let _output = BpfObjBuilder::default().build(&bpf_c, &bpf_o).unwrap();

    let obj = OpenOptions::new()
        .read(true)
        .open(bpf_o)
        .expect("failed to open object file");
    unsafe { Mmap::map(&obj) }.expect("Failed to mmap object file")
}

fn btf_from_mmap(mmap: &Mmap) -> GenBtf<'_> {
    let btf = Btf::from_raw("prog", mmap)
        .expect("Failed to initialize Btf")
        .expect("Did not find .BTF section");

    assert_ne!(btf.type_by_kind::<BtfType<'_>>().count(), 0);

    GenBtf::from(btf)
}

#[track_caller]
fn assert_output(actual_output: &str, expected_output: &str) {
    let ao = actual_output.trim_end().trim_start();
    let eo = expected_output.trim_end().trim_start();

    println!("---------------");
    println!("expected output");
    println!("---------------");
    println!("{eo}");
    println!("-------------");
    println!("actual output");
    println!("-------------");
    println!("{ao}");

    assert!(eo == ao);
}

/// Tests the `type_definition` output of a `type_id` against a given expected output
/// Will trim leading and trailing whitespace from both expected output and from
/// the generated `type_definition`
/// fails calling text if `type_definition` does not match `expected_output`
#[track_caller]
fn assert_definition(btf: &GenBtf<'_>, btf_item: &BtfType<'_>, expected_output: &str) {
    let actual_output = btf
        .type_definition(*btf_item, &mut HashSet::new())
        .expect("Failed to generate struct Foo defn");
    assert_output(&actual_output, expected_output)
}

#[test]
fn test_btf_dump_reserved_keyword_escaping() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            u64 type;
            void* mod;
            void* self;
        };

        struct Foo foo = {{0}};
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub r#type: u64,
            pub r#mod: *mut std::ffi::c_void,
            pub slf: *mut std::ffi::c_void,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    r#type: u64::default(),
                    r#mod: std::ptr::null_mut(),
                    slf: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_basic() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        int myglobal = 1;

        struct Foo {
            int x;
            char y[10];
            void *z;
        };

        struct Foo foo = {{0}};
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub y: [i8; 10],
            pub z: *mut std::ffi::c_void,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                    y: [i8::default(); 10],
                    z: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
    let foo = find_type_in_btf!(btf, Var, "foo");
    let myglobal = find_type_in_btf!(btf, Var, "myglobal");

    assert_eq!(
        "Foo",
        btf.type_declaration(foo)
            .expect("Failed to generate foo decl")
    );
    assert_eq!(
        "i32",
        btf.type_declaration(myglobal)
            .expect("Failed to generate myglobal decl")
    );

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_fwd() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct sometypethatdoesnotexist *m;
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    let m = find_type_in_btf!(btf, types::Var<'_>, "m");

    assert_eq!(
        "*mut std::ffi::c_void",
        btf.type_declaration(*m)
            .expect("Failed to generate foo decl")
    );
}

#[test]
fn test_btf_dump_align() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
        } __attribute__((aligned(16)));

        struct Foo foo = {1};
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub __pad_4: [u8; 12],
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_insane_align() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
        } __attribute__((aligned(64)));

        struct Foo foo = {1};
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub __pad_4: [u8; 60],
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                    __pad_4: [u8::default(); 60],
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_basic_long_array() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        int myglobal = 1;

        struct Foo {
            int x;
            char y[33];
            void *z;
        };

        struct Foo foo = {{0}};
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub y: [i8; 33],
            pub z: *mut std::ffi::c_void,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                    y: [i8::default(); 33],
                    z: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our types
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");
    let foo = find_type_in_btf!(btf, Var, "foo");
    let myglobal = find_type_in_btf!(btf, Var, "myglobal");

    assert_eq!(
        "Foo",
        btf.type_declaration(foo)
            .expect("Failed to generate foo decl")
    );
    assert_eq!(
        "i32",
        btf.type_declaration(myglobal)
            .expect("Failed to generate myglobal decl")
    );

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_struct_definition() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Bar {
            u16 x;
        };

        struct Foo {
            int *ip;
            int **ipp;
            struct Bar bar;
            struct Bar *pb;
            volatile u64 v;
            const volatile s64 cv;
            char * restrict r;
        };

        struct Foo foo;
    "#};

    // Note how there's 6 bytes of padding. It's not necessary on 64 bit archs but
    // we've assumed 32 bit arch during padding generation.
    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub ip: *mut i32,
            pub ipp: *mut *mut i32,
            pub bar: Bar,
            pub __pad_18: [u8; 6],
            pub pb: *mut Bar,
            pub v: u64,
            pub cv: i64,
            pub r: *mut i8,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    ip: std::ptr::null_mut(),
                    ipp: std::ptr::null_mut(),
                    bar: Bar::default(),
                    __pad_18: [u8::default(); 6],
                    pb: std::ptr::null_mut(),
                    v: u64::default(),
                    cv: i64::default(),
                    r: std::ptr::null_mut(),
                }
            }
        }
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub x: u16,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_struct_definition_func_proto() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct with_func_proto {
            struct with_func_proto *next;
            void (*func)(struct with_func_proto *);
        };

        struct with_func_proto w;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct with_func_proto {
            pub next: *mut with_func_proto,
            pub func: *mut std::ffi::c_void,
        }
        impl Default for with_func_proto {
            fn default() -> Self {
                Self {
                    next: std::ptr::null_mut(),
                    func: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "with_func_proto");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_struct_definition_long_array() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Bar {
            u16 x;
            u16 y[33];
        };

        struct Foo {
            int *ip;
            int **ipp;
            struct Bar bar;
            struct Bar *pb;
            volatile u64 v;
            const volatile s64 cv;
            char * restrict r;
        };

        struct Foo foo;
    "#};

    // Note how there's 6 bytes of padding. It's not necessary on 64 bit archs but
    // we've assumed 32 bit arch during padding generation.
    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub ip: *mut i32,
            pub ipp: *mut *mut i32,
            pub bar: Bar,
            pub __pad_84: [u8; 4],
            pub pb: *mut Bar,
            pub v: u64,
            pub cv: i64,
            pub r: *mut i8,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    ip: std::ptr::null_mut(),
                    ipp: std::ptr::null_mut(),
                    bar: Bar::default(),
                    __pad_84: [u8::default(); 4],
                    pb: std::ptr::null_mut(),
                    v: u64::default(),
                    cv: i64::default(),
                    r: std::ptr::null_mut(),
                }
            }
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub x: u16,
            pub y: [u16; 33],
        }
        impl Default for Bar {
            fn default() -> Self {
                Self {
                    x: u16::default(),
                    y: [u16::default(); 33],
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_packed_struct() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            char y;
            __s32 z[2];
        } __attribute__((packed));

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C, packed)]
        pub struct Foo {
            pub x: i32,
            pub y: i8,
            pub z: [i32; 2],
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_packed_struct_long_array() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            char y;
            __s32 z[33];
        } __attribute__((packed));

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C, packed)]
        pub struct Foo {
            pub x: i32,
            pub y: i8,
            pub z: [i32; 33],
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                    y: i8::default(),
                    z: [i32::default(); 33],
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_bitfield_1() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            unsigned short x: 2;
            unsigned short y: 3;
        };

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub __pad_0: [u8; 2],
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_bitfield_2() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            const char *name;
            void *kset;
            unsigned int state_initialized: 1;
            unsigned int state_in_sysfs: 1;
            unsigned int state_add_uevent_sent: 1;
            unsigned int state_remove_uevent_sent: 1;
            unsigned int uevent_suppress: 1;
            bool flag;
        };

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub name: *mut i8,
            pub kset: *mut std::ffi::c_void,
            pub __pad_16: [u8; 1],
            pub flag: std::mem::MaybeUninit<bool>,
            pub __pad_18: [u8; 6],
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    name: std::ptr::null_mut(),
                    kset: std::ptr::null_mut(),
                    __pad_16: [u8::default(); 1],
                    flag: std::mem::MaybeUninit::new(bool::default()),
                    __pad_18: [u8::default(); 6],
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_enum() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        enum Foo {
            Zero = 0,
            One,
            Seven = 7,
            ZeroDup = Zero,
            Infinite = 0xffffffff,
        };

        struct Bar {
            enum Foo foo;
        };
        struct Bar bar;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub foo: Foo,
        }
        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
        #[repr(transparent)]
        pub struct Foo(pub u32);
        #[allow(non_upper_case_globals)]
        impl Foo {
            pub const Zero: Foo = Foo(0);
            pub const One: Foo = Foo(1);
            pub const Seven: Foo = Foo(7);
            pub const ZeroDup: Foo = Foo(0);
            pub const Infinite: Foo = Foo(4294967295);
        }
        impl Default for Foo {
            fn default() -> Self { Foo::Zero }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
    assert_definition(&btf, &struct_bar, expected_output);
}

#[test]
fn test_btf_dump_definition_enum_signed() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        enum Foo {
            Zero = 0,
            Infinite = -1,
        };

        struct Bar {
            enum Foo foo;
        };
        struct Bar bar;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub foo: Foo,
        }
        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
        #[repr(transparent)]
        pub struct Foo(pub i32);
        #[allow(non_upper_case_globals)]
        impl Foo {
            pub const Zero: Foo = Foo(0);
            pub const Infinite: Foo = Foo(-1);
        }
        impl Default for Foo {
            fn default() -> Self { Foo::Zero }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
    assert_definition(&btf, &struct_bar, expected_output);
}

#[test]
fn test_btf_dump_definition_enum64() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        enum Foo {
            Zero = 0,
            Infinite = 0xffffffffffffffff,
        };

        struct Bar {
            enum Foo foo;
        };
        struct Bar bar;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub foo: Foo,
        }
        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
        #[repr(transparent)]
        pub struct Foo(pub u64);
        #[allow(non_upper_case_globals)]
        impl Foo {
            pub const Zero: Foo = Foo(0);
            pub const Infinite: Foo = Foo(18446744073709551615);
        }
        impl Default for Foo {
            fn default() -> Self { Foo::Zero }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
    assert_definition(&btf, &struct_bar, expected_output);
}

#[test]
fn test_btf_dump_definition_enum64_signed() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        enum Foo {
            Zero = 0,
            Whatevs = -922337854775808,
        };

        struct Bar {
            enum Foo foo;
        };
        struct Bar bar;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub foo: Foo,
        }
        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
        #[repr(transparent)]
        pub struct Foo(pub i64);
        #[allow(non_upper_case_globals)]
        impl Foo {
            pub const Zero: Foo = Foo(0);
            pub const Whatevs: Foo = Foo(-922337854775808);
        }
        impl Default for Foo {
            fn default() -> Self { Foo::Zero }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_bar = find_type_in_btf!(btf, types::Struct<'_>, "Bar");
    assert_definition(&btf, &struct_bar, expected_output);
}

#[test]
fn test_btf_dump_definition_union() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        union Foo {
            int x;
            __u32 y;
            char z[128];
        };

        union Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union Foo {
            pub x: i32,
            pub y: u32,
            pub z: [i8; 128],
        }
        impl std::fmt::Debug for Foo {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let union_foo = find_type_in_btf!(btf, types::Union<'_>, "Foo");

    assert_definition(&btf, &union_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_shared_dependent_types() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Bar {
            u16 x;
        };

        struct Foo {
            struct Bar bar;
            struct Bar bartwo;
        };

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub bar: Bar,
            pub bartwo: Bar,
        }
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub x: u16,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_datasec() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            char y[10];
            void *z;
        };

        struct Foo foo = {0};

        const int myconstglobal = 0;
    "#};

    let bss_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct bss {
            pub foo: Foo,
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub y: [i8; 10],
            pub z: *mut std::ffi::c_void,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                    y: [i8::default(); 10],
                    z: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let rodata_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct rodata {
            pub myconstglobal: i32,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find out types
    let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
    let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);

    assert_definition(&btf, &bss, bss_output);
    assert_definition(&btf, &rodata, rodata_output);
}

#[test]
fn test_btf_dump_definition_datasec_custom() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        int bss_array[1] SEC(".bss.custom");
        int data_array[1] SEC(".data.custom");
        const int rodata_array[1] SEC(".rodata.custom.1");
    "#};

    let bss_custom_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct bss_custom {
            pub bss_array: [i32; 1],
        }
    "#};

    let data_custom_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct data_custom {
            pub data_array: [i32; 1],
        }
    "#};

    let rodata_custom_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct rodata_custom_1 {
            pub rodata_array: [i32; 1],
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let bss_custom = find_type_in_btf!(btf, types::DataSec<'_>, ".bss.custom", false);
    let data_custom = find_type_in_btf!(btf, types::DataSec<'_>, ".data.custom", false);
    let rodata_custom = find_type_in_btf!(btf, types::DataSec<'_>, ".rodata.custom.1", false);

    assert_definition(&btf, &bss_custom, bss_custom_output);
    assert_definition(&btf, &data_custom, data_custom_output);
    assert_definition(&btf, &rodata_custom, rodata_custom_output);
}

#[test]
fn test_btf_dump_definition_datasec_long_array() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            char y[33];
            void *z;
        };

        struct Foo foo = {0};

        const int myconstglobal = 0;
    "#};

    let bss_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct bss {
            pub foo: Foo,
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub y: [i8; 33],
            pub z: *mut std::ffi::c_void,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                    y: [i8::default(); 33],
                    z: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let rodata_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct rodata {
            pub myconstglobal: i32,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our types
    let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
    let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);

    assert_definition(&btf, &bss, bss_output);
    assert_definition(&btf, &rodata, rodata_output);
}

#[test]
fn test_btf_dump_definition_datasec_multiple() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            char y[10];
            void *z;
        };

        struct Foo foo = {0};
        struct Foo foo2 = {0};
        struct Foo foo3 = {0};

        const int ci = 0;
        const int ci2 = 0;
        const int ci3 = 0;
    "#};

    let bss_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct bss {
            pub foo: Foo,
            pub foo2: Foo,
            pub foo3: Foo,
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub y: [i8; 10],
            pub z: *mut std::ffi::c_void,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    x: i32::default(),
                    y: [i8::default(); 10],
                    z: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let rodata_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct rodata {
            pub ci: i32,
            pub ci2: i32,
            pub ci3: i32,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our types
    let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
    let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);

    assert_definition(&btf, &bss, bss_output);
    assert_definition(&btf, &rodata, rodata_output);
}

#[test]
fn test_btf_dump_definition_datasec_multiple_long_array() {
    let prog_text = indoc! {r#"
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

struct Foo {
    int x;
    char y[33];
    void *z;
};

struct Foo foo = {0};
struct Foo foo2 = {0};
struct Foo foo3 = {0};

const int ci = 0;
const int ci2 = 0;
const int ci3 = 0;
"#};

    let bss_output = indoc! {r#"
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct bss {
    pub foo: Foo,
    pub foo2: Foo,
    pub foo3: Foo,
}
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct Foo {
    pub x: i32,
    pub y: [i8; 33],
    pub z: *mut std::ffi::c_void,
}
impl Default for Foo {
    fn default() -> Self {
        Self {
            x: i32::default(),
            y: [i8::default(); 33],
            z: std::ptr::null_mut(),
        }
    }
}
"#};

    let rodata_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct rodata {
            pub ci: i32,
            pub ci2: i32,
            pub ci3: i32,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our types
    let bss = find_type_in_btf!(btf, types::DataSec<'_>, "bss", true);
    let rodata = find_type_in_btf!(btf, types::DataSec<'_>, "rodata", true);

    assert_definition(&btf, &bss, bss_output);
    assert_definition(&btf, &rodata, rodata_output);
}

#[test]
fn test_btf_dump_definition_struct_inner_anon_union() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            union {
                u8 y[10];
                u16 z[16];
            } bar;
            union {
                u32 w;
                u64 *u;
            } baz;
            int w;
        };

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub bar: __anon_Foo_1,
            pub __pad_36: [u8; 4],
            pub baz: __anon_Foo_2,
            pub w: i32,
            pub __pad_52: [u8; 4],
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_Foo_1 {
            pub y: [u8; 10],
            pub z: [u16; 16],
        }
        impl std::fmt::Debug for __anon_Foo_1 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_Foo_1 {
            fn default() -> Self {
                Self {
                    y: [u8::default(); 10],
                }
            }
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_Foo_2 {
            pub w: u32,
            pub u: *mut u64,
        }
        impl std::fmt::Debug for __anon_Foo_2 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_Foo_2 {
            fn default() -> Self {
                Self {
                    w: u32::default(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_struct_inner_anon_struct() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            struct {
                u8 y[10];
                u16 z[16];
            } bar;
            struct {
                u32 w;
                u64 *u;
            } baz;
            int w;
        };

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub bar: __anon_Foo_1,
            pub baz: __anon_Foo_2,
            pub w: i32,
            pub __pad_68: [u8; 4],
        }
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_Foo_1 {
            pub y: [u8; 10],
            pub z: [u16; 16],
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_Foo_2 {
            pub w: u32,
            pub __pad_4: [u8; 4],
            pub u: *mut u64,
        }
        impl Default for __anon_Foo_2 {
            fn default() -> Self {
                Self {
                    w: u32::default(),
                    __pad_4: [u8::default(); 4],
                    u: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_struct_inner_anon_struct_and_union() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            int x;
            struct {
                u8 y[10];
                u16 z[16];
            } bar;
            union {
                char *a;
                int b;
            } zerg;
            struct {
                u32 w;
                u64 *u;
            } baz;
            int w;
            union {
                u8 c;
                u64 d[5];
            } flarg;
        };

        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub x: i32,
            pub bar: __anon_Foo_1,
            pub zerg: __anon_Foo_2,
            pub baz: __anon_Foo_3,
            pub w: i32,
            pub __pad_76: [u8; 4],
            pub flarg: __anon_Foo_4,
        }
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_Foo_1 {
            pub y: [u8; 10],
            pub z: [u16; 16],
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_Foo_2 {
            pub a: *mut i8,
            pub b: i32,
        }
        impl std::fmt::Debug for __anon_Foo_2 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_Foo_2 {
            fn default() -> Self {
                Self {
                    a: std::ptr::null_mut(),
                }
            }
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_Foo_3 {
            pub w: u32,
            pub __pad_4: [u8; 4],
            pub u: *mut u64,
        }
        impl Default for __anon_Foo_3 {
            fn default() -> Self {
                Self {
                    w: u32::default(),
                    __pad_4: [u8::default(); 4],
                    u: std::ptr::null_mut(),
                }
            }
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_Foo_4 {
            pub c: u8,
            pub d: [u64; 5],
        }
        impl std::fmt::Debug for __anon_Foo_4 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_Foo_4 {
            fn default() -> Self {
                Self {
                    c: u8::default(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find our struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_anon_union_member() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            union {
                char *name;
                void *tp;
            };
        };

        struct Foo foo = {{0}};
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub __anon_Foo_1: __anon_Foo_1,
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_Foo_1 {
            pub name: *mut i8,
            pub tp: *mut std::ffi::c_void,
        }
        impl std::fmt::Debug for __anon_Foo_1 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_Foo_1 {
            fn default() -> Self {
                Self {
                    name: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_anon_member_tree() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            union {
                struct {
                    char *name;
                    void *tp;
                };
                struct Bar {
                    union {
                        struct {
                            char *name;
                            void *trp;
                        };
                        struct Baz {
                            char *name;
                            void *trp;
                        } baz;
                    };
                } bar;
            };
        };

        struct Foo foo = {0};
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub __anon_Foo_1: __anon_Foo_1,
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_Foo_1 {
            pub __anon_Foo_2: __anon_Foo_2,
            pub bar: Bar,
        }
        impl std::fmt::Debug for __anon_Foo_1 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_Foo_1 {
            fn default() -> Self {
                Self {
                    __anon_Foo_2: __anon_Foo_2::default(),
                }
            }
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_Foo_2 {
            pub name: *mut i8,
            pub tp: *mut std::ffi::c_void,
        }
        impl Default for __anon_Foo_2 {
            fn default() -> Self {
                Self {
                    name: std::ptr::null_mut(),
                    tp: std::ptr::null_mut(),
                }
            }
        }
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Bar {
            pub __anon_Bar_1: __anon_Bar_1,
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_Bar_1 {
            pub __anon_Bar_2: __anon_Bar_2,
            pub baz: Baz,
        }
        impl std::fmt::Debug for __anon_Bar_1 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_Bar_1 {
            fn default() -> Self {
                Self {
                    __anon_Bar_2: __anon_Bar_2::default(),
                }
            }
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_Bar_2 {
            pub name: *mut i8,
            pub trp: *mut std::ffi::c_void,
        }
        impl Default for __anon_Bar_2 {
            fn default() -> Self {
                Self {
                    name: std::ptr::null_mut(),
                    trp: std::ptr::null_mut(),
                }
            }
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Baz {
            pub name: *mut i8,
            pub trp: *mut std::ffi::c_void,
        }
        impl Default for Baz {
            fn default() -> Self {
                Self {
                    name: std::ptr::null_mut(),
                    trp: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_anon_enum() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        typedef enum {
            FOO = 1,
        } test_t;
        struct Foo {
            test_t test;
        };
        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub test: __anon_Foo_1,
        }
        #[derive(Debug, Copy, Clone, Eq, PartialEq)]
        #[repr(transparent)]
        pub struct __anon_Foo_1(pub u32);
        #[allow(non_upper_case_globals)]
        impl __anon_Foo_1 {
            pub const FOO: __anon_Foo_1 = __anon_Foo_1(1);
        }
        impl Default for __anon_Foo_1 {
            fn default() -> Self { __anon_Foo_1::FOO }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find the struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_int_encodings() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        struct Foo {
            s32 a;
            u16 b;
            s16 c;
            bool d;
            char e;
        };
        struct Foo foo;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct Foo {
            pub a: i32,
            pub b: u16,
            pub c: i16,
            pub d: std::mem::MaybeUninit<bool>,
            pub e: i8,
        }
        impl Default for Foo {
            fn default() -> Self {
                Self {
                    a: i32::default(),
                    b: u16::default(),
                    c: i16::default(),
                    d: std::mem::MaybeUninit::new(bool::default()),
                    e: i8::default(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find the struct
    let struct_foo = find_type_in_btf!(btf, types::Struct<'_>, "Foo");

    assert_definition(&btf, &struct_foo, expected_output);
}

#[test]
fn test_btf_dump_definition_unnamed_union() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>

        // re-typed 'struct bpf_sock_tuple tup' from vmlinux as of kernel 5.15
        // with a little bit added for additional complexity testing
        struct bpf_sock_tuple_5_15 {
            union {
                struct {
                    __be32 saddr;
                    __be32 daddr;
                    __be16 sport;
                    __be16 dport;
                } ipv4;
                struct {
                    __be32 saddr[4];
                    __be32 daddr[4];
                    __be16 sport;
                    __be16 dport;
                } ipv6;
            };
        
            union {
                int a;
                char *b;
            };
        };
        struct bpf_sock_tuple_5_15 tup;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct bpf_sock_tuple_5_15 {
            pub __anon_bpf_sock_tuple_5_15_1: __anon_bpf_sock_tuple_5_15_1,
            pub __pad_36: [u8; 4],
            pub __anon_bpf_sock_tuple_5_15_2: __anon_bpf_sock_tuple_5_15_2,
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_bpf_sock_tuple_5_15_1 {
            pub ipv4: __anon_bpf_sock_tuple_5_15_3,
            pub ipv6: __anon_bpf_sock_tuple_5_15_4,
        }
        impl std::fmt::Debug for __anon_bpf_sock_tuple_5_15_1 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_bpf_sock_tuple_5_15_1 {
            fn default() -> Self {
                Self {
                    ipv4: __anon_bpf_sock_tuple_5_15_3::default(),
                }
            }
        }
        #[derive(Copy, Clone)]
        #[repr(C)]
        pub union __anon_bpf_sock_tuple_5_15_2 {
            pub a: i32,
            pub b: *mut i8,
        }
        impl std::fmt::Debug for __anon_bpf_sock_tuple_5_15_2 {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(f, "(???)")
            }
        }
        impl Default for __anon_bpf_sock_tuple_5_15_2 {
            fn default() -> Self {
                Self {
                    a: i32::default(),
                }
            }
        }
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_bpf_sock_tuple_5_15_3 {
            pub saddr: u32,
            pub daddr: u32,
            pub sport: u16,
            pub dport: u16,
        }
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct __anon_bpf_sock_tuple_5_15_4 {
            pub saddr: [u32; 4],
            pub daddr: [u32; 4],
            pub sport: u16,
            pub dport: u16,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find the struct
    let struct_bpf_sock_tuple = find_type_in_btf!(btf, types::Struct<'_>, "bpf_sock_tuple_5_15");

    assert_definition(&btf, &struct_bpf_sock_tuple, expected_output);
}

#[test]
fn test_btf_dump_definition_empty_union() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"

        struct struct_with_empty_union {
            int member;
            union {};
        };
        struct struct_with_empty_union s;
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Default, Copy, Clone)]
        #[repr(C)]
        pub struct struct_with_empty_union {
            pub member: i32,
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    // Find the struct
    let struct_with_empty_union =
        find_type_in_btf!(btf, types::Struct<'_>, "struct_with_empty_union");

    assert_definition(&btf, &struct_with_empty_union, expected_output);
}

#[test]
fn test_btf_dump_definition_struct_ops_mixed() {
    let prog_text = indoc! {r#"
        #include "vmlinux.h"
        #include <bpf/bpf_helpers.h>
        #include <bpf/bpf_tracing.h>

        SEC("struct_ops/test_1")
        int BPF_PROG(test_1, struct bpf_dummy_ops_state *state)
        {
            return 0;
        }

        SEC(".struct_ops")
        struct bpf_dummy_ops dummy_1 = {
            .test_1 = (void *)test_1,
        };

        SEC(".struct_ops.link")
        struct bpf_dummy_ops dummy_2 = {
            .test_1 = (void *)test_1,
        };
    "#};

    let expected_output = indoc! {r#"
        #[derive(Debug, Clone)]
        #[repr(C)]
        pub struct StructOps {
            pub dummy_1: *mut types::bpf_dummy_ops,
            pub dummy_2: *mut types::bpf_dummy_ops,
        }

        impl StructOps {

            pub fn dummy_1(&self) -> &types::bpf_dummy_ops {
                // SAFETY: The library ensures that the member is pointing to
                //         valid data.
                unsafe { self.dummy_1.as_ref() }.unwrap()
            }

            pub fn dummy_1_mut(&mut self) -> &mut types::bpf_dummy_ops {
                // SAFETY: The library ensures that the member is pointing to
                //         valid data.
                unsafe { self.dummy_1.as_mut() }.unwrap()
            }

            pub fn dummy_2(&self) -> &types::bpf_dummy_ops {
                // SAFETY: The library ensures that the member is pointing to
                //         valid data.
                unsafe { self.dummy_2.as_ref() }.unwrap()
            }

            pub fn dummy_2_mut(&mut self) -> &mut types::bpf_dummy_ops {
                // SAFETY: The library ensures that the member is pointing to
                //         valid data.
                unsafe { self.dummy_2.as_mut() }.unwrap()
            }
        }
        #[derive(Debug, Copy, Clone)]
        #[repr(C)]
        pub struct bpf_dummy_ops {
            pub test_1: *mut libbpf_rs::libbpf_sys::bpf_program,
            pub test_2: *mut libbpf_rs::libbpf_sys::bpf_program,
            pub test_sleepable: *mut libbpf_rs::libbpf_sys::bpf_program,
        }
        impl Default for bpf_dummy_ops {
            fn default() -> Self {
                Self {
                    test_1: std::ptr::null_mut(),
                    test_2: std::ptr::null_mut(),
                    test_sleepable: std::ptr::null_mut(),
                }
            }
        }
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);
    let mut processed = HashSet::new();
    let mut def = String::new();
    let ops = GenStructOps::new(&btf).unwrap();
    let () = ops.gen_struct_ops_def(&mut def).unwrap();
    let () = ops.gen_dependent_types(&mut processed, &mut def).unwrap();

    assert_output(&def, expected_output);
}

#[test]
fn test_btf_dump_float() {
    let prog_text = indoc! {r#"
        float f = 2.16;
        double d = 12.15;
    "#};

    let mmap = build_btf_mmap(prog_text);
    let btf = btf_from_mmap(&mmap);

    let f = find_type_in_btf!(btf, Var, "f");
    let d = find_type_in_btf!(btf, Var, "d");

    assert_eq!(
        "f32",
        btf.type_declaration(f).expect("Failed to generate f decl")
    );
    assert_eq!(
        "f64",
        btf.type_declaration(d).expect("Failed to generate d decl")
    );
}
