use core::cell::Cell; use kaptan_config::{ ConfigManager, ConfigModel, ConfigStore, ConfigValue, ConfigViolation, FieldDescriptor, FieldId, FieldWriteError, KcfgDecodeError, KcfgExporter, KcfgImporter, KcfgUpdate, ReplaceError, ValidationRule, decode_kcfg, encode_edited_kcfg, }; #[derive(Clone, Debug, PartialEq, kaptan_config::Config)] struct Scalars { #[config(id = 1, key = "test.u8", unit = "")] u8_value: u8, #[config(id = 2, key = "test.u16", unit = "")] u16_value: u16, #[config(id = 3, key = "test.u32", unit = "")] u32_value: u32, #[config(id = 4, key = "test.u64", unit = "")] u64_value: u64, #[config(id = 7, key = "test.i32", unit = "")] i32_value: i32, #[config(id = 9, key = "test.f32", unit = "")] f32_value: f32, #[config(id = 10, key = "test.f64", unit = "")] f64_value: f64, #[config(id = 11, key = "test.bool", unit = "")] bool_value: bool, #[config(id = 12, key = "test.finite", unit = "", finite)] finite: f32, #[config(id = 13, key = "test.nonzero", unit = "", nonzero)] nonzero: f64, } impl ConfigModel for Scalars { type Active = Self; type Context = (); const SCHEMA_ID: [u8; 16] = ::SCHEMA_ID; fn descriptor(index: u16) -> Option<&'static FieldDescriptor> { ::descriptor(usize::from(index)) } fn field_count() -> u16 { ::FIELD_COUNT as u16 } fn field_value(&self, id: FieldId) -> Option { ::field_value(self, id) } fn active_field_value(active: &Self::Active, id: FieldId) -> Option { active.field_value(id) } fn set_field(&mut self, id: FieldId, value: ConfigValue) -> Result<(), FieldWriteError> { ::set_field(self, id, value) } fn validate(&self, _: &()) -> Result { ::validate(self)?; Ok(self.clone()) } } fn factory() -> Scalars { Scalars { u8_value: u8::MAX, u16_value: u16::MAX, u32_value: u32::MAX, u64_value: u64::MAX, i32_value: i32::MIN, f32_value: 0.0, f64_value: 0.0, bool_value: true, finite: 1.0, nonzero: 1.0, } } struct Store<'a>(&'a Cell); impl ConfigStore for Store<'_> { type Error = (); fn load(&mut self, _: &Scalars) -> Result, ()> { Ok(None) } fn persist(&mut self, _: &Scalars) -> Result<(), ()> { self.0.set(self.0.get() + 1); Ok(()) } } fn export(current: &Scalars, factory: &Scalars) -> Vec { let exporter = KcfgExporter::new(current, factory, false).unwrap(); let mut bytes = vec![0; exporter.length()]; exporter.read(0, &mut bytes).unwrap(); bytes } fn edit(bytes: &[u8], candidate: &Scalars) -> Vec { let source = decode_kcfg(bytes, &mut [0; kaptan_config::KCFG_FIELD_ID_SCRATCH_BYTES]).unwrap(); let mut edited = vec![0; bytes.len()]; encode_edited_kcfg(&source, false, &mut edited, |field| { candidate.field_value(field.id()).unwrap() }) .unwrap(); edited } fn import(bytes: &[u8], factory: &Scalars) -> KcfgUpdate { let mut importer = KcfgImporter::new(factory).unwrap(); for byte in bytes { importer.push(factory, core::slice::from_ref(byte)).unwrap(); } importer.finish().unwrap() } fn assert_exact(actual: ConfigValue, expected: ConfigValue) { match (actual, expected) { (ConfigValue::F32(a), ConfigValue::F32(b)) => assert_eq!(a.to_bits(), b.to_bits()), (ConfigValue::F64(a), ConfigValue::F64(b)) => assert_eq!(a.to_bits(), b.to_bits()), _ => assert_eq!(actual, expected), } } #[test] fn independent_editors_compare_the_downloaded_base_before_even_noop_or_validation() { let a = factory(); let writes = Cell::new(0); let mut manager = ConfigManager::recover(a.clone(), (), Store(&writes)).unwrap(); let editor_a = export(manager.committed().unwrap(), &a); let editor_b = export(manager.committed().unwrap(), &a); let mut b = a.clone(); b.u64_value -= 1; // Above the exact-integer range of floating-point transports. b.f32_value = -0.0; b.f64_value = -0.0; let update = import(&edit(&editor_b, &b), &a); assert!( manager .replace_if_current(update.candidate, &update.expected_base) .unwrap() .restart_required ); assert_eq!(writes.get(), 1); // Editing an already-edited draft must not rebase it. Even a requested // value set exactly equal to current B is stale when its base was A. let draft = edit(&edit(&editor_a, &b), &b); let update = import(&draft, &a); assert_eq!( manager.replace_if_current(update.candidate, &update.expected_base), Err(ReplaceError::Conflict) ); let update = import(&editor_a, &a); assert_eq!( manager.replace_if_current(update.candidate, &update.expected_base), Err(ReplaceError::Conflict) ); let mut invalid = b.clone(); invalid.finite = f32::NAN; let violation = ConfigViolation::field(FieldId::new(12), ValidationRule::NonFinite); assert_eq!(invalid.validate(&()).unwrap_err(), violation); assert_eq!( manager.replace_if_current(invalid.clone(), &a), Err(ReplaceError::Conflict) ); assert_eq!( manager.replace_if_current(invalid, &b), Err(ReplaceError::InvalidCandidate(violation)) ); assert_eq!(writes.get(), 1); let fresh = import(&export(manager.committed().unwrap(), &a), &a); manager .replace_if_current(fresh.candidate, &fresh.expected_base) .unwrap(); assert_eq!(writes.get(), 1, "same-base no-op does not consume flash"); assert_eq!(manager.active().f32_value.to_bits(), 0.0_f32.to_bits()); assert_eq!( manager.committed().unwrap().f32_value.to_bits(), (-0.0_f32).to_bits() ); assert!(!manager.is_factory(&b)); assert!(!manager.replace_if_current(a, &b).unwrap().restart_required); assert_eq!(writes.get(), 2); } #[test] fn v3_preserves_all_native_widths_and_signed_zero_in_both_value_sets() { let factory = factory(); let mut current = factory.clone(); current.f32_value = -0.0; current.f64_value = -0.0; let bytes = export(¤t, &factory); assert_eq!(bytes[4], 3); let artifact = decode_kcfg(&bytes, &mut [0; kaptan_config::KCFG_FIELD_ID_SCRATCH_BYTES]).unwrap(); assert_eq!(artifact.schema_id(), Scalars::SCHEMA_ID); for field in artifact.fields() { assert_eq!(field.finite(), field.id() == FieldId::new(12)); assert_eq!(field.nonzero(), field.id() == FieldId::new(13)); let expected = current.field_value(field.id()).unwrap(); assert_exact(field.value(), expected); assert_exact(field.expected_base_value(), expected); } let update = import(&edit(&bytes, &factory), &factory); for index in 0..Scalars::field_count() { let id = Scalars::descriptor(index).unwrap().id; assert_exact( update.candidate.field_value(id).unwrap(), factory.field_value(id).unwrap(), ); assert_exact( update.expected_base.field_value(id).unwrap(), current.field_value(id).unwrap(), ); } // 24 header + 4 CRC + 70 prefix + 132 value + 87 key bytes. assert_eq!(bytes.len(), 317); let mut importer = KcfgImporter::new(&factory).unwrap(); assert_eq!( importer.push(&factory, &export(¤t, ¤t)), Err(KcfgDecodeError::FactoryMismatch(FieldId::new(9))) ); // Base checks, no-op checks, restart checks, and factory checks must all // distinguish signed zero even when every other scalar matches. for id in [FieldId::new(9), FieldId::new(10)] { let writes = Cell::new(0); let mut manager = ConfigManager::recover(factory.clone(), (), Store(&writes)).unwrap(); let mut negative_zero = factory.clone(); negative_zero .set_field(id, current.field_value(id).unwrap()) .unwrap(); assert!(!manager.is_factory(&negative_zero)); assert_eq!( manager.replace_if_current(factory.clone(), &negative_zero), Err(ReplaceError::Conflict) ); assert!( manager .replace_if_current(negative_zero.clone(), &factory) .unwrap() .restart_required ); assert_eq!(writes.get(), 1); assert_eq!( manager.replace_if_current(negative_zero, &factory), Err(ReplaceError::Conflict) ); } } #[test] fn nonfinite_payload_identity_survives_artifacts_cas_noops_and_factory_checks() { for (single, double) in [ (0x7f80_0000, 0xfff0_0000_0000_0000), // infinities (0x7fc0_0042, 0xfff8_0000_0000_0042), // quiet payloads (0xff80_0042, 0x7ff0_0000_0000_0042), // signaling payloads ] { let mut factory = factory(); factory.f32_value = f32::from_bits(single); factory.f64_value = f64::from_bits(double); let writes = Cell::new(0); let mut manager = ConfigManager::recover(factory.clone(), (), Store(&writes)).unwrap(); let bytes = export(&factory, &factory); let update = import(&bytes, &factory); assert!(manager.is_factory(&update.candidate)); assert!( !manager .replace_if_current(update.candidate, &update.expected_base) .unwrap() .restart_required ); assert_eq!(writes.get(), 0); let mut other = factory.clone(); other.f64_value = f64::from_bits(double ^ 1); assert!(!manager.is_factory(&other)); assert_eq!( manager.replace_if_current(factory.clone(), &other), Err(ReplaceError::Conflict) ); assert!( manager .replace_if_current(other.clone(), &factory) .unwrap() .restart_required ); assert_eq!(writes.get(), 1); let update = import(&export(&other, &factory), &factory); assert_eq!( update.candidate.field_value(FieldId::new(10)), Some(ConfigValue::F64(other.f64_value)) ); } assert_ne!(ConfigValue::U32(0), ConfigValue::F32(0.0)); } #[test] fn streaming_import_checks_rule_flags_and_rejects_the_unshipped_old_format() { let factory = factory(); let bytes = export(&factory, &factory); let mut old = bytes.clone(); old[4] = 2; let mut importer = KcfgImporter::new(&factory).unwrap(); assert_eq!( importer.push(&factory, &old), Err(KcfgDecodeError::UnsupportedVersion(2)) ); let mut offset = 24; for index in 0..Scalars::field_count() { let field = Scalars::descriptor(index).unwrap(); if field.finite || field.nonzero { let mut edited = bytes.clone(); edited[offset + 3] ^= if field.finite { 4 } else { 8 }; let mut importer = KcfgImporter::new(&factory).unwrap(); assert_eq!( importer.push(&factory, &edited), Err(KcfgDecodeError::DescriptorMismatch(field.id)) ); } offset += 7 + field.kind.width() * 3 + field.display_key.len() + field.unit.len(); } }