mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-07-18 00:45:07 +00:00
Add infrastructure to add integration tests with podman
This commit is contained in:
parent
a1667bf4ba
commit
3283aa0dc2
10 changed files with 294 additions and 57 deletions
|
|
@ -2,12 +2,25 @@
|
|||
|
||||
## Integration tests
|
||||
|
||||
### With bubblewrap
|
||||
|
||||
Install bubblewrap:
|
||||
`apt-get install bubblewrap`.
|
||||
|
||||
Run with `cargo test -p vuinputd-tests --features "requires-privileges requires-uinput requires-bwrap"`.
|
||||
|
||||
### With podman
|
||||
|
||||
Install podman:
|
||||
`apt-get install podman`.
|
||||
|
||||
Create test container for podman
|
||||
```
|
||||
cargo build -p vuinputd-tests
|
||||
podman build -t vuinputd-tests -f vuinputd-tests/podman/Containerfile .
|
||||
```
|
||||
|
||||
Run with `cargo test -p vuinputd-tests --features "requires-privileges requires-uinput requires-podman"`.
|
||||
|
||||
## Performance tests
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ name = "vuinputd-tests"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "test-ipc"
|
||||
|
||||
[[bin]]
|
||||
name = "test-keyboard"
|
||||
|
||||
[[bin]]
|
||||
name = "bwrap-ipc"
|
||||
name = "test-ok"
|
||||
|
||||
[dependencies]
|
||||
uinput-ioctls = { path = "../uinput-ioctls" }
|
||||
|
|
@ -22,4 +25,5 @@ serde_json = "1.0"
|
|||
[features]
|
||||
requires-privileges = []
|
||||
requires-uinput = []
|
||||
requires-bwrap = []
|
||||
requires-bwrap = []
|
||||
requires-podman = []
|
||||
8
vuinputd-tests/podman/Containerfile
Normal file
8
vuinputd-tests/podman/Containerfile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Build from project root
|
||||
# > cargo build -p vuinputd-tests
|
||||
# > podman build -t vuinputd-tests -f vuinputd-tests/podman/Containerfile .
|
||||
|
||||
FROM ubuntu:24.04
|
||||
COPY target/debug/test-ipc /test-ipc
|
||||
COPY target/debug/test-keyboard /test-keyboard
|
||||
COPY target/debug/test-ok /test-ok
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
use core::panic;
|
||||
use std::time::Duration;
|
||||
|
||||
use vuinputd_tests::bwrap::SandboxChildIpc;
|
||||
use vuinputd_tests::ipc::SandboxChildIpc;
|
||||
|
||||
fn main() {
|
||||
println!("starting bwrap-ipc");
|
||||
println!("starting test-ipc");
|
||||
let ipc = unsafe { SandboxChildIpc::from_fd() };
|
||||
|
||||
let incoming = ipc
|
||||
7
vuinputd-tests/src/bin/test-ok.rs
Normal file
7
vuinputd-tests/src/bin/test-ok.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
fn main() {
|
||||
println!("test-ok");
|
||||
}
|
||||
|
|
@ -3,16 +3,17 @@
|
|||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use std::io;
|
||||
use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd};
|
||||
use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd};
|
||||
use std::os::unix::net::UnixDatagram;
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::{Command, Output};
|
||||
use std::time::Duration;
|
||||
|
||||
use nix::errno::Errno;
|
||||
use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
|
||||
use nix::unistd::close;
|
||||
|
||||
use crate::ipc::{SandboxChildIpc, SandboxIpc};
|
||||
|
||||
/// Check if bubblewrap is available.
|
||||
pub fn bwrap_available() -> bool {
|
||||
Command::new("bwrap")
|
||||
|
|
@ -22,56 +23,6 @@ pub fn bwrap_available() -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// IPC handle kept by the parent.
|
||||
pub struct SandboxIpc {
|
||||
sock: UnixDatagram,
|
||||
}
|
||||
|
||||
impl SandboxIpc {
|
||||
pub fn recv(&self, read_timeout: Option<Duration>) -> io::Result<Vec<u8>> {
|
||||
let mut buf = vec![0u8; 4096];
|
||||
self.sock.set_read_timeout(read_timeout)?;
|
||||
let n = self.sock.recv(&mut buf)?;
|
||||
buf.truncate(n);
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub fn send(&self, data: &[u8]) -> io::Result<()> {
|
||||
self.sock.send(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// IPC handle inside the container.
|
||||
pub struct SandboxChildIpc {
|
||||
sock: UnixDatagram,
|
||||
}
|
||||
|
||||
impl SandboxChildIpc {
|
||||
/// FD number is fixed and known.
|
||||
pub const FD: RawFd = 3;
|
||||
|
||||
/// # Safety
|
||||
/// Must only be called once in the child.
|
||||
pub unsafe fn from_fd() -> Self {
|
||||
let sock = UnixDatagram::from_raw_fd(Self::FD);
|
||||
Self { sock }
|
||||
}
|
||||
|
||||
pub fn send(&self, data: &[u8]) -> io::Result<()> {
|
||||
self.sock.send(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recv(&self, read_timeout: Option<Duration>) -> io::Result<Vec<u8>> {
|
||||
let mut buf = vec![0u8; 4096];
|
||||
self.sock.set_read_timeout(read_timeout)?;
|
||||
let n = self.sock.recv(&mut buf)?;
|
||||
buf.truncate(n);
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for bubblewrap invocations.
|
||||
#[derive(Default)]
|
||||
pub struct BwrapBuilder {
|
||||
|
|
|
|||
62
vuinputd-tests/src/ipc.rs
Normal file
62
vuinputd-tests/src/ipc.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use std::{
|
||||
io,
|
||||
os::{
|
||||
fd::{FromRawFd, RawFd},
|
||||
unix::net::UnixDatagram,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// IPC handle kept by the parent.
|
||||
pub struct SandboxIpc {
|
||||
pub sock: UnixDatagram,
|
||||
}
|
||||
|
||||
impl SandboxIpc {
|
||||
pub fn recv(&self, read_timeout: Option<Duration>) -> io::Result<Vec<u8>> {
|
||||
let mut buf = vec![0u8; 4096];
|
||||
self.sock.set_read_timeout(read_timeout)?;
|
||||
let n = self.sock.recv(&mut buf)?;
|
||||
buf.truncate(n);
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub fn send(&self, data: &[u8]) -> io::Result<()> {
|
||||
self.sock.send(data)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// IPC handle inside the container.
|
||||
pub struct SandboxChildIpc {
|
||||
sock: UnixDatagram,
|
||||
}
|
||||
|
||||
impl SandboxChildIpc {
|
||||
/// FD number is fixed and known.
|
||||
pub const FD: RawFd = 3;
|
||||
|
||||
/// # Safety
|
||||
/// Must only be called once in the child.
|
||||
pub unsafe fn from_fd() -> Self {
|
||||
let sock = UnixDatagram::from_raw_fd(Self::FD);
|
||||
Self { sock }
|
||||
}
|
||||
|
||||
pub fn send(&self, data: &[u8]) -> io::Result<()> {
|
||||
self.sock.send(data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recv(&self, read_timeout: Option<Duration>) -> io::Result<Vec<u8>> {
|
||||
let mut buf = vec![0u8; 4096];
|
||||
self.sock.set_read_timeout(read_timeout)?;
|
||||
let n = self.sock.recv(&mut buf)?;
|
||||
buf.truncate(n);
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,5 +3,7 @@
|
|||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
pub mod bwrap;
|
||||
pub mod ipc;
|
||||
pub mod podman;
|
||||
pub mod run_vuinputd;
|
||||
pub mod test_log;
|
||||
|
|
|
|||
190
vuinputd-tests/src/podman.rs
Normal file
190
vuinputd-tests/src/podman.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use nix::errno::Errno;
|
||||
use nix::sys::socket::{AddressFamily, SockFlag, SockType};
|
||||
use nix::unistd::close;
|
||||
use std::io;
|
||||
use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd};
|
||||
use std::os::unix::net::UnixDatagram;
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::{Command, Output};
|
||||
|
||||
use crate::ipc::{SandboxChildIpc, SandboxIpc};
|
||||
|
||||
/// Check if podman is available.
|
||||
pub fn podman_available() -> bool {
|
||||
Command::new("podman")
|
||||
.arg("--version")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Builder for podman run invocations.
|
||||
#[derive(Default)]
|
||||
pub struct PodmanBuilder {
|
||||
args: Vec<String>,
|
||||
ipc_child_fd: Option<OwnedFd>,
|
||||
}
|
||||
|
||||
impl PodmanBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// `podman run`
|
||||
fn run_cmd(mut self) -> Self {
|
||||
self.args.push("run".into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rm(mut self) -> Self {
|
||||
self.args.push("--rm".into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn detach(mut self) -> Self {
|
||||
self.args.push("--detach".into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn name(mut self, name: &str) -> Self {
|
||||
self.args.push("--name".into());
|
||||
self.args.push(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tty(mut self) -> Self {
|
||||
self.args.push("--tty".into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn interactive(mut self) -> Self {
|
||||
self.args.push("--interactive".into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn device(mut self, spec: &str) -> Self {
|
||||
self.args.push("--device".into());
|
||||
self.args.push(spec.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn volume(mut self, spec: &str) -> Self {
|
||||
self.args.push("-v".into());
|
||||
self.args.push(spec.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn publish(mut self, spec: &str) -> Self {
|
||||
self.args.push("--publish".into());
|
||||
self.args.push(spec.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn env(mut self, key: &str, value: &str) -> Self {
|
||||
self.args.push("-e".into());
|
||||
self.args.push(format!("{key}={value}"));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn group_add(mut self, group: u32) -> Self {
|
||||
self.args.push("--group-add".into());
|
||||
self.args.push(group.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn security_opt(mut self, opt: &str) -> Self {
|
||||
self.args.push("--security-opt".into());
|
||||
self.args.push(opt.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable bidirectional IPC using a Unix seqpacket socketpair.
|
||||
pub fn with_ipc(mut self) -> io::Result<(Self, SandboxIpc)> {
|
||||
let (parent, child) = nix::sys::socket::socketpair(
|
||||
AddressFamily::Unix,
|
||||
SockType::SeqPacket,
|
||||
None,
|
||||
SockFlag::empty(),
|
||||
)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
// Parent side
|
||||
let parent_sock = unsafe { UnixDatagram::from_raw_fd(parent.into_raw_fd()) };
|
||||
|
||||
// Child side must become FD 3 inside container
|
||||
self.ipc_child_fd = Some(child);
|
||||
|
||||
Ok((self, SandboxIpc { sock: parent_sock }))
|
||||
}
|
||||
|
||||
/// Final image reference
|
||||
pub fn image(mut self, image: &str) -> Self {
|
||||
self.args.push(image.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Optional command override inside the container
|
||||
pub fn command(mut self, cmd: &[&str]) -> Self {
|
||||
self.args.extend(cmd.iter().map(|s| s.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> io::Result<Output> {
|
||||
println!("Arguments for podman: {:?}", &self.args);
|
||||
|
||||
let mut cmd = Command::new("podman");
|
||||
|
||||
if let Some(fd) = self.ipc_child_fd.take() {
|
||||
// give up ownership of ipc_child_fd in host process.
|
||||
let fd = fd.into_raw_fd();
|
||||
|
||||
// Move child FD to 3. Note that the FD 3 needs to be linked at the
|
||||
// beginning of the child program.
|
||||
unsafe {
|
||||
cmd.pre_exec(move || {
|
||||
let res = libc::dup2(fd, SandboxChildIpc::FD);
|
||||
Errno::result(res)
|
||||
.map(drop)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
close(fd).ok();
|
||||
Ok(())
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
cmd.args(&self.args).output()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "requires-podman")]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn podman_builder_smoke() {
|
||||
if !podman_available() {
|
||||
panic!("podman not available");
|
||||
}
|
||||
|
||||
let out = PodmanBuilder::new()
|
||||
.run_cmd()
|
||||
.rm()
|
||||
//.detach()
|
||||
.name(&format!("vuinputd-podman-tests"))
|
||||
.image("localhost/vuinputd-tests:latest")
|
||||
.command(&["/test-ok"])
|
||||
.run()
|
||||
.unwrap();
|
||||
|
||||
println!("Output");
|
||||
println!("stdout: {}", str::from_utf8(&out.stdout).unwrap());
|
||||
println!("stderr: {}", str::from_utf8(&out.stderr).unwrap());
|
||||
|
||||
assert!(out.status.success());
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ fn test_bwrap_simple() {
|
|||
#[cfg(all(feature = "requires-privileges", feature = "requires-bwrap"))]
|
||||
#[test]
|
||||
fn test_bwrap_ipc() {
|
||||
let bwrap_ipc = env!("CARGO_BIN_EXE_bwrap-ipc");
|
||||
let bwrap_ipc = env!("CARGO_BIN_EXE_test-ipc");
|
||||
|
||||
let (builder, ipc) = bwrap::BwrapBuilder::new()
|
||||
.unshare_all()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue