1use rvm_types::{OwnedRegionId, RvmError, RvmResult};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[repr(u8)]
20pub enum Tier {
21 Hot = 0,
24 Warm = 1,
27 Dormant = 2,
30 Cold = 3,
33}
34
35impl Tier {
36 #[must_use]
38 pub const fn index(self) -> u8 {
39 self as u8
40 }
41
42 #[must_use]
44 pub const fn from_u8(val: u8) -> Option<Self> {
45 match val {
46 0 => Some(Self::Hot),
47 1 => Some(Self::Warm),
48 2 => Some(Self::Dormant),
49 3 => Some(Self::Cold),
50 _ => None,
51 }
52 }
53}
54
55pub struct TierThresholds {
58 pub hot_to_warm: u16,
61 pub warm_to_dormant: u16,
63 pub dormant_to_cold: u16,
65 pub warm_to_hot: u16,
67 pub dormant_to_warm: u16,
69}
70
71impl TierThresholds {
72 pub const DEFAULT: Self = Self {
74 hot_to_warm: 7_000,
75 warm_to_dormant: 4_000,
76 dormant_to_cold: 1_000,
77 warm_to_hot: 8_000,
78 dormant_to_warm: 5_000,
79 };
80}
81
82#[derive(Debug, Clone, Copy)]
84pub struct RegionTierState {
85 pub region_id: OwnedRegionId,
87 pub tier: Tier,
89 pub last_access_epoch: u32,
91 pub cut_value: u16,
94 pub recency_score: u16,
96 occupied: bool,
98}
99
100impl RegionTierState {
101 const EMPTY: Self = Self {
103 region_id: OwnedRegionId::new(0),
104 tier: Tier::Warm,
105 last_access_epoch: 0,
106 cut_value: 0,
107 recency_score: 0,
108 occupied: false,
109 };
110
111 #[must_use]
114 pub const fn residency_score(self) -> u16 {
115 self.cut_value.saturating_add(self.recency_score)
116 }
117}
118
119pub struct TierManager<const MAX_REGIONS: usize> {
124 regions: [RegionTierState; MAX_REGIONS],
126 count: usize,
128 thresholds: TierThresholds,
130 current_epoch: u32,
132}
133
134impl<const MAX_REGIONS: usize> Default for TierManager<MAX_REGIONS> {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl<const MAX_REGIONS: usize> TierManager<MAX_REGIONS> {
141 #[must_use]
143 pub const fn new() -> Self {
144 Self {
145 regions: [RegionTierState::EMPTY; MAX_REGIONS],
146 count: 0,
147 thresholds: TierThresholds::DEFAULT,
148 current_epoch: 0,
149 }
150 }
151
152 #[must_use]
154 pub const fn with_thresholds(thresholds: TierThresholds) -> Self {
155 Self {
156 regions: [RegionTierState::EMPTY; MAX_REGIONS],
157 count: 0,
158 thresholds,
159 current_epoch: 0,
160 }
161 }
162
163 #[must_use]
165 pub const fn count(&self) -> usize {
166 self.count
167 }
168
169 #[must_use]
171 pub const fn current_epoch(&self) -> u32 {
172 self.current_epoch
173 }
174
175 pub fn advance_epoch(&mut self) {
177 self.current_epoch = self.current_epoch.saturating_add(1);
178 }
179
180 pub fn register(&mut self, region_id: OwnedRegionId, initial_tier: Tier) -> RvmResult<()> {
187 if self.count >= MAX_REGIONS {
188 return Err(RvmError::ResourceLimitExceeded);
189 }
190 if self.find_slot(region_id).is_some() {
192 return Err(RvmError::MemoryOverlap);
193 }
194 for slot in &mut self.regions {
196 if !slot.occupied {
197 *slot = RegionTierState {
198 region_id,
199 tier: initial_tier,
200 last_access_epoch: self.current_epoch,
201 cut_value: 0,
202 recency_score: 5_000, occupied: true,
204 };
205 self.count += 1;
206 return Ok(());
207 }
208 }
209 Err(RvmError::ResourceLimitExceeded)
210 }
211
212 pub fn unregister(&mut self, region_id: OwnedRegionId) -> RvmResult<()> {
218 match self.find_slot(region_id) {
219 Some(idx) => {
220 self.regions[idx] = RegionTierState::EMPTY;
221 self.count -= 1;
222 Ok(())
223 }
224 None => Err(RvmError::PartitionNotFound),
225 }
226 }
227
228 pub fn record_access(&mut self, region_id: OwnedRegionId) -> RvmResult<()> {
234 match self.find_slot(region_id) {
235 Some(idx) => {
236 self.regions[idx].last_access_epoch = self.current_epoch;
237 self.regions[idx].recency_score = self.regions[idx]
239 .recency_score
240 .saturating_add(1_000)
241 .min(10_000);
242 Ok(())
243 }
244 None => Err(RvmError::PartitionNotFound),
245 }
246 }
247
248 pub fn update_cut_value(&mut self, region_id: OwnedRegionId, cut_value: u16) -> RvmResult<()> {
254 match self.find_slot(region_id) {
255 Some(idx) => {
256 self.regions[idx].cut_value = cut_value.min(10_000);
257 Ok(())
258 }
259 None => Err(RvmError::PartitionNotFound),
260 }
261 }
262
263 pub fn promote(&mut self, region_id: OwnedRegionId, target_tier: Tier) -> RvmResult<Tier> {
275 let idx = self
276 .find_slot(region_id)
277 .ok_or(RvmError::PartitionNotFound)?;
278 let current = self.regions[idx].tier;
279
280 if target_tier >= current {
282 return Err(RvmError::InvalidTierTransition);
283 }
284 if current == Tier::Cold {
286 return Err(RvmError::InvalidTierTransition);
287 }
288 let score = self.regions[idx].residency_score();
290 let threshold = self.promotion_threshold(target_tier);
291 if score < threshold {
292 return Err(RvmError::CoherenceBelowThreshold);
293 }
294
295 let old_tier = self.regions[idx].tier;
296 self.regions[idx].tier = target_tier;
297 self.regions[idx].last_access_epoch = self.current_epoch;
298 Ok(old_tier)
299 }
300
301 pub fn demote(&mut self, region_id: OwnedRegionId, target_tier: Tier) -> RvmResult<Tier> {
311 let idx = self
312 .find_slot(region_id)
313 .ok_or(RvmError::PartitionNotFound)?;
314 let current = self.regions[idx].tier;
315
316 if target_tier <= current {
318 return Err(RvmError::InvalidTierTransition);
319 }
320
321 let old_tier = self.regions[idx].tier;
322 self.regions[idx].tier = target_tier;
323 Ok(old_tier)
324 }
325
326 #[must_use]
328 pub fn get(&self, region_id: OwnedRegionId) -> Option<&RegionTierState> {
329 self.find_slot(region_id).map(|idx| &self.regions[idx])
330 }
331
332 pub fn decay_recency(&mut self, decay_amount: u16) {
335 for slot in &mut self.regions {
336 if slot.occupied {
337 slot.recency_score = slot.recency_score.saturating_sub(decay_amount);
338 }
339 }
340 }
341
342 pub fn find_demotion_candidates(&self, out: &mut [(OwnedRegionId, Tier)]) -> usize {
350 let mut written = 0;
351 for slot in &self.regions {
352 if !slot.occupied || written >= out.len() {
353 continue;
354 }
355 let score = slot.residency_score();
356 let target = match slot.tier {
357 Tier::Hot if score < self.thresholds.hot_to_warm => Some(Tier::Warm),
358 Tier::Warm if score < self.thresholds.warm_to_dormant => Some(Tier::Dormant),
359 Tier::Dormant if score < self.thresholds.dormant_to_cold => Some(Tier::Cold),
360 _ => None,
361 };
362 if let Some(target_tier) = target {
363 out[written] = (slot.region_id, target_tier);
364 written += 1;
365 }
366 }
367 written
368 }
369
370 fn find_slot(&self, region_id: OwnedRegionId) -> Option<usize> {
374 self.regions
375 .iter()
376 .position(|s| s.occupied && s.region_id == region_id)
377 }
378
379 const fn promotion_threshold(&self, target: Tier) -> u16 {
381 match target {
382 Tier::Hot => self.thresholds.warm_to_hot,
383 Tier::Warm => self.thresholds.dormant_to_warm,
384 Tier::Dormant | Tier::Cold => u16::MAX,
386 }
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 fn rid(id: u64) -> OwnedRegionId {
395 OwnedRegionId::new(id)
396 }
397
398 #[test]
399 fn tier_from_u8_round_trips() {
400 for val in 0..=3u8 {
401 let tier = Tier::from_u8(val).unwrap();
402 assert_eq!(tier.index(), val);
403 }
404 assert!(Tier::from_u8(4).is_none());
405 assert!(Tier::from_u8(255).is_none());
406 }
407
408 #[test]
409 fn tier_ordering() {
410 assert!(Tier::Hot < Tier::Warm);
411 assert!(Tier::Warm < Tier::Dormant);
412 assert!(Tier::Dormant < Tier::Cold);
413 }
414
415 #[test]
416 fn register_and_get() {
417 let mut mgr = TierManager::<8>::new();
418 mgr.register(rid(1), Tier::Warm).unwrap();
419 assert_eq!(mgr.count(), 1);
420
421 let state = mgr.get(rid(1)).unwrap();
422 assert_eq!(state.tier, Tier::Warm);
423 assert_eq!(state.region_id, rid(1));
424 assert!(state.occupied);
425 }
426
427 #[test]
428 fn register_duplicate_fails() {
429 let mut mgr = TierManager::<8>::new();
430 mgr.register(rid(1), Tier::Warm).unwrap();
431 assert_eq!(
432 mgr.register(rid(1), Tier::Hot),
433 Err(RvmError::MemoryOverlap)
434 );
435 }
436
437 #[test]
438 fn register_at_capacity_fails() {
439 let mut mgr = TierManager::<2>::new();
440 mgr.register(rid(1), Tier::Warm).unwrap();
441 mgr.register(rid(2), Tier::Warm).unwrap();
442 assert_eq!(
443 mgr.register(rid(3), Tier::Warm),
444 Err(RvmError::ResourceLimitExceeded)
445 );
446 }
447
448 #[test]
449 fn unregister_frees_slot() {
450 let mut mgr = TierManager::<4>::new();
451 mgr.register(rid(1), Tier::Warm).unwrap();
452 mgr.register(rid(2), Tier::Hot).unwrap();
453 assert_eq!(mgr.count(), 2);
454
455 mgr.unregister(rid(1)).unwrap();
456 assert_eq!(mgr.count(), 1);
457 assert!(mgr.get(rid(1)).is_none());
458
459 mgr.register(rid(3), Tier::Dormant).unwrap();
461 assert_eq!(mgr.count(), 2);
462 }
463
464 #[test]
465 fn unregister_nonexistent_fails() {
466 let mut mgr = TierManager::<4>::new();
467 assert_eq!(mgr.unregister(rid(99)), Err(RvmError::PartitionNotFound));
468 }
469
470 #[test]
471 fn promote_warm_to_hot() {
472 let mut mgr = TierManager::<4>::new();
473 mgr.register(rid(1), Tier::Warm).unwrap();
474 assert_eq!(
477 mgr.promote(rid(1), Tier::Hot),
478 Err(RvmError::CoherenceBelowThreshold)
479 );
480
481 mgr.update_cut_value(rid(1), 4_000).unwrap();
483 let old = mgr.promote(rid(1), Tier::Hot).unwrap();
485 assert_eq!(old, Tier::Warm);
486 assert_eq!(mgr.get(rid(1)).unwrap().tier, Tier::Hot);
487 }
488
489 #[test]
490 fn promote_dormant_to_warm() {
491 let mut mgr = TierManager::<4>::new();
492 mgr.register(rid(1), Tier::Dormant).unwrap();
493 let old = mgr.promote(rid(1), Tier::Warm).unwrap();
496 assert_eq!(old, Tier::Dormant);
497 assert_eq!(mgr.get(rid(1)).unwrap().tier, Tier::Warm);
498 }
499
500 #[test]
501 fn promote_cold_always_fails() {
502 let mut mgr = TierManager::<4>::new();
503 mgr.register(rid(1), Tier::Cold).unwrap();
504 mgr.update_cut_value(rid(1), 10_000).unwrap();
505 assert_eq!(
506 mgr.promote(rid(1), Tier::Dormant),
507 Err(RvmError::InvalidTierTransition)
508 );
509 }
510
511 #[test]
512 fn promote_same_tier_fails() {
513 let mut mgr = TierManager::<4>::new();
514 mgr.register(rid(1), Tier::Warm).unwrap();
515 assert_eq!(
516 mgr.promote(rid(1), Tier::Warm),
517 Err(RvmError::InvalidTierTransition)
518 );
519 }
520
521 #[test]
522 fn promote_to_lower_tier_fails() {
523 let mut mgr = TierManager::<4>::new();
524 mgr.register(rid(1), Tier::Warm).unwrap();
525 assert_eq!(
526 mgr.promote(rid(1), Tier::Dormant),
527 Err(RvmError::InvalidTierTransition)
528 );
529 }
530
531 #[test]
532 fn demote_hot_to_warm() {
533 let mut mgr = TierManager::<4>::new();
534 mgr.register(rid(1), Tier::Hot).unwrap();
535 let old = mgr.demote(rid(1), Tier::Warm).unwrap();
536 assert_eq!(old, Tier::Hot);
537 assert_eq!(mgr.get(rid(1)).unwrap().tier, Tier::Warm);
538 }
539
540 #[test]
541 fn demote_to_higher_tier_fails() {
542 let mut mgr = TierManager::<4>::new();
543 mgr.register(rid(1), Tier::Warm).unwrap();
544 assert_eq!(
545 mgr.demote(rid(1), Tier::Hot),
546 Err(RvmError::InvalidTierTransition)
547 );
548 }
549
550 #[test]
551 fn demote_same_tier_fails() {
552 let mut mgr = TierManager::<4>::new();
553 mgr.register(rid(1), Tier::Warm).unwrap();
554 assert_eq!(
555 mgr.demote(rid(1), Tier::Warm),
556 Err(RvmError::InvalidTierTransition)
557 );
558 }
559
560 #[test]
561 fn demote_warm_to_cold_skipping_dormant() {
562 let mut mgr = TierManager::<4>::new();
563 mgr.register(rid(1), Tier::Warm).unwrap();
564 let old = mgr.demote(rid(1), Tier::Cold).unwrap();
565 assert_eq!(old, Tier::Warm);
566 assert_eq!(mgr.get(rid(1)).unwrap().tier, Tier::Cold);
567 }
568
569 #[test]
570 fn record_access_boosts_recency() {
571 let mut mgr = TierManager::<4>::new();
572 mgr.register(rid(1), Tier::Warm).unwrap();
573 let before = mgr.get(rid(1)).unwrap().recency_score;
574
575 mgr.record_access(rid(1)).unwrap();
576 let after = mgr.get(rid(1)).unwrap().recency_score;
577 assert!(after > before);
578 }
579
580 #[test]
581 fn record_access_nonexistent_fails() {
582 let mut mgr = TierManager::<4>::new();
583 assert_eq!(mgr.record_access(rid(99)), Err(RvmError::PartitionNotFound));
584 }
585
586 #[test]
587 fn decay_recency_reduces_scores() {
588 let mut mgr = TierManager::<4>::new();
589 mgr.register(rid(1), Tier::Warm).unwrap();
590 let before = mgr.get(rid(1)).unwrap().recency_score;
591
592 mgr.decay_recency(1_000);
593 let after = mgr.get(rid(1)).unwrap().recency_score;
594 assert_eq!(after, before - 1_000);
595 }
596
597 #[test]
598 fn decay_recency_saturates_at_zero() {
599 let mut mgr = TierManager::<4>::new();
600 mgr.register(rid(1), Tier::Warm).unwrap();
601 mgr.decay_recency(20_000); assert_eq!(mgr.get(rid(1)).unwrap().recency_score, 0);
603 }
604
605 #[test]
606 fn find_demotion_candidates() {
607 let mut mgr = TierManager::<8>::new();
608 mgr.register(rid(1), Tier::Hot).unwrap();
609 mgr.register(rid(2), Tier::Warm).unwrap();
610 mgr.register(rid(3), Tier::Dormant).unwrap();
611
612 mgr.decay_recency(10_000);
614
615 let mut buf = [(OwnedRegionId::new(0), Tier::Hot); 8];
616 let n = mgr.find_demotion_candidates(&mut buf);
617
618 assert_eq!(n, 3);
620
621 let candidates: &[(OwnedRegionId, Tier)] = &buf[..n];
623 assert!(candidates
624 .iter()
625 .any(|(id, t)| *id == rid(1) && *t == Tier::Warm));
626 assert!(candidates
627 .iter()
628 .any(|(id, t)| *id == rid(2) && *t == Tier::Dormant));
629 assert!(candidates
630 .iter()
631 .any(|(id, t)| *id == rid(3) && *t == Tier::Cold));
632 }
633
634 #[test]
635 fn advance_epoch() {
636 let mut mgr = TierManager::<4>::new();
637 assert_eq!(mgr.current_epoch(), 0);
638 mgr.advance_epoch();
639 assert_eq!(mgr.current_epoch(), 1);
640 mgr.advance_epoch();
641 assert_eq!(mgr.current_epoch(), 2);
642 }
643
644 #[test]
645 fn residency_score_computation() {
646 let state = RegionTierState {
647 region_id: rid(1),
648 tier: Tier::Warm,
649 last_access_epoch: 0,
650 cut_value: 3_000,
651 recency_score: 4_000,
652 occupied: true,
653 };
654 assert_eq!(state.residency_score(), 7_000);
655 }
656
657 #[test]
658 fn residency_score_saturates() {
659 let state = RegionTierState {
660 region_id: rid(1),
661 tier: Tier::Warm,
662 last_access_epoch: 0,
663 cut_value: 60_000,
664 recency_score: 60_000,
665 occupied: true,
666 };
667 assert_eq!(state.residency_score(), u16::MAX);
668 }
669
670 #[test]
671 fn residency_score_no_overflow_within_range() {
672 let state = RegionTierState {
673 region_id: rid(1),
674 tier: Tier::Warm,
675 last_access_epoch: 0,
676 cut_value: 10_000,
677 recency_score: 10_000,
678 occupied: true,
679 };
680 assert_eq!(state.residency_score(), 20_000);
681 }
682
683 #[test]
684 fn update_cut_value_clamps() {
685 let mut mgr = TierManager::<4>::new();
686 mgr.register(rid(1), Tier::Warm).unwrap();
687 mgr.update_cut_value(rid(1), 50_000).unwrap();
688 assert_eq!(mgr.get(rid(1)).unwrap().cut_value, 10_000);
689 }
690
691 #[test]
696 fn dc1_fallback_cut_value_stays_zero_without_coherence() {
697 let mut mgr = TierManager::<4>::new();
700 mgr.register(rid(1), Tier::Warm).unwrap();
701
702 let state = mgr.get(rid(1)).unwrap();
703 assert_eq!(state.cut_value, 0);
704 assert_eq!(state.residency_score(), 5_000);
706 }
707
708 #[test]
709 fn dc1_fallback_promotion_blocked_by_low_recency() {
710 let mut mgr = TierManager::<4>::new();
713 mgr.register(rid(1), Tier::Warm).unwrap();
714 assert_eq!(
715 mgr.promote(rid(1), Tier::Hot),
716 Err(RvmError::CoherenceBelowThreshold)
717 );
718 }
719
720 #[test]
721 fn dc1_fallback_promotion_possible_with_high_recency() {
722 let mut mgr = TierManager::<4>::new();
723 mgr.register(rid(1), Tier::Warm).unwrap();
724
725 mgr.record_access(rid(1)).unwrap();
728 mgr.record_access(rid(1)).unwrap();
729 mgr.record_access(rid(1)).unwrap();
730 let state = mgr.get(rid(1)).unwrap();
731 assert_eq!(state.recency_score, 8_000);
732 assert_eq!(state.residency_score(), 8_000); let old = mgr.promote(rid(1), Tier::Hot).unwrap();
736 assert_eq!(old, Tier::Warm);
737 }
738
739 #[test]
740 fn dc1_fallback_demotion_on_decay() {
741 let mut mgr = TierManager::<8>::new();
742 mgr.register(rid(1), Tier::Hot).unwrap();
743
744 let mut buf = [(OwnedRegionId::new(0), Tier::Hot); 8];
748 let n = mgr.find_demotion_candidates(&mut buf);
749 assert_eq!(n, 1);
750 assert_eq!(buf[0].0, rid(1));
751 assert_eq!(buf[0].1, Tier::Warm);
752 }
753
754 #[test]
755 fn dc1_fallback_warm_to_dormant_demotion_after_decay() {
756 let mut mgr = TierManager::<4>::new();
757 mgr.register(rid(1), Tier::Warm).unwrap();
758
759 mgr.decay_recency(2_000);
762 let mut buf = [(OwnedRegionId::new(0), Tier::Hot); 4];
763 let n = mgr.find_demotion_candidates(&mut buf);
764 assert_eq!(n, 1);
765 assert_eq!(buf[0].0, rid(1));
766 assert_eq!(buf[0].1, Tier::Dormant);
767 }
768
769 #[test]
770 fn dc1_fallback_dormant_to_cold_demotion() {
771 let mut mgr = TierManager::<4>::new();
772 mgr.register(rid(1), Tier::Dormant).unwrap();
773
774 mgr.decay_recency(5_000); let mut buf = [(OwnedRegionId::new(0), Tier::Hot); 4];
777 let n = mgr.find_demotion_candidates(&mut buf);
778 assert_eq!(n, 1);
779 assert_eq!(buf[0].0, rid(1));
780 assert_eq!(buf[0].1, Tier::Cold);
781 }
782
783 #[test]
784 fn recency_access_saturates_at_10000() {
785 let mut mgr = TierManager::<4>::new();
786 mgr.register(rid(1), Tier::Warm).unwrap();
787
788 for _ in 0..20 {
790 mgr.record_access(rid(1)).unwrap();
791 }
792 assert_eq!(mgr.get(rid(1)).unwrap().recency_score, 10_000);
793 }
794
795 #[test]
796 fn epoch_advance_is_monotonic() {
797 let mut mgr = TierManager::<4>::new();
798 for expected in 0..10u32 {
799 assert_eq!(mgr.current_epoch(), expected);
800 mgr.advance_epoch();
801 }
802 assert_eq!(mgr.current_epoch(), 10);
803 }
804
805 #[test]
806 fn registered_region_records_access_epoch() {
807 let mut mgr = TierManager::<4>::new();
808 mgr.advance_epoch();
809 mgr.advance_epoch(); mgr.register(rid(1), Tier::Warm).unwrap();
811 assert_eq!(mgr.get(rid(1)).unwrap().last_access_epoch, 2);
812
813 mgr.advance_epoch(); mgr.record_access(rid(1)).unwrap();
815 assert_eq!(mgr.get(rid(1)).unwrap().last_access_epoch, 3);
816 }
817
818 #[test]
819 fn find_demotion_candidates_respects_buffer_size() {
820 let mut mgr = TierManager::<8>::new();
821 for i in 1..=5u64 {
822 mgr.register(rid(i), Tier::Hot).unwrap();
823 }
824 let mut buf = [(OwnedRegionId::new(0), Tier::Hot); 2];
827 let n = mgr.find_demotion_candidates(&mut buf);
828 assert_eq!(n, 2); }
830
831 #[test]
832 fn cold_region_not_a_demotion_candidate() {
833 let mut mgr = TierManager::<4>::new();
834 mgr.register(rid(1), Tier::Cold).unwrap();
835 mgr.decay_recency(10_000);
836
837 let mut buf = [(OwnedRegionId::new(0), Tier::Hot); 4];
838 let n = mgr.find_demotion_candidates(&mut buf);
839 assert_eq!(n, 0);
841 }
842
843 #[test]
844 fn custom_thresholds() {
845 let thresholds = TierThresholds {
846 hot_to_warm: 9_000,
847 warm_to_dormant: 6_000,
848 dormant_to_cold: 3_000,
849 warm_to_hot: 9_500,
850 dormant_to_warm: 7_000,
851 };
852 let mgr = TierManager::<4>::with_thresholds(thresholds);
853 assert_eq!(mgr.count(), 0);
854 }
855
856 #[test]
857 fn update_cut_value_nonexistent_fails() {
858 let mut mgr = TierManager::<4>::new();
859 assert_eq!(
860 mgr.update_cut_value(rid(99), 5_000),
861 Err(RvmError::PartitionNotFound)
862 );
863 }
864
865 #[test]
866 fn promote_nonexistent_fails() {
867 let mut mgr = TierManager::<4>::new();
868 assert_eq!(
869 mgr.promote(rid(99), Tier::Hot),
870 Err(RvmError::PartitionNotFound)
871 );
872 }
873
874 #[test]
875 fn demote_nonexistent_fails() {
876 let mut mgr = TierManager::<4>::new();
877 assert_eq!(
878 mgr.demote(rid(99), Tier::Cold),
879 Err(RvmError::PartitionNotFound)
880 );
881 }
882}