use super::ops::{CgroupDef, Op};
use crate::test_support::Payload;
#[derive(Debug, Clone, Default)]
pub struct Backdrop {
pub cgroups: Vec<CgroupDef>,
pub payloads: Vec<&'static Payload>,
pub ops: Vec<Op>,
}
impl Backdrop {
#[must_use = "dropping a Backdrop discards the scenario layout"]
pub const fn new() -> Self {
Backdrop {
cgroups: Vec::new(),
payloads: Vec::new(),
ops: Vec::new(),
}
}
#[must_use = "builder methods consume self; bind the result"]
pub fn push_cgroup(mut self, def: CgroupDef) -> Self {
self.cgroups.push(def);
self
}
#[must_use = "builder methods consume self; bind the result"]
pub fn extend_cgroups<I: IntoIterator<Item = CgroupDef>>(mut self, defs: I) -> Self {
self.cgroups.extend(defs);
self
}
#[must_use = "builder methods return Self; bind the result"]
pub fn from_cgroups<I: IntoIterator<Item = CgroupDef>>(defs: I) -> Self {
Self::new().extend_cgroups(defs)
}
#[must_use = "builder methods consume self; bind the result"]
pub fn push_payload(mut self, payload: &'static Payload) -> Self {
self.payloads.push(payload);
self
}
#[must_use = "builder methods consume self; bind the result"]
pub fn extend_payloads<I: IntoIterator<Item = &'static Payload>>(
mut self,
payloads: I,
) -> Self {
self.payloads.extend(payloads);
self
}
#[must_use = "builder methods consume self; bind the result"]
pub fn push_op(mut self, op: Op) -> Self {
self.ops.push(op);
self
}
#[must_use = "builder methods consume self; bind the result"]
pub fn extend_ops<I: IntoIterator<Item = Op>>(mut self, ops: I) -> Self {
self.ops.extend(ops);
self
}
pub fn is_empty(&self) -> bool {
self.cgroups.is_empty() && self.payloads.is_empty() && self.ops.is_empty()
}
}
impl FromIterator<CgroupDef> for Backdrop {
fn from_iter<I: IntoIterator<Item = CgroupDef>>(defs: I) -> Self {
Self::from_cgroups(defs)
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_PAYLOAD: Payload = Payload::binary("test_bin", "/bin/true");
const TEST_PAYLOAD_2: Payload = Payload::binary("test_bin_2", "/bin/false");
const TEST_PAYLOAD_3: Payload = Payload::binary("test_bin_3", "/bin/false");
const TEST_PAYLOAD_4: Payload = Payload::binary("test_bin_4", "/bin/false");
const TEST_PAYLOAD_5: Payload = Payload::binary("test_bin_5", "/bin/false");
const TEST_PAYLOAD_6: Payload = Payload::binary("test_bin_6", "/bin/false");
const TEST_PAYLOAD_7: Payload = Payload::binary("test_bin_7", "/bin/false");
#[test]
fn empty_backdrop_has_no_entities() {
let b = Backdrop::new();
assert!(b.cgroups.is_empty());
assert!(b.payloads.is_empty());
assert!(b.ops.is_empty());
assert!(b.is_empty());
}
#[test]
fn new_returns_empty() {
let b = Backdrop::new();
assert!(b.is_empty());
}
#[test]
fn push_cgroup_populates_cgroups() {
let b = Backdrop::new().push_cgroup(CgroupDef::named("cg0"));
assert_eq!(b.cgroups.len(), 1);
assert_eq!(b.cgroups[0].name.as_ref(), "cg0");
assert!(!b.is_empty());
}
#[test]
fn extend_cgroups_preserves_input_order() {
let b = Backdrop::new().extend_cgroups([
CgroupDef::named("cg0"),
CgroupDef::named("cg1"),
CgroupDef::named("cg2"),
]);
assert_eq!(b.cgroups.len(), 3);
assert_eq!(b.cgroups[0].name.as_ref(), "cg0");
assert_eq!(b.cgroups[1].name.as_ref(), "cg1");
assert_eq!(b.cgroups[2].name.as_ref(), "cg2");
}
#[test]
fn extend_cgroups_preserves_declaration_order_for_many_entries() {
const NAMES: [&str; 7] = ["cg5", "cg0", "cg3", "cg6", "cg1", "cg4", "cg2"];
let b = Backdrop::new().extend_cgroups(NAMES.map(CgroupDef::named));
assert_eq!(b.cgroups.len(), NAMES.len());
for (i, expected) in NAMES.iter().enumerate() {
assert_eq!(
b.cgroups[i].name.as_ref(),
*expected,
"index {i} should be {expected}"
);
}
}
#[test]
fn push_payload_populates_payloads() {
let b = Backdrop::new().push_payload(&TEST_PAYLOAD);
assert_eq!(b.payloads.len(), 1);
assert_eq!(b.payloads[0].name, "test_bin");
assert!(!b.is_empty());
}
#[test]
fn extend_payloads_preserves_order() {
let b = Backdrop::new().extend_payloads([&TEST_PAYLOAD, &TEST_PAYLOAD_2]);
assert_eq!(b.payloads.len(), 2);
assert_eq!(b.payloads[0].name, "test_bin");
assert_eq!(b.payloads[1].name, "test_bin_2");
}
#[test]
fn extend_payloads_preserves_declaration_order_for_many_entries() {
let inputs = [
&TEST_PAYLOAD_5,
&TEST_PAYLOAD,
&TEST_PAYLOAD_3,
&TEST_PAYLOAD_7,
&TEST_PAYLOAD_2,
&TEST_PAYLOAD_6,
&TEST_PAYLOAD_4,
];
let expected = [
"test_bin_5",
"test_bin",
"test_bin_3",
"test_bin_7",
"test_bin_2",
"test_bin_6",
"test_bin_4",
];
let b = Backdrop::new().extend_payloads(inputs);
assert_eq!(b.payloads.len(), expected.len());
for (i, name) in expected.iter().enumerate() {
assert_eq!(b.payloads[i].name, *name, "index {i} should be {name}");
}
}
#[test]
fn push_then_extend_preserves_order() {
let b = Backdrop::new()
.push_payload(&TEST_PAYLOAD)
.extend_payloads([&TEST_PAYLOAD_2]);
assert_eq!(b.payloads.len(), 2);
assert_eq!(b.payloads[0].name, "test_bin");
assert_eq!(b.payloads[1].name, "test_bin_2");
}
#[test]
fn chain_builds_in_order() {
let b = Backdrop::new()
.push_cgroup(CgroupDef::named("cg_a"))
.push_payload(&TEST_PAYLOAD)
.push_cgroup(CgroupDef::named("cg_b"));
assert_eq!(b.cgroups.len(), 2);
assert_eq!(b.cgroups[0].name.as_ref(), "cg_a");
assert_eq!(b.cgroups[1].name.as_ref(), "cg_b");
assert_eq!(b.payloads.len(), 1);
assert!(!b.is_empty());
}
#[test]
fn default_impl_matches_new() {
let d: Backdrop = Default::default();
assert!(d.is_empty());
assert_eq!(d.cgroups.len(), Backdrop::new().cgroups.len());
assert_eq!(d.payloads.len(), Backdrop::new().payloads.len());
assert_eq!(d.ops.len(), Backdrop::new().ops.len());
}
const _BACKDROP_NEW_IS_CONST_EVALUABLE: Backdrop = Backdrop::new();
#[test]
fn push_op_populates_ops() {
let b = Backdrop::new().push_op(Op::add_cgroup("empty_target"));
assert_eq!(b.ops.len(), 1);
assert!(matches!(&b.ops[0], Op::AddCgroup { name } if name.as_ref() == "empty_target"));
assert!(!b.is_empty());
}
#[test]
fn extend_ops_preserves_order() {
let b =
Backdrop::new().extend_ops(vec![Op::add_cgroup("cg_1"), Op::add_cgroup("cg_1/sub")]);
assert_eq!(b.ops.len(), 2);
assert!(matches!(&b.ops[0], Op::AddCgroup { name } if name.as_ref() == "cg_1"));
assert!(matches!(&b.ops[1], Op::AddCgroup { name } if name.as_ref() == "cg_1/sub"));
}
#[test]
fn chain_push_op_interleaves_with_other_builders() {
let b = Backdrop::new()
.push_cgroup(CgroupDef::named("cg_workers"))
.push_op(Op::add_cgroup("cg_empty"))
.push_payload(&TEST_PAYLOAD);
assert_eq!(b.cgroups.len(), 1);
assert_eq!(b.ops.len(), 1);
assert_eq!(b.payloads.len(), 1);
assert!(!b.is_empty());
}
#[test]
fn clone_is_independent_per_field() {
let original = Backdrop::new();
let mut cloned = original.clone();
cloned.cgroups.push(CgroupDef::named("cg_added_to_clone"));
cloned.payloads.push(&TEST_PAYLOAD);
cloned.ops.push(Op::add_cgroup("cg_op_on_clone"));
assert!(
original.cgroups.is_empty(),
"original.cgroups must stay empty after clone mutation"
);
assert!(
original.payloads.is_empty(),
"original.payloads must stay empty after clone mutation"
);
assert!(
original.ops.is_empty(),
"original.ops must stay empty after clone mutation"
);
assert_eq!(cloned.cgroups.len(), 1, "clone cgroups: unexpected count");
assert_eq!(
cloned.cgroups[0].name.as_ref(),
"cg_added_to_clone",
"clone cgroups: pushed value not present at index 0"
);
assert_eq!(cloned.payloads.len(), 1, "clone payloads: unexpected count");
assert!(
std::ptr::eq(cloned.payloads[0], &TEST_PAYLOAD),
"clone payloads: pushed pointer not present at index 0"
);
assert_eq!(cloned.ops.len(), 1, "clone ops: unexpected count");
assert!(
matches!(&cloned.ops[0], Op::AddCgroup { name } if name.as_ref() == "cg_op_on_clone"),
"clone ops: pushed Op not present at index 0"
);
}
#[test]
fn from_cgroups_preserves_order_leaves_other_fields_empty() {
let b = Backdrop::from_cgroups([
CgroupDef::named("cg_a"),
CgroupDef::named("cg_b"),
CgroupDef::named("cg_c"),
]);
assert_eq!(b.cgroups.len(), 3);
assert_eq!(b.cgroups[0].name.as_ref(), "cg_a");
assert_eq!(b.cgroups[1].name.as_ref(), "cg_b");
assert_eq!(b.cgroups[2].name.as_ref(), "cg_c");
assert!(b.payloads.is_empty());
assert!(b.ops.is_empty());
}
#[test]
fn from_iterator_matches_from_cgroups() {
let defs = vec![CgroupDef::named("cg_0"), CgroupDef::named("cg_1")];
let from_constructor = Backdrop::from_cgroups(defs.clone());
let from_iter: Backdrop = defs.into_iter().collect();
assert_eq!(from_iter.cgroups.len(), from_constructor.cgroups.len());
assert_eq!(
from_iter.cgroups[0].name.as_ref(),
from_constructor.cgroups[0].name.as_ref()
);
assert_eq!(
from_iter.cgroups[1].name.as_ref(),
from_constructor.cgroups[1].name.as_ref()
);
}
#[test]
fn from_cgroups_empty_input_yields_empty_backdrop() {
let b = Backdrop::from_cgroups(std::iter::empty());
assert!(b.is_empty());
}
#[test]
fn default_matches_new() {
let from_new = Backdrop::new();
let from_trait: Backdrop = Default::default();
assert_eq!(
from_trait.cgroups.len(),
from_new.cgroups.len(),
"cgroups Vec drift"
);
assert_eq!(
from_trait.payloads.len(),
from_new.payloads.len(),
"payloads Vec drift"
);
assert_eq!(from_trait.ops.len(), from_new.ops.len(), "ops Vec drift");
assert!(from_new.cgroups.is_empty());
assert!(from_new.payloads.is_empty());
assert!(from_new.ops.is_empty());
}
}