#![allow(
clippy::approx_constant,
clippy::useless_vec,
clippy::len_zero,
clippy::unnecessary_cast,
clippy::redundant_closure,
clippy::too_many_arguments,
clippy::type_complexity,
clippy::needless_borrow,
clippy::enum_variant_names,
clippy::upper_case_acronyms,
clippy::inconsistent_digit_grouping,
clippy::unit_cmp,
clippy::assertions_on_constants,
clippy::iter_on_single_items,
clippy::expect_fun_call,
clippy::redundant_pattern_matching,
variant_size_differences,
clippy::absurd_extreme_comparisons,
clippy::nonminimal_bool,
clippy::for_kv_map,
clippy::needless_range_loop,
clippy::single_match,
clippy::collapsible_if,
clippy::needless_return,
clippy::redundant_clone,
clippy::map_entry,
clippy::match_single_binding,
clippy::bool_comparison,
clippy::derivable_impls,
clippy::manual_range_contains,
clippy::needless_borrows_for_generic_args,
clippy::manual_map,
clippy::vec_init_then_push,
clippy::identity_op,
clippy::manual_flatten,
clippy::single_char_pattern,
clippy::search_is_some,
clippy::option_map_unit_fn,
clippy::while_let_on_iterator,
clippy::clone_on_copy,
clippy::box_collection,
clippy::redundant_field_names,
clippy::ptr_arg,
clippy::large_enum_variant,
clippy::match_ref_pats,
clippy::needless_pass_by_value,
clippy::unused_unit,
clippy::let_and_return,
clippy::suspicious_else_formatting,
clippy::manual_strip,
clippy::match_like_matches_macro,
clippy::from_over_into,
clippy::wrong_self_convention,
clippy::inherent_to_string,
clippy::new_without_default,
clippy::unnecessary_wraps,
clippy::field_reassign_with_default,
clippy::manual_find,
clippy::unnecessary_lazy_evaluations,
clippy::should_implement_trait,
clippy::missing_safety_doc,
clippy::unusual_byte_groupings,
clippy::bool_assert_comparison,
clippy::zero_prefixed_literal,
clippy::await_holding_lock,
clippy::manual_saturating_arithmetic,
clippy::explicit_counter_loop,
clippy::needless_lifetimes,
clippy::single_component_path_imports,
clippy::uninlined_format_args,
clippy::iter_cloned_collect,
clippy::manual_str_repeat,
clippy::excessive_precision,
clippy::precedence,
clippy::unnecessary_literal_unwrap
)]
use oxicode::versioning::{
can_migrate, check_compatibility, decode_versioned, decode_versioned_with_check,
encode_versioned, extract_version, is_versioned, migration_path, CompatibilityLevel, Version,
};
#[test]
fn test_version_comparison_ordering() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
assert!(v2 > v1);
assert!(v1 < v2);
}
#[test]
fn test_version_new_fields() {
let v = Version::new(3, 7, 11);
assert_eq!(v.major, 3);
assert_eq!(v.minor, 7);
assert_eq!(v.patch, 11);
}
#[test]
fn test_version_equality() {
assert_eq!(Version::new(1, 2, 3), Version::new(1, 2, 3));
assert_ne!(Version::new(1, 2, 3), Version::new(1, 2, 4));
}
#[test]
fn test_version_zero() {
let v = Version::zero();
assert_eq!(v.major, 0);
assert_eq!(v.minor, 0);
assert_eq!(v.patch, 0);
}
#[test]
fn test_version_parse_valid() {
assert_eq!(Version::parse("1.2.3"), Some(Version::new(1, 2, 3)));
assert_eq!(Version::parse("0.0.0"), Some(Version::new(0, 0, 0)));
assert_eq!(
Version::parse("65535.65535.65535"),
Some(Version::new(65535, 65535, 65535))
);
}
#[test]
fn test_version_parse_invalid() {
assert_eq!(Version::parse("1.2"), None);
assert_eq!(Version::parse("1.2.3.4"), None);
assert_eq!(Version::parse("not.a.version"), None);
assert_eq!(Version::parse(""), None);
}
#[test]
fn test_version_bytes_roundtrip() {
let v = Version::new(10, 20, 30);
let bytes = v.to_bytes();
let v2 = Version::from_bytes(&bytes).expect("from_bytes failed");
assert_eq!(v, v2);
}
#[test]
fn test_version_tuple() {
let v = Version::new(5, 6, 7);
assert_eq!(v.tuple(), (5, 6, 7));
}
#[test]
fn test_version_satisfies() {
let v = Version::new(1, 5, 0);
assert!(v.satisfies(&Version::new(1, 0, 0)));
assert!(!v.satisfies(&Version::new(2, 0, 0)));
}
#[test]
fn test_version_is_compatible_with() {
assert!(Version::new(1, 0, 0).is_compatible_with(&Version::new(1, 5, 0)));
assert!(!Version::new(1, 0, 0).is_compatible_with(&Version::new(2, 0, 0)));
assert!(Version::new(0, 1, 0).is_compatible_with(&Version::new(0, 1, 5)));
assert!(!Version::new(0, 1, 0).is_compatible_with(&Version::new(0, 2, 0)));
}
#[test]
fn test_version_breaking_change() {
assert!(Version::new(2, 0, 0).is_breaking_change_from(&Version::new(1, 0, 0)));
assert!(!Version::new(1, 5, 0).is_breaking_change_from(&Version::new(1, 0, 0)));
}
#[test]
fn test_version_minor_update() {
assert!(Version::new(1, 2, 0).is_minor_update_from(&Version::new(1, 0, 0)));
assert!(!Version::new(2, 0, 0).is_minor_update_from(&Version::new(1, 0, 0)));
}
#[test]
fn test_version_patch_update() {
assert!(Version::new(1, 0, 1).is_patch_update_from(&Version::new(1, 0, 0)));
assert!(!Version::new(1, 1, 0).is_patch_update_from(&Version::new(1, 0, 0)));
}
#[test]
fn test_encode_decode_versioned_raw() {
let data = b"hello world";
let version = Version::new(1, 0, 0);
let encoded = encode_versioned(data, version).expect("encode failed");
let (decoded, ver) = decode_versioned(&encoded).expect("decode failed");
assert_eq!(decoded.as_slice(), data.as_slice());
assert_eq!(ver, version);
}
#[test]
fn test_is_versioned_detection() {
let data = b"raw bytes";
assert!(!is_versioned(data));
let version = Version::new(1, 0, 0);
let encoded = encode_versioned(data, version).expect("encode failed");
assert!(is_versioned(&encoded));
}
#[test]
fn test_is_versioned_empty_slice() {
assert!(!is_versioned(&[]));
assert!(!is_versioned(&[0x4F, 0x58])); }
#[test]
fn test_extract_version_only() {
let data = b"payload";
let version = Version::new(3, 1, 4);
let encoded = encode_versioned(data, version).expect("encode failed");
let extracted = extract_version(&encoded).expect("extract failed");
assert_eq!(extracted, version);
}
#[test]
fn test_decode_versioned_with_check_compatible() {
let data = b"test payload";
let data_version = Version::new(1, 3, 0);
let current = Version::new(1, 5, 0);
let min = Some(Version::new(1, 0, 0));
let encoded = encode_versioned(data, data_version).expect("encode failed");
let (payload, ver, compat) =
decode_versioned_with_check(&encoded, current, min).expect("decode failed");
assert_eq!(payload.as_slice(), data.as_slice());
assert_eq!(ver, data_version);
assert!(compat.is_usable());
}
#[test]
fn test_decode_versioned_with_check_incompatible() {
let data = b"test";
let data_version = Version::new(2, 0, 0);
let current = Version::new(1, 0, 0);
let encoded = encode_versioned(data, data_version).expect("encode failed");
let result = decode_versioned_with_check(&encoded, current, None);
assert!(result.is_err());
}
#[test]
fn test_decode_invalid_magic() {
let garbage = b"not valid data at all";
let result = decode_versioned(garbage);
assert!(result.is_err());
}
#[test]
fn test_decode_truncated_data() {
let short = &[0x4F, 0x58, 0x49, 0x56]; let result = decode_versioned(short);
assert!(result.is_err());
}
#[test]
fn test_encode_empty_payload() {
let data: &[u8] = &[];
let version = Version::new(1, 0, 0);
let encoded = encode_versioned(data, version).expect("encode failed");
let (decoded, ver) = decode_versioned(&encoded).expect("decode failed");
assert!(decoded.is_empty());
assert_eq!(ver, version);
}
#[test]
fn test_compatibility_same_version() {
let v = Version::new(1, 0, 0);
assert_eq!(
check_compatibility(v, v, None),
CompatibilityLevel::Compatible
);
}
#[test]
fn test_compatibility_patch_difference_older_data() {
let data = Version::new(1, 0, 0);
let current = Version::new(1, 0, 5);
let compat = check_compatibility(data, current, None);
assert_eq!(compat, CompatibilityLevel::Compatible);
}
#[test]
fn test_compatibility_older_minor() {
let data = Version::new(1, 0, 0);
let current = Version::new(1, 5, 0);
let compat = check_compatibility(data, current, None);
assert!(compat.is_usable());
}
#[test]
fn test_compatibility_incompatible_major() {
let data = Version::new(1, 0, 0);
let current = Version::new(2, 0, 0);
assert_eq!(
check_compatibility(data, current, None),
CompatibilityLevel::Incompatible
);
}
#[test]
fn test_compatibility_below_minimum() {
let data = Version::new(1, 1, 0);
let current = Version::new(1, 5, 0);
let min = Some(Version::new(1, 3, 0));
assert_eq!(
check_compatibility(data, current, min),
CompatibilityLevel::Incompatible
);
}
#[test]
fn test_compatibility_0x_same_minor() {
let data = Version::new(0, 1, 0);
let current = Version::new(0, 1, 9);
assert_eq!(
check_compatibility(data, current, None),
CompatibilityLevel::Compatible
);
}
#[test]
fn test_compatibility_0x_different_minor() {
let data = Version::new(0, 1, 0);
let current = Version::new(0, 2, 0);
assert_eq!(
check_compatibility(data, current, None),
CompatibilityLevel::Incompatible
);
}
#[test]
fn test_compatibility_newer_data_than_current() {
let data = Version::new(1, 9, 0);
let current = Version::new(1, 0, 0);
let compat = check_compatibility(data, current, None);
assert_eq!(compat, CompatibilityLevel::CompatibleWithWarnings);
}
#[test]
fn test_compatibility_level_methods() {
assert!(CompatibilityLevel::Compatible.is_usable());
assert!(CompatibilityLevel::Compatible.is_fully_compatible());
assert!(!CompatibilityLevel::Compatible.has_warnings());
assert!(CompatibilityLevel::CompatibleWithWarnings.is_usable());
assert!(!CompatibilityLevel::CompatibleWithWarnings.is_fully_compatible());
assert!(CompatibilityLevel::CompatibleWithWarnings.has_warnings());
assert!(!CompatibilityLevel::Incompatible.is_usable());
assert!(!CompatibilityLevel::Incompatible.is_fully_compatible());
assert!(!CompatibilityLevel::Incompatible.has_warnings());
}
#[test]
fn test_can_migrate_same_major() {
assert!(can_migrate(Version::new(1, 0, 0), Version::new(1, 9, 0)));
}
#[test]
fn test_can_migrate_forward_major() {
assert!(can_migrate(Version::new(1, 0, 0), Version::new(3, 0, 0)));
}
#[test]
fn test_cannot_migrate_backward() {
assert!(!can_migrate(Version::new(3, 0, 0), Version::new(1, 0, 0)));
}
#[test]
fn test_can_migrate_same_version() {
assert!(can_migrate(Version::new(1, 0, 0), Version::new(1, 0, 0)));
}
#[test]
fn test_migration_path_same_version() {
let path = migration_path(Version::new(1, 0, 0), Version::new(1, 0, 0));
assert!(path.is_empty());
}
#[test]
fn test_migration_path_same_major() {
let path = migration_path(Version::new(1, 0, 0), Version::new(1, 5, 0));
assert!(path.is_empty());
}
#[test]
fn test_migration_path_one_major_bump() {
let path = migration_path(Version::new(1, 0, 0), Version::new(2, 0, 0));
assert!(
path.is_empty(),
"direct migration needs no intermediate steps"
);
}
#[test]
fn test_migration_path_two_major_bumps() {
let path = migration_path(Version::new(1, 0, 0), Version::new(3, 0, 0));
assert_eq!(path.len(), 1);
assert_eq!(path[0], Version::new(2, 0, 0));
}
#[test]
fn test_migration_path_three_major_bumps() {
let path = migration_path(Version::new(1, 0, 0), Version::new(4, 0, 0));
assert_eq!(path.len(), 2);
assert_eq!(path[0], Version::new(2, 0, 0));
assert_eq!(path[1], Version::new(3, 0, 0));
}
#[test]
fn test_encode_decode_versioned_value_u32() {
let version = Version::new(1, 0, 0);
let encoded = oxicode::encode_versioned_value(&42u32, version).expect("encode failed");
let (decoded, ver, _consumed): (u32, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, 42u32);
assert_eq!(ver, version);
}
#[test]
fn test_encode_decode_versioned_value_u64() {
let version = Version::new(2, 5, 0);
let encoded = oxicode::encode_versioned_value(&99u64, version).expect("encode failed");
let (decoded, ver, _): (u64, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, 99u64);
assert_eq!(ver, version);
}
#[test]
fn test_encode_decode_versioned_value_bool() {
let version = Version::new(1, 0, 0);
for val in [true, false] {
let encoded = oxicode::encode_versioned_value(&val, version).expect("encode failed");
let (decoded, ver, _): (bool, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, val);
assert_eq!(ver, version);
}
}
#[test]
fn test_encode_decode_versioned_value_string() {
let version = Version::new(2, 3, 1);
let original = String::from("hello oxicode versioning");
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode failed");
let (decoded, ver, _): (String, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, original);
assert_eq!(ver, version);
}
#[test]
fn test_encode_decode_versioned_value_vec_u8() {
let version = Version::new(1, 0, 0);
let original: Vec<u8> = vec![1, 2, 3, 4, 5, 255, 0];
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode failed");
let (decoded, ver, _): (Vec<u8>, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, original);
assert_eq!(ver, version);
}
#[test]
fn test_encode_decode_versioned_value_vec_u32() {
let version = Version::new(1, 2, 3);
let original: Vec<u32> = vec![100, 200, 300, 400, 500];
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode failed");
let (decoded, ver, _): (Vec<u32>, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, original);
assert_eq!(ver, version);
}
#[test]
fn test_version_is_preserved_in_encoded_data() {
let version = Version::new(5, 12, 99);
let encoded = oxicode::encode_versioned_value(&100u64, version).expect("encode failed");
let extracted = extract_version(&encoded).expect("extract failed");
assert_eq!(extracted, version);
}
#[test]
fn test_versioned_value_is_detected_as_versioned() {
let version = Version::new(1, 0, 0);
let encoded = oxicode::encode_versioned_value(&true, version).expect("encode failed");
assert!(is_versioned(&encoded));
}
#[test]
fn test_versioned_value_decode_wrong_type_fails() {
let version = Version::new(1, 0, 0);
let encoded = oxicode::encode_versioned_value(&42u32, version).expect("encode failed");
let result: oxicode::Result<(String, _, _)> = oxicode::decode_versioned_value(&encoded);
let _ = result; }
#[test]
fn test_multiple_version_numbers_round_trip() {
let versions = [
Version::new(0, 0, 0),
Version::new(1, 0, 0),
Version::new(0, 1, 0),
Version::new(0, 0, 1),
Version::new(255, 255, 255),
Version::new(1000, 2000, 3000),
Version::new(65535, 65535, 65535),
];
for version in versions {
let encoded = oxicode::encode_versioned_value(&version.tuple().0, version).expect("encode");
let (_, ver, _): (u16, _, _) = oxicode::decode_versioned_value(&encoded).expect("decode");
assert_eq!(ver, version);
}
}
#[cfg(feature = "derive")]
mod derive_tests {
use super::*;
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct DataV1 {
name: String,
value: u32,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct SimpleRecord {
id: u64,
score: f32,
active: bool,
}
#[test]
fn test_derived_struct_versioned_roundtrip() {
let version = Version::new(1, 0, 0);
let original = DataV1 {
name: String::from("test"),
value: 42,
};
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode failed");
let (decoded, ver, _): (DataV1, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, original);
assert_eq!(ver, version);
}
#[test]
fn test_derived_struct_complex_roundtrip() {
let version = Version::new(2, 1, 0);
let original = SimpleRecord {
id: 9999,
score: 2.71,
active: true,
};
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode failed");
let (decoded, ver, _): (SimpleRecord, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, original);
assert_eq!(ver, version);
}
#[test]
fn test_version_schema_evolution_metadata() {
let v1_data = DataV1 {
name: String::from("record"),
value: 100,
};
let v1 = Version::new(1, 0, 0);
let encoded = oxicode::encode_versioned_value(&v1_data, v1).expect("encode failed");
let stored_version = extract_version(&encoded).expect("extract failed");
assert_eq!(stored_version.major, 1);
let current = Version::new(1, 5, 0);
let compat = check_compatibility(stored_version, current, None);
assert!(compat.is_usable());
}
#[test]
fn test_version_numbers_comparison() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
assert!(v2 > v1);
assert!(v1 < v2);
assert_ne!(v1, v2);
let v_patch = Version::new(1, 0, 1);
assert!(v_patch > v1);
let v_minor = Version::new(1, 1, 0);
assert!(v_minor > v1);
assert!(v_minor > v_patch);
}
}
#[test]
fn test_version_ordering() {
let v1_0_0 = Version::new(1, 0, 0);
let v1_0_1 = Version::new(1, 0, 1);
let v1_1_0 = Version::new(1, 1, 0);
let v2_0_0 = Version::new(2, 0, 0);
assert!(v1_0_0 < v1_0_1);
assert!(v1_0_1 < v1_1_0);
assert!(v1_1_0 < v2_0_0);
assert!(v1_0_0 < v2_0_0);
assert_eq!(v1_0_0, Version::new(1, 0, 0));
}
#[test]
fn test_can_migrate_extended() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
assert!(can_migrate(v1, v1));
assert!(can_migrate(v1, v2));
assert!(!can_migrate(v2, v1));
}
#[test]
fn test_versioned_encoding_is_deterministic() {
let v = Version::new(1, 2, 3);
let enc1 = oxicode::encode_versioned_value(&42u32, v).expect("encode 1");
let enc2 = oxicode::encode_versioned_value(&42u32, v).expect("encode 2");
assert_eq!(enc1, enc2, "versioned encoding must be deterministic");
}
#[test]
fn test_different_versions_produce_different_bytes() {
let enc_v1 = oxicode::encode_versioned_value(&42u32, Version::new(1, 0, 0)).expect("encode v1");
let enc_v2 = oxicode::encode_versioned_value(&42u32, Version::new(2, 0, 0)).expect("encode v2");
assert_ne!(
enc_v1, enc_v2,
"different versions should produce different bytes"
);
}
#[test]
fn test_version_compatibility_chain() {
let versions = [
Version::new(1, 0, 0),
Version::new(1, 1, 0),
Version::new(1, 2, 0),
Version::new(1, 3, 0),
];
for (i, &from) in versions.iter().enumerate() {
for &to in &versions[i..] {
assert!(
can_migrate(from, to),
"expected can_migrate({from} -> {to})"
);
}
}
}
#[test]
fn test_stored_version_matches_encoded_version() {
let v = Version::new(3, 7, 11);
let encoded = oxicode::encode_versioned_value(&100u64, v).expect("encode");
let stored = extract_version(&encoded).expect("extract");
assert_eq!(stored, v);
}
#[test]
fn test_decode_versioned_value_tuple_shape() {
let v = Version::new(2, 0, 0);
let enc = oxicode::encode_versioned_value(&255u8, v).expect("encode u8");
let (val, ver, consumed): (u8, _, usize) =
oxicode::decode_versioned_value(&enc).expect("decode u8");
assert_eq!(val, 255u8);
assert_eq!(ver, v);
assert!(consumed > 0, "consumed bytes must be positive");
let enc = oxicode::encode_versioned_value(&-42i32, v).expect("encode i32");
let (val, ver, consumed): (i32, _, usize) =
oxicode::decode_versioned_value(&enc).expect("decode i32");
assert_eq!(val, -42i32);
assert_eq!(ver, v);
assert!(consumed > 0);
}
#[cfg(feature = "derive")]
mod extra_derive_tests {
use super::*;
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct UserV1 {
id: u64,
name: String,
}
#[test]
fn test_multi_version_migration_chain() {
let user_v1 = UserV1 {
id: 1,
name: "alice".to_string(),
};
let v1 = Version::new(1, 0, 0);
let bytes_v1 = oxicode::encode_versioned_value(&user_v1, v1).expect("encode v1");
let stored = extract_version(&bytes_v1).expect("extract version");
assert_eq!(stored, v1);
let (decoded, version, _consumed): (UserV1, _, _) =
oxicode::decode_versioned_value(&bytes_v1).expect("decode v1");
assert_eq!(version, v1);
assert_eq!(decoded, user_v1);
}
#[test]
fn test_v1_v2_bytes_differ() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
let record = UserV1 {
id: 42,
name: "bob".to_string(),
};
let enc_v1 = oxicode::encode_versioned_value(&record, v1).expect("encode v1");
let enc_v2 = oxicode::encode_versioned_value(&record, v2).expect("encode v2");
assert_ne!(enc_v1, enc_v2, "v1 and v2 headers must differ");
}
#[test]
fn test_schema_evolution_compatibility_gate() {
let v1 = Version::new(1, 0, 0);
let v_current = Version::new(1, 5, 0);
let record = UserV1 {
id: 7,
name: "carol".to_string(),
};
let encoded = oxicode::encode_versioned_value(&record, v1).expect("encode");
let stored = extract_version(&encoded).expect("extract");
let compat = check_compatibility(stored, v_current, None);
assert!(compat.is_usable());
}
}
#[cfg(feature = "derive")]
#[test]
fn test_version_schema_evolution_add_optional_field() {
use oxicode::versioning::{check_compatibility, extract_version, Version};
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct RecordV1 {
id: u64,
name: String,
}
#[derive(Debug, PartialEq)]
struct RecordV2 {
id: u64,
name: String,
tag: Option<String>,
}
let v1 = Version::new(1, 0, 0);
let original = RecordV1 {
id: 7,
name: "alice".to_string(),
};
let encoded = oxicode::encode_versioned_value(&original, v1).expect("encode v1");
let stored = extract_version(&encoded).expect("extract");
assert_eq!(stored.major, 1);
assert_eq!(stored.minor, 0);
let v2 = Version::new(1, 1, 0);
let compat = check_compatibility(stored, v2, None);
assert!(compat.is_usable(), "V1 data must be usable by a V2 reader");
let (decoded_v1, ver, _): (RecordV1, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode as v1");
assert_eq!(ver, v1);
assert_eq!(decoded_v1.id, 7);
let migrated = RecordV2 {
id: decoded_v1.id,
name: decoded_v1.name,
tag: None,
};
assert_eq!(
migrated.tag, None,
"new optional field must default to None"
);
}
#[cfg(feature = "derive")]
#[test]
fn test_versioned_value_roundtrip_v1_to_v3() {
use oxicode::versioning::{can_migrate, extract_version, migration_path, Version};
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct Payload {
value: u32,
label: String,
}
let v1 = Version::new(1, 0, 0);
let v3 = Version::new(3, 0, 0);
let original = Payload {
value: 42,
label: "test".to_string(),
};
let encoded = oxicode::encode_versioned_value(&original, v1).expect("encode");
assert!(can_migrate(v1, v3), "forward migration must be supported");
let path = migration_path(v1, v3);
assert_eq!(path.len(), 1);
assert_eq!(path[0], Version::new(2, 0, 0));
let stored = extract_version(&encoded).expect("extract");
assert_eq!(stored, v1);
let (decoded, ver, _): (Payload, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode");
assert_eq!(ver, v1);
assert_eq!(decoded, original);
}
#[test]
fn test_versioned_decode_unknown_version_error() {
use oxicode::versioning::{decode_versioned, encode_versioned, Version};
let mut bytes = encode_versioned(b"payload", Version::new(1, 0, 0)).expect("encode");
bytes[4] = 99;
let result = decode_versioned(&bytes);
assert!(
result.is_err(),
"decoding with unknown header version must fail"
);
}
#[test]
fn test_migration_chain_three_steps() {
use oxicode::versioning::{migration_path, Version};
let from = Version::new(1, 0, 0);
let to = Version::new(4, 0, 0);
let path = migration_path(from, to);
assert_eq!(path.len(), 2, "three-step chain has two intermediate nodes");
assert_eq!(path[0], Version::new(2, 0, 0));
assert_eq!(path[1], Version::new(3, 0, 0));
}
#[cfg(feature = "derive")]
#[test]
fn test_versioned_with_nested_struct() {
use oxicode::versioning::Version;
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct Inner {
x: i32,
y: i32,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct Outer {
name: String,
pos: Inner,
weight: f64,
}
let version = Version::new(1, 2, 3);
let original = Outer {
name: "origin".to_string(),
pos: Inner { x: -5, y: 10 },
weight: std::f64::consts::PI,
};
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode");
let (decoded, ver, _): (Outer, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode");
assert_eq!(decoded, original);
assert_eq!(ver, version);
}
#[cfg(feature = "derive")]
#[test]
fn test_version_field_count_mismatch() {
use oxicode::versioning::{decode_versioned, encode_versioned, Version};
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct TwoFields {
a: u64,
b: u64,
}
let version = Version::new(1, 0, 0);
let original = TwoFields { a: 1, b: 2 };
let payload = oxicode::encode_to_vec(&original).expect("encode payload");
let short_payload = &payload[..1];
let versioned_truncated = encode_versioned(short_payload, version).expect("encode versioned");
let (raw, _ver) = decode_versioned(&versioned_truncated).expect("decode raw");
let result: oxicode::Result<(TwoFields, _)> = oxicode::decode_from_slice(&raw);
assert!(
result.is_err(),
"truncated payload must not decode as TwoFields"
);
}
#[test]
fn test_can_migrate_true() {
use oxicode::versioning::{can_migrate, Version};
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
assert!(
can_migrate(v1, v2),
"forward major migration must be possible"
);
}
#[test]
fn test_can_migrate_false() {
use oxicode::versioning::{can_migrate, Version};
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
assert!(
!can_migrate(v2, v1),
"backward (downgrade) migration must not be possible"
);
}
#[test]
fn test_migration_path_chain() {
use oxicode::versioning::{migration_path, Version};
let v1 = Version::new(1, 0, 0);
let v3 = Version::new(3, 0, 0);
let path = migration_path(v1, v3);
assert_eq!(path.len(), 1, "one intermediate step between v1 and v3");
assert_eq!(
path[0],
Version::new(2, 0, 0),
"intermediate step must be v2"
);
}
#[test]
fn test_versioned_encode_includes_version_header() {
use oxicode::versioning::{Version, VERSIONED_MAGIC};
let version = Version::new(2, 3, 4);
let encoded = oxicode::encode_versioned_value(&99u32, version).expect("encode");
assert!(
encoded.len() >= VERSIONED_MAGIC.len(),
"encoded output must be at least as long as the magic bytes"
);
assert_eq!(
&encoded[..VERSIONED_MAGIC.len()],
&VERSIONED_MAGIC,
"first bytes must be the OXIV magic"
);
}
#[test]
fn test_compatibility_encode_current_decode_old() {
use oxicode::versioning::{
decode_versioned_with_check, encode_versioned, CompatibilityLevel, Version,
};
let new_version = Version::new(1, 5, 0);
let old_reader = Version::new(1, 0, 0);
let encoded = encode_versioned(b"data", new_version).expect("encode");
let result = decode_versioned_with_check(&encoded, old_reader, None);
let (_, ver, compat) = result.expect("decode must succeed despite version mismatch");
assert_eq!(ver, new_version);
assert_eq!(
compat,
CompatibilityLevel::CompatibleWithWarnings,
"newer-minor data decoded by older reader must yield CompatibleWithWarnings"
);
}
#[cfg(feature = "derive")]
#[test]
fn test_versioned_large_payload() {
use oxicode::versioning::Version;
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct LargeRecord {
id: u64,
values: Vec<u32>,
}
let version = Version::new(1, 0, 0);
let original = LargeRecord {
id: 12345,
values: (0u32..100).collect(),
};
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode");
let (decoded, ver, consumed): (LargeRecord, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode");
assert_eq!(decoded, original);
assert_eq!(ver, version);
assert!(consumed > 0);
let plain = oxicode::encode_to_vec(&original).expect("plain encode");
assert!(encoded.len() > plain.len());
}
#[test]
fn test_version_number_in_wire_format() {
use oxicode::versioning::Version;
let version = Version::new(3, 7, 11);
let encoded = oxicode::encode_versioned_value(&0u8, version).expect("encode");
let major = u16::from_le_bytes([encoded[5], encoded[6]]);
let minor = u16::from_le_bytes([encoded[7], encoded[8]]);
let patch = u16::from_le_bytes([encoded[9], encoded[10]]);
assert_eq!(major, 3, "major must be encoded at bytes 5–6 as LE u16");
assert_eq!(minor, 7, "minor must be encoded at bytes 7–8 as LE u16");
assert_eq!(patch, 11, "patch must be encoded at bytes 9–10 as LE u16");
}
#[test]
fn test_multiple_migrations_applied() {
use oxicode::versioning::{extract_version, Version};
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
let v3 = Version::new(3, 0, 0);
let enc1 = oxicode::encode_versioned_value(&100u64, v1).expect("encode v1");
assert_eq!(extract_version(&enc1).expect("extract v1"), v1);
let (val2, _, _): (u64, _, _) = oxicode::decode_versioned_value(&enc1).expect("decode v1");
let enc2 = oxicode::encode_versioned_value(&val2, v2).expect("encode v2");
assert_eq!(extract_version(&enc2).expect("extract v2"), v2);
let (val3, _, _): (u64, _, _) = oxicode::decode_versioned_value(&enc2).expect("decode v2");
let enc3 = oxicode::encode_versioned_value(&val3, v3).expect("encode v3");
assert_eq!(extract_version(&enc3).expect("extract v3"), v3);
let (final_val, final_ver, _): (u64, _, _) =
oxicode::decode_versioned_value(&enc3).expect("decode v3");
assert_eq!(final_val, 100u64);
assert_eq!(final_ver, v3);
}
#[cfg(feature = "derive")]
#[test]
fn test_versioned_default_field_values() {
use oxicode::versioning::Version;
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct ConfigV1 {
timeout_ms: u32,
retries: u8,
}
#[derive(Debug, PartialEq)]
struct ConfigV2 {
timeout_ms: u32,
retries: u8,
verbose: bool,
}
let v1 = Version::new(1, 0, 0);
let original = ConfigV1 {
timeout_ms: 5000,
retries: 3,
};
let encoded = oxicode::encode_versioned_value(&original, v1).expect("encode v1");
let (decoded_v1, ver, _): (ConfigV1, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode v1");
assert_eq!(ver, v1);
let migrated = ConfigV2 {
timeout_ms: decoded_v1.timeout_ms,
retries: decoded_v1.retries,
verbose: bool::default(),
};
assert_eq!(migrated.timeout_ms, 5000);
assert_eq!(migrated.retries, 3);
assert!(
!migrated.verbose,
"new field must default to false during migration"
);
}
#[test]
fn test_version_new_creates_correct_fields() {
let v = Version::new(100, 200, 300);
assert_eq!(v.major, 100, "major must be 100");
assert_eq!(v.minor, 200, "minor must be 200");
assert_eq!(v.patch, 300, "patch must be 300");
let v2 = Version::new(0, 0, 1);
assert_eq!(v2.major, 0);
assert_eq!(v2.minor, 0);
assert_eq!(v2.patch, 1);
}
#[test]
fn test_version_ordering_chain() {
let v1_0_0 = Version::new(1, 0, 0);
let v1_1_0 = Version::new(1, 1, 0);
let v2_0_0 = Version::new(2, 0, 0);
assert!(v1_0_0 < v1_1_0, "1.0.0 must be less than 1.1.0");
assert!(v1_1_0 < v2_0_0, "1.1.0 must be less than 2.0.0");
assert!(v1_0_0 < v2_0_0, "1.0.0 must be less than 2.0.0");
assert_eq!(
v1_0_0,
Version::new(1, 0, 0),
"identical versions must be equal"
);
}
#[cfg(feature = "derive")]
#[test]
fn test_encode_versioned_value_large_struct_roundtrip() {
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct BigRecord {
id: u64,
name: String,
score: f64,
active: bool,
tags: Vec<String>,
counts: Vec<u32>,
}
let version = Version::new(3, 2, 1);
let original = BigRecord {
id: 999_999_999,
name: "large record test".to_string(),
score: 1234.5678_f64,
active: true,
tags: vec!["alpha".to_string(), "beta".to_string(), "gamma".to_string()],
counts: (0u32..50).collect(),
};
let encoded = oxicode::encode_versioned_value(&original, version).expect("encode failed");
let (decoded, ver, consumed): (BigRecord, _, usize) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(decoded, original, "decoded value must equal original");
assert_eq!(ver, version, "decoded version must match encoded version");
assert!(consumed > 0, "consumed must be positive");
}
#[test]
fn test_decode_versioned_value_returns_correct_version() {
let expected_version = Version::new(7, 3, 15);
let encoded = oxicode::encode_versioned_value(&42u32, expected_version).expect("encode failed");
let (val, ver, _): (u32, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode failed");
assert_eq!(val, 42u32);
assert_eq!(
ver, expected_version,
"returned version must equal the version used at encoding time"
);
}
#[test]
fn test_version_u16_boundary_values() {
let max = Version::new(u16::MAX, u16::MAX, u16::MAX);
let bytes = max.to_bytes();
let restored = Version::from_bytes(&bytes).expect("from_bytes must succeed for max version");
assert_eq!(
max, restored,
"max-value version must survive bytes roundtrip"
);
let zero = Version::new(0, 0, 0);
let bytes0 = zero.to_bytes();
let restored0 = Version::from_bytes(&bytes0).expect("from_bytes must succeed for zero version");
assert_eq!(zero, restored0, "zero version must survive bytes roundtrip");
}
#[test]
fn test_encode_versioned_value_vec_string() {
let version = Version::new(2, 0, 0);
let original: Vec<String> = vec![
"hello".to_string(),
"versioned".to_string(),
"world".to_string(),
"".to_string(), ];
let encoded =
oxicode::encode_versioned_value(&original, version).expect("encode Vec<String> failed");
let (decoded, ver, _): (Vec<String>, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode Vec<String> failed");
assert_eq!(
decoded, original,
"Vec<String> must survive versioned roundtrip"
);
assert_eq!(ver, version);
}
#[cfg(feature = "derive")]
#[test]
fn test_encode_versioned_value_nested_structs() {
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct InnerNode {
value: u64,
label: String,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct MiddleNode {
children: Vec<InnerNode>,
count: u32,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct OuterNode {
name: String,
middle: MiddleNode,
active: bool,
}
let version = Version::new(1, 5, 0);
let original = OuterNode {
name: "root".to_string(),
middle: MiddleNode {
children: vec![
InnerNode {
value: 1,
label: "first".to_string(),
},
InnerNode {
value: 2,
label: "second".to_string(),
},
],
count: 2,
},
active: true,
};
let encoded =
oxicode::encode_versioned_value(&original, version).expect("encode nested structs failed");
let (decoded, ver, _): (OuterNode, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode nested structs failed");
assert_eq!(decoded, original);
assert_eq!(ver, version);
}
#[test]
fn test_multiple_sequential_versioned_operations() {
let pairs: &[(u64, Version)] = &[
(0, Version::new(1, 0, 0)),
(1, Version::new(1, 1, 0)),
(u64::MAX, Version::new(2, 0, 0)),
(42, Version::new(0, 5, 3)),
(100, Version::new(10, 0, 255)),
];
for (value, version) in pairs {
let encoded =
oxicode::encode_versioned_value(value, *version).expect("encode in sequence failed");
let (decoded, ver, _): (u64, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode in sequence failed");
assert_eq!(decoded, *value, "sequential value mismatch");
assert_eq!(ver, *version, "sequential version mismatch");
}
}
#[test]
fn test_versioned_primitives_u64_bool_string() {
{
let v = Version::new(1, 0, 0);
let encoded = oxicode::encode_versioned_value(&u64::MAX, v).expect("encode u64");
let (val, ver, _): (u64, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode u64");
assert_eq!(val, u64::MAX);
assert_eq!(ver, v);
}
{
let v = Version::new(2, 1, 0);
for b in [true, false] {
let encoded = oxicode::encode_versioned_value(&b, v).expect("encode bool");
let (val, ver, _): (bool, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode bool");
assert_eq!(val, b);
assert_eq!(ver, v);
}
}
{
let v = Version::new(3, 0, 7);
let s = "hello versioned string".to_string();
let encoded = oxicode::encode_versioned_value(&s, v).expect("encode String");
let (val, ver, _): (String, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode String");
assert_eq!(val, s);
assert_eq!(ver, v);
}
}
#[test]
fn test_decode_versioned_value_version_zero() {
let version = Version::zero();
assert_eq!(version.major, 0);
assert_eq!(version.minor, 0);
assert_eq!(version.patch, 0);
let encoded =
oxicode::encode_versioned_value(&999u32, version).expect("encode with zero version");
let (val, ver, _): (u32, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode with zero version");
assert_eq!(val, 999u32);
assert_eq!(ver, version, "all-zero version must be preserved");
}
#[cfg(feature = "derive")]
#[test]
fn test_schema_evolution_skip_and_default_field() {
use oxicode::versioning::{check_compatibility, extract_version};
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct ArticleV1 {
id: u64,
title: String,
}
#[derive(Debug, PartialEq)]
struct ArticleV2 {
id: u64,
title: String,
description: String,
}
let v1 = Version::new(1, 0, 0);
let original = ArticleV1 {
id: 42,
title: "Evolution Test".to_string(),
};
let encoded = oxicode::encode_versioned_value(&original, v1).expect("encode v1");
let stored = extract_version(&encoded).expect("extract version");
let v2_current = Version::new(1, 1, 0);
let compat = check_compatibility(stored, v2_current, None);
assert!(compat.is_usable(), "V1 data must be usable by V2 reader");
let (decoded_v1, ver, _): (ArticleV1, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode as V1");
assert_eq!(ver, v1);
let migrated = ArticleV2 {
id: decoded_v1.id,
title: decoded_v1.title,
description: String::new(),
};
assert_eq!(migrated.id, 42);
assert_eq!(migrated.title, "Evolution Test");
assert!(
migrated.description.is_empty(),
"default description must be empty string"
);
}
#[test]
fn test_versioned_value_larger_than_plain() {
let value = 12345u64;
let version = Version::new(1, 0, 0);
let plain = oxicode::encode_to_vec(&value).expect("plain encode");
let versioned = oxicode::encode_versioned_value(&value, version).expect("versioned encode");
assert!(
versioned.len() > plain.len(),
"versioned output ({} bytes) must be larger than plain ({} bytes)",
versioned.len(),
plain.len()
);
assert_eq!(
versioned.len() - plain.len(),
11,
"version header overhead must be exactly 11 bytes"
);
}
#[test]
fn test_version_header_three_fields_at_known_offsets() {
let version = Version::new(1, 2, 3);
let encoded = oxicode::encode_versioned_value(&0u8, version).expect("encode");
assert!(encoded.len() >= 11, "encoded must be at least 11 bytes");
let major = u16::from_le_bytes([encoded[5], encoded[6]]);
let minor = u16::from_le_bytes([encoded[7], encoded[8]]);
let patch = u16::from_le_bytes([encoded[9], encoded[10]]);
assert_eq!(major, 1, "major at bytes 5-6");
assert_eq!(minor, 2, "minor at bytes 7-8");
assert_eq!(patch, 3, "patch at bytes 9-10");
}
#[test]
fn test_version_max_values_roundtrip() {
let max_version = Version::new(u16::MAX, u16::MAX, u16::MAX);
let payload = 77u32;
let encoded =
oxicode::encode_versioned_value(&payload, max_version).expect("encode max version");
let (val, ver, _): (u32, _, _) =
oxicode::decode_versioned_value(&encoded).expect("decode max version");
assert_eq!(val, payload);
assert_eq!(
ver, max_version,
"max version (65535.65535.65535) must roundtrip"
);
assert_eq!(ver.major, u16::MAX);
assert_eq!(ver.minor, u16::MAX);
assert_eq!(ver.patch, u16::MAX);
}
#[cfg(feature = "derive")]
#[test]
fn test_multiple_structs_different_versions() {
use oxicode::{Decode, Encode};
#[derive(Debug, PartialEq, Encode, Decode)]
struct TypeA {
x: u32,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct TypeB {
name: String,
count: u64,
}
#[derive(Debug, PartialEq, Encode, Decode)]
struct TypeC {
flag: bool,
values: Vec<u8>,
}
let va = Version::new(1, 0, 0);
let vb = Version::new(2, 3, 0);
let vc = Version::new(0, 9, 5);
let a = TypeA { x: 42 };
let b = TypeB {
name: "struct_b".to_string(),
count: 100,
};
let c = TypeC {
flag: false,
values: vec![10, 20, 30],
};
let enc_a = oxicode::encode_versioned_value(&a, va).expect("encode A");
let enc_b = oxicode::encode_versioned_value(&b, vb).expect("encode B");
let enc_c = oxicode::encode_versioned_value(&c, vc).expect("encode C");
let (da, vera, _): (TypeA, _, _) = oxicode::decode_versioned_value(&enc_a).expect("decode A");
let (db, verb, _): (TypeB, _, _) = oxicode::decode_versioned_value(&enc_b).expect("decode B");
let (dc, verc, _): (TypeC, _, _) = oxicode::decode_versioned_value(&enc_c).expect("decode C");
assert_eq!(da, a);
assert_eq!(vera, va);
assert_eq!(db, b);
assert_eq!(verb, vb);
assert_eq!(dc, c);
assert_eq!(verc, vc);
}
#[test]
fn test_decode_versioned_then_reencode_different_version() {
let v1 = Version::new(1, 0, 0);
let v2 = Version::new(2, 0, 0);
let original = 55555u32;
let enc_v1 = oxicode::encode_versioned_value(&original, v1).expect("encode v1");
let (val, ver1, _): (u32, _, _) = oxicode::decode_versioned_value(&enc_v1).expect("decode v1");
assert_eq!(val, original);
assert_eq!(ver1, v1);
let enc_v2 = oxicode::encode_versioned_value(&val, v2).expect("re-encode as v2");
let (val2, ver2, _): (u32, _, _) = oxicode::decode_versioned_value(&enc_v2).expect("decode v2");
assert_eq!(
val2, original,
"value must be unchanged after version migration"
);
assert_eq!(ver2, v2, "re-encoded data must carry V2 header");
assert_ne!(enc_v1, enc_v2, "V1 and V2 encoded bytes must differ");
}
#[cfg(any(feature = "compression-lz4", feature = "compression-zstd"))]
#[test]
fn test_versioned_with_compression_none() {
use oxicode::compression::{compress, decompress, Compression};
use oxicode::versioning::{decode_versioned, encode_versioned, extract_version, Version};
let version = Version::new(1, 2, 3);
let payload_value = 9999u64;
let plain = oxicode::encode_to_vec(&payload_value).expect("plain encode");
let compressed = compress(&plain, Compression::None).expect("compress None");
let versioned = encode_versioned(&compressed, version).expect("encode versioned");
let stored_ver = extract_version(&versioned).expect("extract version");
assert_eq!(stored_ver, version);
let (raw, ver) = decode_versioned(&versioned).expect("decode versioned");
assert_eq!(ver, version);
let decompressed = decompress(&raw).expect("decompress");
let (val, _): (u64, _) = oxicode::decode_from_slice(&decompressed).expect("plain decode");
assert_eq!(val, payload_value);
}
#[test]
fn test_error_on_truncated_version_header() {
use oxicode::versioning::{decode_versioned, encode_versioned, Version};
let version = Version::new(1, 0, 0);
let full = encode_versioned(b"payload", version).expect("encode");
for len in 0..11usize {
let truncated = &full[..len];
let result = decode_versioned(truncated);
assert!(
result.is_err(),
"decode must fail for truncated header of length {len}"
);
}
}
#[test]
fn test_batch_100_items_versioned() {
let version = Version::new(1, 0, 0);
let mut encoded_items: Vec<Vec<u8>> = Vec::with_capacity(100);
for i in 0u64..100 {
let enc = oxicode::encode_versioned_value(&i, version).expect("batch encode");
encoded_items.push(enc);
}
for (i, enc) in encoded_items.iter().enumerate() {
let (val, ver, _): (u64, _, _) =
oxicode::decode_versioned_value(enc).expect("batch decode");
assert_eq!(val, i as u64, "batch item {i}: value mismatch");
assert_eq!(ver, version, "batch item {i}: version mismatch");
}
}
#[test]
fn test_version_display_format() {
let cases: &[(Version, &str)] = &[
(Version::new(1, 2, 3), "1.2.3"),
(Version::new(0, 0, 0), "0.0.0"),
(Version::new(10, 20, 30), "10.20.30"),
(
Version::new(u16::MAX, u16::MAX, u16::MAX),
"65535.65535.65535",
),
(Version::new(1, 0, 0), "1.0.0"),
];
for (v, expected) in cases {
let formatted = format!("{}", v);
assert_eq!(
formatted, *expected,
"display format for {:?} must be {:?}, got {:?}",
v, expected, formatted
);
}
}