1use std::collections::BTreeMap;
10
11use crate::{MetadataImage, group_version, krabka_version, metadata_version, transaction_version};
12
13pub trait Feature: Sync {
15 fn name(&self) -> &'static str;
17
18 fn supported_range(&self) -> (i16, i16);
21
22 fn default_level(&self, bootstrap_mv: i16) -> i16;
26
27 fn min_required_floor(&self, _image: &MetadataImage) -> i16 {
31 self.supported_range().0
32 }
33
34 fn dependencies(&self, _level: i16) -> &'static [(&'static str, i16)] {
38 &[]
39 }
40
41 fn level_name(&self, _level: i16) -> Option<&'static str> {
44 None
45 }
46}
47
48pub struct MetadataVersionFeature;
51
52impl Feature for MetadataVersionFeature {
53 fn name(&self) -> &'static str {
54 metadata_version::METADATA_VERSION_FEATURE
55 }
56 fn supported_range(&self) -> (i16, i16) {
57 (
58 metadata_version::METADATA_VERSION_MIN,
59 metadata_version::METADATA_VERSION_MAX,
60 )
61 }
62 fn default_level(&self, bootstrap_mv: i16) -> i16 {
63 bootstrap_mv.clamp(
66 metadata_version::METADATA_VERSION_MIN,
67 metadata_version::METADATA_VERSION_MAX,
68 )
69 }
70 fn min_required_floor(&self, image: &MetadataImage) -> i16 {
71 image.min_required_metadata_version()
72 }
73 fn level_name(&self, level: i16) -> Option<&'static str> {
74 metadata_version::from_feature_level(level).map(metadata_version::MetadataVersion::ivn)
75 }
76}
77
78pub struct GroupVersionFeature;
81
82impl Feature for GroupVersionFeature {
83 fn name(&self) -> &'static str {
84 group_version::GROUP_VERSION_FEATURE
85 }
86 fn supported_range(&self) -> (i16, i16) {
87 (
88 group_version::GROUP_VERSION_MIN,
89 group_version::GROUP_VERSION_MAX,
90 )
91 }
92 fn default_level(&self, bootstrap_mv: i16) -> i16 {
93 if bootstrap_mv >= group_version::GROUP_VERSION_GA_METADATA_LEVEL {
94 group_version::GROUP_VERSION_MAX
95 } else {
96 group_version::GROUP_VERSION_MIN
97 }
98 }
99 }
105
106pub struct TransactionVersionFeature;
112
113impl Feature for TransactionVersionFeature {
114 fn name(&self) -> &'static str {
115 transaction_version::TRANSACTION_VERSION_FEATURE
116 }
117 fn supported_range(&self) -> (i16, i16) {
118 (
119 transaction_version::TRANSACTION_VERSION_MIN,
120 transaction_version::TRANSACTION_VERSION_MAX,
121 )
122 }
123 fn default_level(&self, bootstrap_mv: i16) -> i16 {
127 use crate::transaction_version::{TV1_METADATA_LEVEL, TV2_METADATA_LEVEL};
130 match bootstrap_mv {
131 level if level >= TV2_METADATA_LEVEL => 2,
132 level if level >= TV1_METADATA_LEVEL => 1,
133 _ => 0,
134 }
135 }
136 }
138
139pub struct ShareVersionFeature;
147
148impl Feature for ShareVersionFeature {
149 fn name(&self) -> &'static str {
150 metadata_version::SHARE_VERSION_FEATURE
151 }
152 fn supported_range(&self) -> (i16, i16) {
153 (
154 metadata_version::SHARE_VERSION_MIN,
155 metadata_version::SHARE_VERSION_MAX,
156 )
157 }
158 fn default_level(&self, bootstrap_mv: i16) -> i16 {
159 use crate::metadata_version::{
160 SHARE_VERSION_2_DEFAULT_METADATA_LEVEL, SHARE_VERSION_DEFAULT_METADATA_LEVEL,
161 };
162 match bootstrap_mv {
163 level if level >= SHARE_VERSION_2_DEFAULT_METADATA_LEVEL => 2,
164 level if level >= SHARE_VERSION_DEFAULT_METADATA_LEVEL => 1,
165 _ => metadata_version::SHARE_VERSION_MIN,
166 }
167 }
168 }
170
171pub struct StreamsVersionFeature;
177
178impl Feature for StreamsVersionFeature {
179 fn name(&self) -> &'static str {
180 metadata_version::STREAMS_VERSION_FEATURE
181 }
182 fn supported_range(&self) -> (i16, i16) {
183 (
184 metadata_version::STREAMS_VERSION_MIN,
185 metadata_version::STREAMS_VERSION_MAX,
186 )
187 }
188 fn default_level(&self, bootstrap_mv: i16) -> i16 {
189 if bootstrap_mv >= metadata_version::STREAMS_VERSION_DEFAULT_METADATA_LEVEL {
190 metadata_version::STREAMS_VERSION_MAX
191 } else {
192 metadata_version::STREAMS_VERSION_MIN
193 }
194 }
195 }
200
201pub struct ElrVersionFeature;
213
214impl Feature for ElrVersionFeature {
215 fn name(&self) -> &'static str {
216 metadata_version::ELR_VERSION_FEATURE
217 }
218 fn supported_range(&self) -> (i16, i16) {
219 (
220 metadata_version::ELR_VERSION_MIN,
221 metadata_version::ELR_VERSION_MAX,
222 )
223 }
224 fn default_level(&self, bootstrap_mv: i16) -> i16 {
225 if bootstrap_mv >= metadata_version::ELR_DEFAULT_METADATA_LEVEL {
226 metadata_version::ELR_VERSION_MAX
227 } else {
228 metadata_version::ELR_VERSION_MIN
229 }
230 }
231 fn dependencies(&self, level: i16) -> &'static [(&'static str, i16)] {
232 if level >= 1 {
233 const ELRV_1_DEPENDENCIES: &[(&str, i16)] = &[(
234 metadata_version::METADATA_VERSION_FEATURE,
235 metadata_version::ELR_MIN_LEVEL,
236 )];
237 ELRV_1_DEPENDENCIES
238 } else {
239 &[]
240 }
241 }
242 }
246
247pub struct KRaftVersionFeature;
250
251impl Feature for KRaftVersionFeature {
252 fn name(&self) -> &'static str {
253 metadata_version::KRAFT_VERSION_FEATURE
254 }
255 fn supported_range(&self) -> (i16, i16) {
256 (0, 1)
257 }
258 fn default_level(&self, _bootstrap_mv: i16) -> i16 {
259 0
260 }
261}
262
263pub struct KrabkaVersionFeature;
269
270impl Feature for KrabkaVersionFeature {
271 fn name(&self) -> &'static str {
272 krabka_version::KRABKA_VERSION_FEATURE
273 }
274 fn supported_range(&self) -> (i16, i16) {
275 (
276 krabka_version::KRABKA_VERSION_MIN,
277 krabka_version::KRABKA_VERSION_MAX,
278 )
279 }
280 fn default_level(&self, _bootstrap_mv: i16) -> i16 {
281 krabka_version::KRABKA_VERSION_MIN
282 }
283 }
289
290#[must_use]
292pub fn feature_registry() -> &'static [&'static dyn Feature] {
293 const REGISTRY: &[&dyn Feature] = &[
294 &MetadataVersionFeature,
295 &GroupVersionFeature,
296 &TransactionVersionFeature,
297 &ShareVersionFeature,
298 &StreamsVersionFeature,
299 &ElrVersionFeature,
300 &KRaftVersionFeature,
301 &KrabkaVersionFeature,
302 ];
303 REGISTRY
304}
305
306#[must_use]
309pub fn supported_feature_ranges() -> BTreeMap<String, (i16, i16)> {
310 let mut ranges: BTreeMap<_, _> = feature_registry()
311 .iter()
312 .map(|feature| (feature.name().to_owned(), feature.supported_range()))
313 .collect();
314 ranges.insert(
319 metadata_version::METADATA_DOWNGRADE_CAPABILITY_FEATURE.into(),
320 (
321 metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
322 metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
323 ),
324 );
325 ranges
326}
327
328#[must_use]
330pub fn feature(name: &str) -> Option<&'static dyn Feature> {
331 feature_registry()
332 .iter()
333 .copied()
334 .find(|f| f.name() == name)
335}
336
337#[must_use]
344pub fn bootstrap_feature_records(bootstrap_mv: i16) -> Vec<crate::MetadataRecord> {
345 bootstrap_feature_records_with_overrides(bootstrap_mv, &BTreeMap::new())
346}
347
348#[must_use]
359pub fn bootstrap_feature_records_with_overrides(
360 bootstrap_mv: i16,
361 overrides: &BTreeMap<String, i16>,
362) -> Vec<crate::MetadataRecord> {
363 feature_registry()
364 .iter()
365 .filter(|f| f.name() != metadata_version::KRAFT_VERSION_FEATURE)
366 .filter_map(|f| {
367 let level = overrides
368 .get(f.name())
369 .copied()
370 .unwrap_or_else(|| f.default_level(bootstrap_mv));
371 (level > 0).then(|| {
372 crate::MetadataRecord::V1FeatureLevel(crate::FeatureLevelRecord {
373 name: f.name().to_owned(),
374 level,
375 })
376 })
377 })
378 .collect()
379}
380
381pub fn validate_feature_dependencies(resolved: &BTreeMap<String, i16>) -> Result<(), String> {
392 check_deps(resolved, |name, level| {
393 feature(name).map_or(&[][..], |f| f.dependencies(level))
394 })
395}
396
397fn check_deps(
401 resolved: &BTreeMap<String, i16>,
402 deps_of: impl Fn(&str, i16) -> &'static [(&'static str, i16)],
403) -> Result<(), String> {
404 for (name, &level) in resolved {
405 for &(dep, min) in deps_of(name, level) {
406 let have = resolved.get(dep).copied().unwrap_or(0);
407 if have < min {
408 return Err(format!(
409 "feature {name}={level} requires {dep}>={min}, but {dep} is finalized at {have}"
410 ));
411 }
412 }
413 }
414 Ok(())
415}
416
417#[must_use]
420pub fn is_supported_level(name: &str, level: i16) -> bool {
421 feature(name).is_some_and(|f| {
422 let (min, max) = f.supported_range();
423 (min..=max).contains(&level)
424 })
425}
426
427#[cfg(test)]
428mod tests {
429 use std::collections::BTreeMap;
430
431 use assert2::check;
432
433 use super::*;
434
435 #[test]
436 fn registry_contains_metadata_version() {
437 let f = feature("metadata.version").expect("registered");
438 assert2::assert!(f.supported_range() == (7, 34));
439 assert2::assert!(feature("not.a.feature").is_none());
440 }
441
442 #[test]
443 fn metadata_version_default_is_the_bootstrap_level_clamped() {
444 let f = feature("metadata.version").unwrap();
445 for (_case, bootstrap, want) in [
446 ("maximum bootstrap", 34, 34),
447 ("minimum bootstrap", 7, 7),
448 ("above maximum", 99, 34),
449 ("below minimum", 1, 7),
450 ] {
451 assert2::assert!(f.default_level(bootstrap) == want);
452 }
453 }
454
455 #[test]
456 fn is_supported_level_checks_range() {
457 for (name, level, want) in [
458 ("metadata.version", 7, true),
459 ("metadata.version", 34, true),
460 ("metadata.version", 6, false),
461 ("metadata.version", 35, false),
462 ("transaction.version", 2, true),
464 ("transaction.version", 3, false),
465 ("krabka.version", 0, true),
466 ("krabka.version", 1, true),
467 ("krabka.version", 2, true),
468 ("krabka.version", 3, true),
469 ("krabka.version", 4, false),
470 ("not.a.feature", 1, false),
471 ] {
472 assert2::assert!(is_supported_level(name, level) == want);
473 }
474 }
475
476 #[test]
478 fn supported_feature_ranges_are_pinned() {
479 let want: BTreeMap<String, (i16, i16)> = [
480 ("eligible.leader.replicas.version", (0, 1)),
481 ("group.version", (0, 1)),
482 ("krabka.metadata.downgrade", (1, 1)),
483 ("krabka.version", (0, 3)),
484 ("kraft.version", (0, 1)),
485 ("metadata.version", (7, 34)),
486 ("share.version", (0, 2)),
487 ("streams.version", (0, 1)),
488 ("transaction.version", (0, 2)),
489 ]
490 .into_iter()
491 .map(|(name, range)| (name.to_owned(), range))
492 .collect();
493 check!(supported_feature_ranges() == want);
494 }
495
496 #[test]
499 fn krabka_version_bootstraps_at_zero() {
500 use crate::metadata_version::{METADATA_VERSION_MAX, METADATA_VERSION_MIN};
501 for (case, bootstrap, overrides, want) in [
502 ("minimum release", METADATA_VERSION_MIN, vec![], None),
503 ("latest release", METADATA_VERSION_MAX, vec![], None),
504 (
505 "override to 1 earns a record",
506 METADATA_VERSION_MAX,
507 vec![("krabka.version".to_owned(), 1)],
508 Some(1),
509 ),
510 (
511 "override to 0 omits the record",
512 METADATA_VERSION_MAX,
513 vec![("krabka.version".to_owned(), 0)],
514 None,
515 ),
516 ] {
517 let overrides: BTreeMap<String, i16> = overrides.into_iter().collect();
518 let levels = levels_of(&bootstrap_feature_records_with_overrides(
519 bootstrap, &overrides,
520 ));
521 check!(levels.get("krabka.version").copied() == want, "{case}");
522 }
523 }
524
525 #[test]
526 fn krabka_version_declares_no_dependencies_and_no_level_name() {
527 let f = feature("krabka.version").expect("registered");
528 check!((f.dependencies(0), f.dependencies(1), f.level_name(1)) == (&[][..], &[][..], None));
529 let resolved = BTreeMap::from([("krabka.version".to_owned(), 1)]);
530 check!(validate_feature_dependencies(&resolved) == Ok(()));
531 }
532
533 #[test]
534 fn downgrade_capability_is_registration_only() {
535 let ranges = supported_feature_ranges();
536 check!(
537 ranges.get(metadata_version::METADATA_DOWNGRADE_CAPABILITY_FEATURE)
538 == Some(&(
539 metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
540 metadata_version::METADATA_DOWNGRADE_CAPABILITY_LEVEL,
541 ))
542 );
543 check!(feature(metadata_version::METADATA_DOWNGRADE_CAPABILITY_FEATURE).is_none());
544 }
545
546 #[test]
547 fn metadata_version_level_name() {
548 let f = feature("metadata.version").unwrap();
549 for (_case, level, want) in [
550 ("latest level", 34, Some("4.5-IV0")),
551 ("controller unregistration level", 33, Some("4.4-IV2")),
552 ("CIDR ACL level", 32, Some("4.4-IV1")),
553 ("cordoned log dirs level", 30, Some("4.3-IV0")),
554 ("earliest level", 7, Some("3.3-IV3")),
555 ("unknown level", 99, None),
556 ] {
557 assert2::assert!(f.level_name(level) == want);
558 }
559 }
560
561 #[test]
562 fn group_version_registered_with_range() {
563 let f = feature("group.version").expect("registered");
564 assert2::assert!(f.supported_range() == (0, 1));
565 }
566
567 #[test]
568 fn registry_feature_contracts_are_pinned() {
569 let image = MetadataImage::new(uuid::Uuid::nil());
570 let expected = [
571 ("metadata.version", (7, 34), 25, 7),
572 ("group.version", (0, 1), 1, 0),
573 ("transaction.version", (0, 2), 2, 0),
574 ("share.version", (0, 2), 0, 0),
575 ("streams.version", (0, 1), 0, 0),
576 ("eligible.leader.replicas.version", (0, 1), 0, 0),
577 ("kraft.version", (0, 1), 0, 0),
581 ("krabka.version", (0, 3), 0, 0),
582 ];
583
584 for (name, range, default_at_25, floor) in expected {
585 let f = feature(name).expect("registered");
586 check!(
587 (
588 f.supported_range(),
589 f.default_level(25),
590 f.min_required_floor(&image),
591 ) == (range, default_at_25, floor),
592 "feature {name}"
593 );
594 }
595 }
596
597 #[test]
598 fn group_version_default_follows_release() {
599 let f = feature("group.version").unwrap();
600 for (_case, bootstrap, want) in [
601 (
602 "before GA threshold",
603 group_version::GROUP_VERSION_GA_METADATA_LEVEL - 1,
604 0,
605 ),
606 (
607 "at GA threshold",
608 group_version::GROUP_VERSION_GA_METADATA_LEVEL,
609 1,
610 ),
611 ("after GA threshold", 25, 1),
612 ] {
613 assert2::assert!(f.default_level(bootstrap) == want);
614 }
615 }
616
617 #[test]
618 fn group_version_declares_no_hard_dependencies() {
619 let f = feature("group.version").unwrap();
620 assert2::assert!(
621 (f.dependencies(0).is_empty(), f.dependencies(1).is_empty()) == (true, true)
622 );
623 }
624
625 #[test]
626 fn transaction_version_registered() {
627 let f = feature("transaction.version").expect("registered");
628 assert2::assert!(f.supported_range() == (0, 2));
629 }
630
631 #[test]
632 fn streams_version_registered_with_no_dependencies() {
633 let f = feature("streams.version").expect("registered");
634 check!((f.supported_range(), f.dependencies(1).is_empty()) == ((0, 1), true));
635 }
636
637 #[test]
640 fn opt_in_feature_defaults_follow_kafka_bootstrap_levels() {
641 use crate::metadata_version::{
642 ELR_DEFAULT_METADATA_LEVEL, METADATA_VERSION_MAX,
643 SHARE_VERSION_2_DEFAULT_METADATA_LEVEL, SHARE_VERSION_DEFAULT_METADATA_LEVEL,
644 STREAMS_VERSION_DEFAULT_METADATA_LEVEL,
645 };
646 for (name, bootstrap, want) in [
647 ("eligible.leader.replicas.version", 25, 0),
648 (
649 "eligible.leader.replicas.version",
650 ELR_DEFAULT_METADATA_LEVEL - 1,
651 0,
652 ),
653 (
654 "eligible.leader.replicas.version",
655 ELR_DEFAULT_METADATA_LEVEL,
656 1,
657 ),
658 ("eligible.leader.replicas.version", METADATA_VERSION_MAX, 1),
659 ("share.version", SHARE_VERSION_DEFAULT_METADATA_LEVEL - 1, 0),
660 ("share.version", SHARE_VERSION_DEFAULT_METADATA_LEVEL, 1),
661 (
662 "share.version",
663 SHARE_VERSION_2_DEFAULT_METADATA_LEVEL - 1,
664 1,
665 ),
666 ("share.version", SHARE_VERSION_2_DEFAULT_METADATA_LEVEL, 2),
667 ("share.version", METADATA_VERSION_MAX, 2),
668 (
669 "streams.version",
670 STREAMS_VERSION_DEFAULT_METADATA_LEVEL - 1,
671 0,
672 ),
673 ("streams.version", STREAMS_VERSION_DEFAULT_METADATA_LEVEL, 1),
674 ("streams.version", METADATA_VERSION_MAX, 1),
675 ] {
676 check!(
677 feature(name).unwrap().default_level(bootstrap) == want,
678 "feature {name} at bootstrap level {bootstrap}"
679 );
680 }
681 }
682
683 #[test]
684 fn elr_version_is_opt_in_and_depends_on_the_elr_metadata_level() {
685 let f = feature("eligible.leader.replicas.version").expect("registered");
686 check!(
688 (
689 f.supported_range(),
690 f.default_level(metadata_version::METADATA_VERSION_MAX),
691 f.dependencies(0),
692 f.dependencies(1),
693 ) == (
694 (0, 1),
695 1,
696 &[][..],
697 &[(
698 metadata_version::METADATA_VERSION_FEATURE,
699 metadata_version::ELR_MIN_LEVEL,
700 )][..],
701 )
702 );
703 }
704
705 #[test]
706 fn elr_version_level_one_requires_the_elr_metadata_version() {
707 for (_case, metadata_level, want_err) in [
708 ("below 4.0-IV1", metadata_version::ELR_MIN_LEVEL - 1, true),
709 ("at 4.0-IV1", metadata_version::ELR_MIN_LEVEL, false),
710 (
711 "above 4.0-IV1",
712 metadata_version::METADATA_VERSION_MAX,
713 false,
714 ),
715 ] {
716 let resolved = BTreeMap::from([
717 (
718 metadata_version::METADATA_VERSION_FEATURE.to_owned(),
719 metadata_level,
720 ),
721 (metadata_version::ELR_VERSION_FEATURE.to_owned(), 1),
722 ]);
723 check!(validate_feature_dependencies(&resolved).is_err() == want_err);
724 }
725 }
726
727 #[test]
728 fn transaction_version_default_jumps_to_two_at_4_0_iv2() {
729 let f = feature("transaction.version").unwrap();
730 for (_case, bootstrap, want) in [
731 ("below activation", 23, 0),
732 ("at activation", 24, 2),
733 ("after activation", 25, 2),
734 ] {
735 assert2::assert!(f.default_level(bootstrap) == want);
736 }
737 }
738
739 #[test]
740 fn transaction_version_declares_no_hard_dependencies() {
741 let f = feature("transaction.version").unwrap();
742 for level in [0, 1, 2] {
743 assert2::assert!(f.dependencies(level).is_empty());
744 }
745 }
746
747 #[test]
748 fn transaction_version_ga_threshold_is_4_0_iv2() {
749 assert2::assert!(
752 metadata_version::from_feature_level(transaction_version::TV2_METADATA_LEVEL)
753 .unwrap()
754 .ivn()
755 == "4.0-IV2"
756 );
757 }
758
759 fn levels_of(recs: &[crate::MetadataRecord]) -> BTreeMap<String, i16> {
760 recs.iter()
761 .filter_map(|r| match r {
762 crate::MetadataRecord::V1FeatureLevel(f) => Some((f.name.clone(), f.level)),
763 _ => None,
764 })
765 .collect()
766 }
767
768 #[test]
769 fn bootstrap_omits_level_zero_features() {
770 let levels = levels_of(&bootstrap_feature_records(25));
773 assert2::assert!(
774 levels
775 == BTreeMap::from([
776 ("metadata.version".to_owned(), 25),
777 ("group.version".to_owned(), 1),
778 ("transaction.version".to_owned(), 2),
779 ])
780 );
781 }
782
783 #[test]
786 fn bootstrap_at_the_latest_level_enables_every_released_feature() {
787 let want: Vec<crate::MetadataRecord> = [
788 ("metadata.version", 34),
789 ("group.version", 1),
790 ("transaction.version", 2),
791 ("share.version", 2),
792 ("streams.version", 1),
793 ("eligible.leader.replicas.version", 1),
794 ]
795 .into_iter()
796 .map(|(name, level)| {
797 crate::MetadataRecord::V1FeatureLevel(crate::FeatureLevelRecord {
798 name: name.to_owned(),
799 level,
800 })
801 })
802 .collect();
803 check!(bootstrap_feature_records(metadata_version::METADATA_VERSION_MAX) == want);
804 }
805
806 #[test]
807 fn bootstrap_with_overrides_applies_explicit_levels() {
808 let mut ov = BTreeMap::new();
810 ov.insert("group.version".to_owned(), 0i16);
811 let levels = levels_of(&bootstrap_feature_records_with_overrides(25, &ov));
812 assert2::assert!(
813 levels
814 == BTreeMap::from([
815 ("metadata.version".to_owned(), 25),
816 ("transaction.version".to_owned(), 2),
817 ])
818 );
819 }
820
821 #[test]
822 fn bootstrap_override_can_enable_an_opt_in_feature() {
823 let mut ov = BTreeMap::new();
826 ov.insert("streams.version".to_owned(), 1i16);
827 let levels = levels_of(&bootstrap_feature_records_with_overrides(25, &ov));
828 assert2::assert!(levels.get("streams.version") == Some(&1));
829 }
830
831 #[test]
832 fn bootstrap_unlisted_feature_follows_bootstrap_mv() {
833 let levels = levels_of(&bootstrap_feature_records_with_overrides(
836 23,
837 &BTreeMap::new(),
838 ));
839 assert2::assert!(
840 levels
841 == BTreeMap::from([
842 ("metadata.version".to_owned(), 23),
843 ("group.version".to_owned(), 1),
844 ])
845 );
846 }
847
848 #[test]
849 fn validate_dependencies_ok_for_real_registry() {
850 let mut resolved = BTreeMap::new();
852 resolved.insert("metadata.version".to_owned(), 25i16);
853 resolved.insert("group.version".to_owned(), 1i16);
854 resolved.insert("transaction.version".to_owned(), 2i16);
855 assert2::assert!(validate_feature_dependencies(&resolved).is_ok());
856 }
857
858 #[test]
859 fn check_deps_enforces_minimum_dependency_levels() {
860 fn deps_of(name: &str, level: i16) -> &'static [(&'static str, i16)] {
862 match (name, level) {
863 ("b", 1) => &[("a", 2)],
864 _ => &[],
865 }
866 }
867 let mut ok = BTreeMap::new();
868 ok.insert("a".to_owned(), 2i16);
869 ok.insert("b".to_owned(), 1i16);
870 assert2::assert!(check_deps(&ok, deps_of).is_ok());
871
872 let mut too_low = BTreeMap::new();
873 too_low.insert("a".to_owned(), 1i16);
874 too_low.insert("b".to_owned(), 1i16);
875 assert2::assert!(check_deps(&too_low, deps_of).is_err());
876
877 let mut missing = BTreeMap::new();
879 missing.insert("b".to_owned(), 1i16);
880 assert2::assert!(check_deps(&missing, deps_of).is_err());
881 }
882
883 #[test]
884 fn group_version_ga_threshold_is_4_0_iv0() {
885 assert2::assert!(
889 metadata_version::from_feature_level(group_version::GROUP_VERSION_GA_METADATA_LEVEL)
890 .unwrap()
891 .ivn()
892 == "4.0-IV0"
893 );
894 }
895
896 #[test]
906 fn default_min_required_floor_is_supported_min() {
907 let img = MetadataImage::new(uuid::Uuid::nil());
909 assert2::assert!(feature("group.version").unwrap().min_required_floor(&img) == 0);
910 }
911
912 #[test]
913 fn metadata_version_min_required_floor_tracks_image() {
914 let img = MetadataImage::new(uuid::Uuid::nil());
916 let mv = feature("metadata.version").unwrap();
917 assert2::assert!(mv.min_required_floor(&img) == 7);
919 assert2::assert!(img.min_required_metadata_version() == 7);
920 }
921
922 #[test]
923 fn plain_features_have_no_level_name() {
924 assert2::assert!(feature("group.version").unwrap().level_name(1).is_none());
926 assert2::assert!(
927 feature("transaction.version")
928 .unwrap()
929 .level_name(2)
930 .is_none()
931 );
932 }
933
934 #[test]
935 fn share_version_accessors() {
936 let f = feature("share.version").expect("registered");
937 check!(
939 (f.name(), f.supported_range(), f.default_level(25)) == ("share.version", (0, 2), 0)
940 );
941 }
942}