Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support Unit Testing of Userspace Programs #999

Open
OliverGavin opened this issue Jul 25, 2024 · 0 comments
Open

Support Unit Testing of Userspace Programs #999

OliverGavin opened this issue Jul 25, 2024 · 0 comments

Comments

@OliverGavin
Copy link

I have found myself writing mocks for different map types but it is a bit painful; when trying to use new map types I get similar type conflicts, etc until I realize I need to mock more things, IDE is no longer helpful for docs and autocomplete of methods not yet implemented by my mocks, etc.

Maybe there is a better way like creating my own wrapper types I can more easily mock. Ultimately I still end up having to write my own mock implementations and I think this is likely a problem faced by others so I wanted to open this issue to discuss it.

Example mock usage:

#[cfg(any(not(test), rust_analyzer))]
type PerCpuArray<V> = aya::maps::PerCpuArray<aya::maps::MapData, V>;

#[cfg(all(test, not(rust_analyzer)))]
use crate::mocks::aya::{Ebpf, PerCpuArray};

Example mock (quick and dirty):

#[derive(Clone)]
pub struct PerCpuArray<V: Pod> {
    inner: Arc<Mutex<Vec<Vec<V>>>>,
    _v: std::marker::PhantomData<V>,
}

impl<V: Pod> PerCpuArray<V> {
    pub(crate) fn new(len: usize, val: V) -> Self {
        let arr = vec![val.clone(); nr_cpus().unwrap()];
        PerCpuArray {
            inner: Arc::new(Mutex::new(vec![arr.clone(); len])),
            _v: core::marker::PhantomData,
        }
    }
}

impl TryFrom<Map> for PerCpuArray<u64> {
    type Error = MapError;

    fn try_from(_map: Map) -> Result<PerCpuArray<u64>, MapError> {
        Ok(PerCpuArray::new(1, 0u64))
    }
}

impl<V: Pod> PerCpuArray<V> {
    pub fn get(&self, index: &u32, _flags: u64) -> Result<PerCpuValues<V>, MapError> {
        let guard = self.inner.lock().unwrap();
        let values = guard.get(*index as usize).ok_or(MapError::OutOfBounds {
            index: *index,
            max_entries: guard.len() as u32,
        })?;
        PerCpuValues::try_from(values.clone()).map_err(|_| MapError::KeyNotFound)
    }

    pub fn set(&mut self, index: u32, values: PerCpuValues<V>, _flags: u64) -> Result<(), MapError> {
        let arr = (0..nr_cpus().unwrap())
            .map(|i| values.get(i).unwrap().clone())
            .collect::<Vec<V>>();
        let mut guard = self.inner.lock().unwrap();
        guard[index as usize] = arr;
        Ok(())
    }
}

Similar to #36


One thing I did notice was that tests inside aya leverage test_utils:

aya/aya/src/maps/mod.rs

Lines 1040 to 1076 in a167554

#[cfg(test)]
mod test_utils {
use crate::{
bpf_map_def,
generated::{bpf_cmd, bpf_map_type},
maps::MapData,
obj::{self, maps::LegacyMap, EbpfSectionKind},
sys::{override_syscall, Syscall},
};
pub(super) fn new_map(obj: obj::Map) -> MapData {
override_syscall(|call| match call {
Syscall::Ebpf {
cmd: bpf_cmd::BPF_MAP_CREATE,
..
} => Ok(crate::MockableFd::mock_signed_fd().into()),
call => panic!("unexpected syscall {:?}", call),
});
MapData::create(obj, "foo", None).unwrap()
}
pub(super) fn new_obj_map<K>(map_type: bpf_map_type) -> obj::Map {
obj::Map::Legacy(LegacyMap {
def: bpf_map_def {
map_type: map_type as u32,
key_size: std::mem::size_of::<K>() as u32,
value_size: 4,
max_entries: 1024,
..Default::default()
},
section_index: 0,
section_kind: EbpfSectionKind::Maps,
data: Vec::new(),
symbol_index: None,
})
}
}

which uses override_syscall:

pub(crate) fn override_syscall(call: unsafe fn(Syscall<'_>) -> SysResult<i64>) {

Which is used as a mock here:

return TEST_SYSCALL.with(|test_impl| unsafe { test_impl.borrow()(call) });

However, this is only usable internally to the crate and when compiled to a test target. It would be really cool to expose something similar to make it easier to test for users of aya.

There are a few different ways around it but I am thinking the simplest and least intrusive could be a feature that users can enable for their tests that allows them to override syscalls. Going one step further, the creation of the maps themselves could be mocked appropriately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant