use std::collections::BTreeMap;
use super::*;
#[test]
fn processor_id_orders_by_group_then_number() {
let a = ProcessorId {
group: 0,
number: 5,
};
let b = ProcessorId {
group: 1,
number: 0,
};
assert!(a < b);
}
#[test]
fn a_memory_domain_may_have_no_processors() {
let domain = Domain {
kind: DomainKind::Memory {
memory_bytes: Observed::Known(64 * 1024 * 1024 * 1024),
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
assert!(domain.processors.is_empty());
let DomainKind::Memory { memory_bytes } = domain.kind else {
panic!("expected Memory")
};
assert_eq!(memory_bytes, Observed::Known(64 * 1024 * 1024 * 1024));
}
#[test]
fn a_discovered_memory_domain_has_no_known_size() {
let domain = Domain {
kind: DomainKind::Memory {
memory_bytes: Observed::NotObserved,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let DomainKind::Memory { memory_bytes } = domain.kind else {
panic!("expected Memory")
};
assert_eq!(memory_bytes, Observed::NotObserved);
}
#[test]
fn an_unrecognised_domain_kind_carries_its_attributes() {
let mut attributes = BTreeMap::new();
attributes.insert("watts".to_string(), AttributeValue::Float(15.5));
let domain = Domain {
kind: DomainKind::Other {
name: "power".to_string(),
attributes: attributes.clone(),
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let DomainKind::Other {
name,
attributes: got,
} = &domain.kind
else {
panic!("expected Other")
};
assert_eq!(name, "power");
assert_eq!(got, &attributes);
}
#[test]
fn attribute_value_supports_nested_structures() {
let mut inner = BTreeMap::new();
inner.insert("a".to_string(), AttributeValue::Bool(true));
let value = AttributeValue::Array(vec![AttributeValue::Null, AttributeValue::Object(inner)]);
assert_eq!(
value.clone(),
value,
"AttributeValue must support equality for round-trip tests later"
);
}
#[cfg(feature = "serde")]
mod serde_tests {
use std::collections::BTreeMap;
use super::super::*;
use crate::observed::Observed;
use crate::processor_set::ProcessorSet;
fn round_trip(domain: &Domain) -> Domain {
let json = serde_json::to_string(domain).expect("serialize");
serde_json::from_str(&json).expect("deserialize")
}
#[test]
fn a_group_domain_round_trips() {
let domain = Domain {
kind: DomainKind::Group,
processors: ProcessorSet::from_group_mask(0, 0b11),
observations: Vec::new(),
};
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn a_core_domain_round_trips_with_its_fields() {
let domain = Domain {
kind: DomainKind::Core {
simultaneous_multithreading: true,
efficiency_class: 7,
},
processors: ProcessorSet::from_group_mask(0, 0b1),
observations: Vec::new(),
};
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn a_cache_domain_round_trips_including_its_cache_type() {
let domain = Domain {
kind: DomainKind::Cache {
level: 3,
associativity: 16,
line_size: 64,
size_bytes: 32 * 1024 * 1024,
cache_type: CacheKind::Unified,
},
processors: ProcessorSet::from_group_mask(0, 0b1111),
observations: Vec::new(),
};
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn a_cache_domain_with_an_unrecognised_cache_type_round_trips() {
let domain = Domain {
kind: DomainKind::Cache {
level: 1,
associativity: 8,
line_size: 64,
size_bytes: 32 * 1024,
cache_type: CacheKind::Other(99),
},
processors: ProcessorSet::from_group_mask(0, 0b1),
observations: Vec::new(),
};
let json = serde_json::to_string(&domain).expect("serialize");
assert!(
json.contains(r#""cache_type":{"other":99}"#),
"unexpected JSON: {json}"
);
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn a_cache_domain_with_a_negative_unrecognised_cache_type_round_trips() {
let domain = Domain {
kind: DomainKind::Cache {
level: 1,
associativity: 8,
line_size: 64,
size_bytes: 32 * 1024,
cache_type: CacheKind::Other(-7),
},
processors: ProcessorSet::from_group_mask(0, 0b1),
observations: Vec::new(),
};
let json = serde_json::to_string(&domain).expect("serialize");
assert!(
json.contains(r#""cache_type":{"other":-7}"#),
"unexpected JSON: {json}"
);
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn a_memory_only_domain_round_trips_with_no_processors() {
let domain = Domain {
kind: DomainKind::Memory {
memory_bytes: Observed::Known(64 * 1024 * 1024 * 1024),
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let json = serde_json::to_string(&domain).expect("serialize");
assert!(
json.contains(r#""processors":[]"#),
"unexpected JSON: {json}"
);
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn a_memory_domain_with_unknown_size_omits_memory_bytes_rather_than_writing_null() {
let domain = Domain {
kind: DomainKind::Memory {
memory_bytes: Observed::NotObserved,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let json = serde_json::to_string(&domain).expect("serialize");
assert!(!json.contains("memory_bytes"), "unexpected JSON: {json}");
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn an_unrecognised_domain_kind_round_trips_its_attributes_losslessly() {
let mut attributes = BTreeMap::new();
attributes.insert("watts".to_string(), AttributeValue::Float(15.5));
attributes.insert("throttled".to_string(), AttributeValue::Bool(false));
let domain = Domain {
kind: DomainKind::Other {
name: "power".to_string(),
attributes,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
assert_eq!(round_trip(&domain), domain);
}
#[test]
fn an_integer_attribute_beyond_f64s_precision_round_trips_exactly() {
let large_unsigned: u64 = (1u64 << 53) + 1;
let large_signed: i64 = -((1i64 << 53) + 1);
let mut attributes = BTreeMap::new();
attributes.insert(
"large_unsigned".to_string(),
AttributeValue::UnsignedInteger(large_unsigned),
);
attributes.insert(
"large_signed".to_string(),
AttributeValue::SignedInteger(large_signed),
);
let domain = Domain {
kind: DomainKind::Other {
name: "precision".to_string(),
attributes,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let restored = round_trip(&domain);
assert_eq!(restored, domain);
let DomainKind::Other { attributes, .. } = &restored.kind else {
panic!("expected Other")
};
assert_eq!(
attributes.get("large_unsigned"),
Some(&AttributeValue::UnsignedInteger(large_unsigned))
);
assert_eq!(
attributes.get("large_signed"),
Some(&AttributeValue::SignedInteger(large_signed))
);
}
#[test]
fn a_memory_bytes_value_beyond_f64s_precision_decodes_exactly() {
let precise: u64 = (1u64 << 53) + 1;
let domain = Domain {
kind: DomainKind::Memory {
memory_bytes: Observed::Known(precise),
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let restored = round_trip(&domain);
let DomainKind::Memory { memory_bytes } = restored.kind else {
panic!("expected Memory")
};
assert_eq!(memory_bytes, Observed::Known(precise));
}
#[test]
fn an_unrecognised_domain_kinds_attribute_colliding_with_a_reserved_field_name_is_refused() {
for reserved in ["kind", "id", "processors"] {
let mut attributes = BTreeMap::new();
attributes.insert(reserved.to_string(), AttributeValue::UnsignedInteger(1));
let domain = Domain {
kind: DomainKind::Other {
name: "power".to_string(),
attributes,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
serde_json::to_string(&domain).expect_err(&format!(
"an attribute named {reserved:?} must not silently overwrite the reserved field"
));
}
}
#[test]
fn an_unrecognised_domain_kind_named_after_a_known_one_is_refused() {
for known in [
"group", "package", "die", "module", "core", "cache", "memory",
] {
let mut attributes = BTreeMap::new();
attributes.insert("watts".to_string(), AttributeValue::UnsignedInteger(15));
let domain = Domain {
kind: DomainKind::Other {
name: known.to_string(),
attributes,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
serde_json::to_string(&domain).expect_err(&format!(
"an unrecognised kind named {known:?} must not be written as that known kind"
));
}
}
#[test]
fn an_unrecognised_domain_kind_with_its_own_name_still_round_trips() {
let mut attributes = BTreeMap::new();
attributes.insert("watts".to_string(), AttributeValue::UnsignedInteger(15));
let domain = Domain {
kind: DomainKind::Other {
name: "power".to_string(),
attributes,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let restored = round_trip(&domain);
assert_eq!(restored, domain);
}
#[test]
fn every_well_known_name_decodes_to_a_named_kind() {
for name in crate::domain::serde_impl::WELL_KNOWN_KIND_NAMES {
let json = format!(
r#"{{"kind":"{name}","id":0,"processors":[],
"simultaneous_multithreading":false,"efficiency_class":0,
"level":1,"associativity":8,"line_size":64,"size_bytes":32768,
"cache_type":"unified","memory_bytes":0}}"#
);
let domain: Domain = serde_json::from_str(&json)
.unwrap_or_else(|e| panic!("{name:?} must decode as a named kind: {e}"));
assert!(
!matches!(domain.kind, DomainKind::Other { .. }),
"{name:?} is listed as well known but decoded as `Other`, so the \
serializer refuses a name nothing actually reserves"
);
}
}
#[test]
fn a_hand_written_synthetic_description_parses() {
let json = r#"{
"kind": "memory",
"id": 5,
"processors": [],
"memory_bytes": 549755813888
}"#;
let domain: Domain = serde_json::from_str(json).expect("parse");
assert!(domain.observations.is_empty());
assert!(domain.processors.is_empty());
assert_eq!(
domain.kind,
DomainKind::Memory {
memory_bytes: Observed::Known(549_755_813_888)
}
);
}
#[test]
fn a_hand_written_description_can_place_kind_in_any_field_order() {
let json = r#"{
"processors": [{"group": 0, "number": 0}],
"id": 1,
"kind": "package"
}"#;
let domain: Domain = serde_json::from_str(json).expect("parse");
assert_eq!(domain.kind, DomainKind::Package);
}
#[test]
fn an_unrecognised_kind_from_hand_written_json_parses_as_other() {
let json = r#"{
"kind": "die",
"id": 0,
"processors": [{"group": 0, "number": 0}]
}"#;
let domain: Domain = serde_json::from_str(json).expect("parse");
assert_eq!(domain.kind, DomainKind::Die);
let json = r#"{
"kind": "quantum-cache",
"id": 0,
"processors": [],
"coherence": "eventual"
}"#;
let domain: Domain = serde_json::from_str(json).expect("parse");
let DomainKind::Other { name, attributes } = domain.kind else {
panic!("expected Other")
};
assert_eq!(name, "quantum-cache");
assert_eq!(
attributes.get("coherence"),
Some(&AttributeValue::String("eventual".to_string()))
);
}
#[test]
fn a_missing_required_field_is_a_clean_error_not_a_panic() {
let json = r#"{"kind": "group", "id": 0}"#;
let error = serde_json::from_str::<Domain>(json).expect_err("processors is required");
assert!(
error.to_string().contains("processors"),
"error should name the missing field: {error}"
);
}
fn memory_domain_with(bytes_literal: &str) -> Result<Domain, serde_json::Error> {
let json = format!(
r#"{{"kind": "memory", "id": 0, "processors": [], "memory_bytes": {bytes_literal}}}"#
);
serde_json::from_str(&json)
}
#[test]
fn a_whole_number_float_is_accepted_as_an_unsigned_field() {
let domain = memory_domain_with("1024.0").expect("4.0 is a whole number");
assert_eq!(
domain.kind,
DomainKind::Memory {
memory_bytes: Observed::Known(1024)
},
"a generator emitting a whole number as a float is ordinary JSON"
);
}
#[test]
fn a_fractional_float_is_refused_rather_than_truncated() {
assert!(
memory_domain_with("1024.5").is_err(),
"a fractional byte count must be refused, not rounded"
);
}
#[test]
fn a_negative_float_is_refused_for_an_unsigned_field() {
assert!(
memory_domain_with("-1.0").is_err(),
"a negative count is out of range for an unsigned field"
);
}
#[test]
fn a_float_beyond_the_unsigned_range_is_refused() {
assert!(
memory_domain_with("1e300").is_err(),
"a float larger than u64::MAX must be refused, not saturated"
);
}
#[test]
fn zero_is_accepted_at_the_bottom_of_the_unsigned_range() {
let domain = memory_domain_with("0.0").expect("zero is in range");
assert_eq!(
domain.kind,
DomainKind::Memory {
memory_bytes: Observed::Known(0)
}
);
}
#[test]
fn an_integer_literal_still_parses_unchanged() {
let domain = memory_domain_with("4096").expect("plain integers parse");
assert_eq!(
domain.kind,
DomainKind::Memory {
memory_bytes: Observed::Known(4096)
}
);
}
fn cache_domain_with_other_type(code_literal: &str) -> Result<Domain, serde_json::Error> {
let json = format!(
r#"{{"kind": "cache", "id": 0, "processors": [],
"level": 2, "associativity": 8, "line_size": 64,
"size_bytes": 1024, "cache_type": {{"other": {code_literal}}}}}"#
);
serde_json::from_str(&json)
}
#[test]
fn a_whole_number_float_is_accepted_as_a_signed_field() {
let domain = cache_domain_with_other_type("9.0").expect("9.0 is a whole number");
let DomainKind::Cache { cache_type, .. } = domain.kind else {
panic!("expected a cache domain");
};
assert_eq!(cache_type, CacheKind::Other(9));
}
#[test]
fn a_negative_whole_float_is_accepted_as_a_signed_field() {
let domain = cache_domain_with_other_type("-3.0").expect("-3.0 is a whole number");
let DomainKind::Cache { cache_type, .. } = domain.kind else {
panic!("expected a cache domain");
};
assert_eq!(cache_type, CacheKind::Other(-3));
}
#[test]
fn a_fractional_float_is_refused_for_a_signed_field() {
assert!(
cache_domain_with_other_type("-3.5").is_err(),
"a fractional cache-type code must be refused, not truncated"
);
}
#[test]
fn a_float_beyond_the_signed_range_is_refused() {
assert!(
cache_domain_with_other_type("-1e300").is_err(),
"a float below i64::MIN must be refused, not saturated"
);
}
#[test]
fn a_false_flag_survives_the_round_trip_as_false() {
let domain = Domain {
kind: DomainKind::Core {
simultaneous_multithreading: false,
efficiency_class: 0,
},
processors: ProcessorSet::from_group_mask(0, 0b1),
observations: Vec::new(),
};
let restored = round_trip(&domain);
assert_eq!(restored, domain);
let DomainKind::Core {
simultaneous_multithreading,
..
} = restored.kind
else {
panic!("a core domain must deserialize as one");
};
assert!(
!simultaneous_multithreading,
"a machine without SMT must not be reported as having it"
);
}
#[test]
fn a_non_boolean_smt_flag_is_refused_rather_than_read_as_true() {
let well_formed = r#"{"kind": "core", "id": 1, "processors": [],
"simultaneous_multithreading": false, "efficiency_class": 0}"#;
serde_json::from_str::<Domain>(well_formed)
.expect("the fixture must parse when only the flag is changed");
for bad in [r#""yes""#, "1", "null"] {
let json = well_formed.replace("false", bad);
let error = serde_json::from_str::<Domain>(&json)
.expect_err("{bad} is not a boolean and must not be read as one");
assert!(
error.to_string().contains("boolean"),
"the refusal must say what was expected: {error}"
);
}
}
#[test]
fn a_cache_type_object_carrying_more_than_the_other_key_is_refused() {
let json = r#"{"kind": "cache", "id": 0, "processors": [],
"level": 2, "associativity": 8, "line_size": 64,
"size_bytes": 1024, "cache_type": {"other": 99, "extra": 1}}"#;
let outcome: Result<Domain, _> = serde_json::from_str(json);
let error = outcome.expect_err("an object with a second key must be refused");
assert!(
error.to_string().contains("cache_type"),
"the refusal must name the field that was not understood, or a \
reader cannot tell which part of their description was rejected: {error}"
);
}
#[test]
fn a_cache_type_object_with_exactly_the_other_key_is_accepted() {
let domain =
cache_domain_with_other_type("99").expect("the single-key form is the one we emit");
let DomainKind::Cache { cache_type, .. } = domain.kind else {
panic!("a cache domain must deserialize as one");
};
assert_eq!(cache_type, CacheKind::Other(99));
}
}
#[cfg(feature = "serde")]
#[test]
fn an_omitted_memory_bytes_and_an_explicit_null_are_different_values() {
let omitted: Domain =
serde_json::from_str(r#"{"kind": "memory", "id": 0, "processors": []}"#).expect("parse");
let explicit_null: Domain = serde_json::from_str(
r#"{"kind": "memory", "id": 0, "processors": [], "memory_bytes": null}"#,
)
.expect("parse");
let DomainKind::Memory {
memory_bytes: from_omission,
} = omitted.kind
else {
panic!("memory");
};
let DomainKind::Memory {
memory_bytes: from_null,
} = explicit_null.kind
else {
panic!("memory");
};
assert_eq!(from_omission, Observed::NotObserved, "nobody addressed it");
assert_eq!(from_null, Observed::Absent, "addressed, and no value");
assert_ne!(from_omission, from_null);
}
#[cfg(feature = "serde")]
#[test]
fn the_three_memory_bytes_states_round_trip_distinctly() {
for state in [
Observed::Known(4096_u64),
Observed::Absent,
Observed::NotObserved,
] {
let domain = Domain {
kind: DomainKind::Memory {
memory_bytes: state,
},
processors: ProcessorSet::empty(),
observations: Vec::new(),
};
let json = serde_json::to_string(&domain).expect("serialize");
let back: Domain = serde_json::from_str(&json).expect("deserialize");
let DomainKind::Memory { memory_bytes } = back.kind else {
panic!("memory");
};
assert_eq!(memory_bytes, state, "round trip changed {json}");
}
}
#[test]
fn a_node_with_genuinely_no_memory_is_known_zero_not_an_absence() {
let zero = Observed::Known(0_u64);
assert!(zero.was_observed());
assert_ne!(zero, Observed::Absent);
assert_ne!(zero, Observed::NotObserved);
}