#![cfg(target_os = "illumos")]
use assert_matches::assert_matches;
use proptest::prelude::BoxedStrategy;
use proptest::prelude::any;
use proptest::proptest;
use proptest::strategy::Strategy;
use scuffle::AddPropertyGroupFlags;
use scuffle::DeletePropertyGroupResult;
use scuffle::EditPropertyGroups;
use scuffle::HasComposedPropertyGroups;
use scuffle::HasDirectPropertyGroups;
use scuffle::PropertyGroupType;
use scuffle::PropertyGroupUpdateResult;
use scuffle::Scf;
use scuffle::TransactionCommitResult;
use scuffle::Value;
use scuffle::ValueKind;
use scuffle::ValueRef;
use scuffle::error::LookupError;
use scuffle::error::SingleValueError;
use scuffle::error::TransactionBuildError;
use scuffle::isolated::IsolatedConfigd;
use std::cell::RefCell;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
#[test]
fn transaction_property_roundtrip() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_new("prop", val.as_value_ref())
.expect("property_new");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"commit should succeed",
);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val]);
}
});
}
#[test]
fn transaction_multi_value_roundtrip() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
let strategy =
proptest::collection::vec(any::<u64>(), 1..=8).prop_map(|counts| {
counts.into_iter().map(Value::Count).collect::<Vec<_>>()
});
proptest!(|(values in strategy)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_new_multiple(
"prop",
ValueKind::Count,
values.iter().map(|v| v.as_value_ref()),
)
.expect("property_new_multiple");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"commit should succeed",
);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, values);
}
});
}
fn strategy_same_kind_values() -> BoxedStrategy<(Value, Value)> {
any::<Value>()
.prop_flat_map(|v1| {
let kind = v1.kind();
let v2_strategy = match kind {
ValueKind::Bool => any::<bool>().prop_map(Value::Bool).boxed(),
ValueKind::Count => any::<u64>().prop_map(Value::Count).boxed(),
ValueKind::Integer => {
any::<i64>().prop_map(Value::Integer).boxed()
}
_ => {
any::<Value>()
.prop_filter("same kind", move |v| v.kind() == kind)
.boxed()
}
};
v2_strategy.prop_map(move |v2| (v1.clone(), v2))
})
.boxed()
}
#[test]
fn transaction_property_ensure_overwrites() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(pair in strategy_same_kind_values())| {
let (val1, val2) = pair;
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
{
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_ensure("prop", val1.as_value_ref())
.expect("property_ensure");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"first commit should succeed",
);
}
let updated = pg.update().expect("updated property group");
assert_eq!(updated, PropertyGroupUpdateResult::Updated);
{
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_ensure("prop", val2.as_value_ref())
.expect("property_ensure");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"second commit should succeed",
);
}
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val2]);
}
});
}
#[test]
fn transaction_property_change_overwrites() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(pair in strategy_same_kind_values())| {
let (val1, val2) = pair;
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", val1.as_value_ref())
.expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"initial commit should succeed",
);
}
{
let inst = instance.borrow();
let mut pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_change("prop", val2.as_value_ref())
.expect("property_change");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"change commit should succeed",
);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val2]);
}
});
}
#[test]
fn transaction_snapshot_visibility_after_refresh() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", val.as_value_ref())
.expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"commit should succeed",
);
}
{
let inst = instance.borrow();
let snapshot = inst
.snapshot("running")
.expect("lookup snapshot")
.expect("running snapshot should exist");
let pg = snapshot
.property_group_composed(&pg_name)
.expect("lookup pg composed");
assert!(
pg.is_none(),
"property group {pg_name} should not be visible \
in the running snapshot before refresh",
);
}
{
let mut inst = instance.borrow_mut();
inst.smf_refresh().expect("refresh");
}
{
let inst = instance.borrow();
let snapshot = inst
.snapshot("running")
.expect("lookup snapshot")
.expect("running snapshot should exist");
let pg = snapshot
.property_group_composed(&pg_name)
.expect("lookup pg composed")
.expect(
"property group should be visible in the \
running snapshot after refresh",
);
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val]);
}
});
}
#[test]
fn service_property_composed_visibility() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = RefCell::new(scope.service("test-svc").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut svc = service.borrow_mut();
let mut pg = svc
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group to service");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", val.as_value_ref())
.expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"commit should succeed",
);
}
{
let svc = service.borrow();
let pg = svc
.property_group_direct(&pg_name)
.expect("lookup service pg")
.expect("pg should exist on service");
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val.clone()]);
let inst = svc
.instance("default")
.expect("lookup instance")
.expect("instance should exist");
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup instance direct pg");
assert!(
pg.is_none(),
"service-level pg {pg_name} should not be visible \
via instance direct-attach",
);
let pg = inst
.property_group_composed(&pg_name)
.expect("lookup instance composed pg")
.expect(
"service-level pg should be visible via \
instance composed view",
);
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val.clone()]);
}
{
let svc = service.borrow();
let inst = svc
.instance("default")
.expect("lookup instance")
.expect("instance should exist");
let snapshot = inst
.snapshot("running")
.expect("lookup snapshot")
.expect("running snapshot should exist");
let pg = snapshot
.property_group_composed(&pg_name)
.expect("lookup snapshot pg");
assert!(
pg.is_none(),
"service-level pg {pg_name} should not be visible \
in the running snapshot before refresh",
);
}
{
let svc = service.borrow();
let mut inst = svc
.instance("default")
.expect("lookup instance")
.expect("instance should exist");
inst.smf_refresh().expect("refresh");
}
{
let svc = service.borrow();
let inst = svc
.instance("default")
.expect("lookup instance")
.expect("instance should exist");
let snapshot = inst
.snapshot("running")
.expect("lookup snapshot")
.expect("running snapshot should exist");
let pg = snapshot
.property_group_composed(&pg_name)
.expect("lookup snapshot pg")
.expect(
"service-level pg should be visible in the \
running snapshot after refresh",
);
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val]);
}
});
}
#[test]
fn delete_property_group() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", val.as_value_ref())
.expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"commit should succeed",
);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val]);
}
{
let mut inst = instance.borrow_mut();
let result = inst
.delete_property_group(&pg_name)
.expect("delete property group");
assert_eq!(result, DeletePropertyGroupResult::Deleted);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg after delete");
assert!(
pg.is_none(),
"pg {pg_name} should not exist after deletion",
);
}
{
let mut inst = instance.borrow_mut();
let result = inst
.delete_property_group(&pg_name)
.expect("delete property group again");
assert_eq!(result, DeletePropertyGroupResult::DoesNotExist);
}
});
}
#[test]
fn transaction_commit_out_of_date() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", val.as_value_ref())
.expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"initial commit should succeed",
);
}
{
let inst = instance.borrow();
let mut pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_ensure("prop", val.as_value_ref())
.expect("property_ensure");
{
let inst2 = service
.instance("default")
.expect("lookup instance")
.expect("instance should exist");
let mut pg2 = inst2
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let tx2 = pg2.transaction().expect("create transaction");
let mut tx2 = tx2.start().expect("start transaction");
tx2.property_ensure("prop", val.as_value_ref())
.expect("property_ensure");
let result = tx2.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"concurrent commit should succeed",
);
}
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
let stale_tx = assert_matches!(
result, TransactionCommitResult::OutOfDate(tx) => tx,
"commit should be out of date after concurrent \
modification",
);
assert!(stale_tx.is_empty());
}
});
}
#[test]
fn transaction_invalid_property_name() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let mut instance = service.instance("default").unwrap().unwrap();
let mut pg = instance
.add_property_group(
"pg",
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
let err = tx
.property_new("prop\0bad", ValueRef::Bool(true))
.expect_err("should fail with InvalidName");
assert_matches!(err, TransactionBuildError::InvalidName { .. });
let err = tx
.property_delete("del\0bad")
.expect_err("should fail with InvalidName");
assert_matches!(err, TransactionBuildError::InvalidName { .. });
let err = tx
.property_ensure("ens\0bad", ValueRef::Bool(true))
.expect_err("should fail with InvalidName");
assert_matches!(
err,
TransactionBuildError::ExistenceLookup {
err: LookupError::InvalidName { .. },
..
}
);
let err = tx
.property_change("chg\0bad", ValueRef::Bool(true))
.expect_err("should fail with InvalidName");
assert_matches!(err, TransactionBuildError::InvalidName { .. });
let err = tx
.property_change_type("ct\0bad", ValueRef::Bool(true))
.expect_err("should fail with InvalidName");
assert_matches!(err, TransactionBuildError::InvalidName { .. });
assert!(tx.is_empty());
}
#[test]
fn transaction_type_mismatch() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let mut instance = service.instance("default").unwrap().unwrap();
let mut pg = instance
.add_property_group(
"pg",
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
let err = tx
.property_new_multiple(
"prop",
ValueKind::Count,
std::iter::once(ValueRef::Bool(true)),
)
.expect_err("should fail with TypeMismatch");
assert_matches!(
err,
TransactionBuildError::TypeMismatch {
property_type: ValueKind::Count,
value_type: ValueKind::Bool,
..
}
);
assert!(tx.is_empty());
}
#[test]
fn property_group_update_already_up_to_date() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let mut instance = service.instance("default").unwrap().unwrap();
let mut pg = instance
.add_property_group(
"pg",
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let result = pg.update().expect("update");
assert_eq!(result, PropertyGroupUpdateResult::AlreadyUpToDate);
{
let inst2 = service.instance("default").unwrap().unwrap();
let mut pg2 = inst2
.property_group_direct("pg")
.expect("lookup pg")
.expect("pg should exist");
let tx = pg2.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", ValueRef::Bool(true)).expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
let result = pg.update().expect("update");
assert_eq!(result, PropertyGroupUpdateResult::Updated);
let result = pg.update().expect("update");
assert_eq!(result, PropertyGroupUpdateResult::AlreadyUpToDate);
}
#[test]
fn transaction_property_delete() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_new("prop", val.as_value_ref())
.expect("property_new");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"initial commit should succeed",
);
}
{
let inst = instance.borrow();
let mut pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_delete("prop").expect("property_delete");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"delete commit should succeed",
);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should still exist after property deletion");
let prop = pg.property("prop").expect("lookup property");
assert!(
prop.is_none(),
"property should not exist after deletion",
);
}
});
}
#[test]
fn transaction_property_change_type() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val1: Value, val2: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", val1.as_value_ref())
.expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"initial commit should succeed",
);
}
{
let inst = instance.borrow();
let mut pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_change_type("prop", val2.as_value_ref())
.expect("property_change_type");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"change_type commit should succeed",
);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![val2]);
}
});
}
#[test]
fn single_value_and_errors() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let instance = RefCell::new(service.instance("default").unwrap().unwrap());
let pg_counter = AtomicU32::new(0);
proptest!(|(val: Value)| {
let n = pg_counter.fetch_add(1, Ordering::Relaxed);
let pg_name = format!("pg{n}");
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
&pg_name,
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new("prop", val.as_value_ref())
.expect("property_new");
let result = tx.commit().expect("commit");
assert_matches!(
result, TransactionCommitResult::Success(_),
"commit should succeed",
);
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct(&pg_name)
.expect("lookup pg")
.expect("pg should exist");
let readback = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.single_value()
.expect("single_value");
assert_eq!(readback, val);
}
});
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
"pg_multi",
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new_multiple(
"prop",
ValueKind::Count,
[ValueRef::Count(1), ValueRef::Count(2)],
)
.expect("property_new_multiple");
let result = tx.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct("pg_multi")
.expect("lookup pg")
.expect("pg should exist");
let err = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.single_value()
.expect_err("should fail with MultipleValues");
assert_matches!(err, SingleValueError::MultipleValues { .. });
}
{
let mut inst = instance.borrow_mut();
let mut pg = inst
.add_property_group(
"pg_empty",
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
tx.property_new_multiple("prop", ValueKind::Count, std::iter::empty())
.expect("property_new_multiple with empty values");
let result = tx.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
{
let inst = instance.borrow();
let pg = inst
.property_group_direct("pg_empty")
.expect("lookup pg")
.expect("pg should exist");
let prop =
pg.property("prop").expect("lookup property").expect("prop exists");
let err = prop.single_value().expect_err("should fail with NoValues");
assert_matches!(err, SingleValueError::NoValues { .. });
let mut values = prop.values().expect("create iterator");
assert_matches!(values.next(), None);
}
}
#[test]
fn transaction_reset_and_retry_after_out_of_date() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let mut instance = service.instance("default").unwrap().unwrap();
let mut pg = instance
.add_property_group(
"retrypg",
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
{
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_new("prop", ValueRef::Count(1)).expect("property_new");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
pg.update().expect("update");
{
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_ensure("prop", ValueRef::Count(42))
.expect("property_ensure");
assert!(!tx.is_empty());
{
let inst2 = service
.instance("default")
.expect("lookup instance")
.expect("instance should exist");
let mut pg2 = inst2
.property_group_direct("retrypg")
.expect("lookup pg")
.expect("pg should exist");
let tx2 = pg2.transaction().expect("create transaction");
let mut tx2 = tx2.start().expect("start transaction");
tx2.property_ensure("prop", ValueRef::Count(99))
.expect("property_ensure");
let result = tx2.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
let result = tx.commit().expect("commit");
let stale_tx = assert_matches!(
result,
TransactionCommitResult::OutOfDate(tx) => tx,
"commit should be out of date",
);
assert!(stale_tx.is_empty());
}
pg.update().expect("update after out of date");
{
let tx = pg.transaction().expect("create retry transaction");
let mut tx = tx.start().expect("start retry transaction");
assert!(tx.is_empty());
tx.property_ensure("prop", ValueRef::Count(42))
.expect("property_ensure on retry");
assert!(!tx.is_empty());
let result = tx.commit().expect("retry commit");
assert_matches!(
result,
TransactionCommitResult::Success(_),
"retry commit should succeed",
);
}
{
let inst = service
.instance("default")
.expect("lookup instance")
.expect("instance should exist");
let pg = inst
.property_group_direct("retrypg")
.expect("lookup pg")
.expect("pg should exist");
let readback = pg
.property("prop")
.expect("lookup property")
.expect("property should exist")
.single_value()
.expect("single_value");
assert_eq!(readback, Value::Count(42));
}
}
#[test]
fn transaction_multi_value_ensure_and_change() {
let isolated =
IsolatedConfigd::builder("test-svc").unwrap().build().unwrap();
let scf = Scf::connect_isolated(&isolated).unwrap();
let scope = scf.scope_local().unwrap();
let service = scope.service("test-svc").unwrap().unwrap();
let mut instance = service.instance("default").unwrap().unwrap();
let mut pg = instance
.add_property_group(
"mvpg",
PropertyGroupType::Application,
AddPropertyGroupFlags::Persistent,
)
.expect("add property group");
{
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_ensure_multiple(
"prop",
ValueKind::Count,
[ValueRef::Count(1), ValueRef::Count(2), ValueRef::Count(3)],
)
.expect("property_ensure_multiple");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
{
let inst = service.instance("default").unwrap().unwrap();
let pg_read = inst
.property_group_direct("mvpg")
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg_read
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(
readback,
vec![Value::Count(1), Value::Count(2), Value::Count(3)],
);
}
pg.update().expect("update");
{
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_ensure_multiple(
"prop",
ValueKind::Count,
[ValueRef::Count(4), ValueRef::Count(5)],
)
.expect("property_ensure_multiple overwrite");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
{
let inst = service.instance("default").unwrap().unwrap();
let pg_read = inst
.property_group_direct("mvpg")
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg_read
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![Value::Count(4), Value::Count(5)]);
}
pg.update().expect("update");
{
let tx = pg.transaction().expect("create transaction");
let mut tx = tx.start().expect("start transaction");
assert!(tx.is_empty());
tx.property_change_multiple(
"prop",
ValueKind::Count,
[ValueRef::Count(6)],
)
.expect("property_change_multiple");
assert!(!tx.is_empty());
let result = tx.commit().expect("commit");
assert_matches!(result, TransactionCommitResult::Success(_));
}
{
let inst = service.instance("default").unwrap().unwrap();
let pg_read = inst
.property_group_direct("mvpg")
.expect("lookup pg")
.expect("pg should exist");
let readback: Vec<Value> = pg_read
.property("prop")
.expect("lookup property")
.expect("property should exist")
.values()
.expect("get values")
.collect::<Result<Vec<_>, _>>()
.expect("iterate values");
assert_eq!(readback, vec![Value::Count(6)]);
}
}