aboutsummaryrefslogtreecommitdiffstats
path: root/tests/queue.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/queue.rs77
1 files changed, 77 insertions, 0 deletions
diff --git a/tests/queue.rs b/tests/queue.rs
index d338f2a..67cbabf 100644
--- a/tests/queue.rs
+++ b/tests/queue.rs
@@ -567,6 +567,83 @@ fn test_shuffle_off_keeps_unplayed_duplicate_entries() {
567 assert_eq!(q.position(), 1); 567 assert_eq!(q.position(), 1);
568} 568}
569 569
570#[test]
571fn test_shuffle_play_now_current_is_inserted_track() {
572 // BUG: under shuffle, `insert_after_current` re-shuffles the tail
573 // (`update_shuffle_indices`), so the inserted track's play-order slot is a
574 // random draw instead of `position + 1`. The daemon's "now" flow (insert →
575 // `set_position(pos+1)` → play `track_at(position)`, src/daemon.rs) then
576 // starts the right file while `current()`/`next()`/the queue display
577 // follow a *different* track, and auto-advance can replay or skip the
578 // inserted track entirely.
579 //
580 // Contract: after the "now" sequence, `current()` must be the inserted
581 // track. The seed sweep keeps the test deterministic — for a fixed seed the
582 // outcome never varies between runs, and pre-fix a given seed satisfies the
583 // invariant with probability 1/(tail size) = 1/3, so the chance all 40
584 // seeds accidentally pass is ~3e-19. The fix makes the invariant hold
585 // structurally (insert into the play order at `position+1`), independent
586 // of the seed.
587 let inserted = PathBuf::from("/music/new.mp3");
588 for seed in 1..=40u64 {
589 let mut q = Queue::with_seed(seed);
590 add_track(&mut q, "/music/a.mp3");
591 add_track(&mut q, "/music/b.mp3");
592 add_track(&mut q, "/music/c.mp3");
593 q.set_shuffle(ShuffleMode::All);
594
595 // `rsp play --now new.mp3` with a non-empty queue: insert after the
596 // current track and move the cursor onto it.
597 q.insert_after_current(inserted.clone());
598 let new_pos = q.position() + 1;
599 q.set_position(new_pos);
600
601 assert_eq!(q.position(), 1);
602 let cur = q.current().expect("queue is not empty");
603 assert_eq!(
604 cur.path, inserted,
605 "seed {}: current() after play-now is {:?}, expected {:?}; shuffled_indices={:?}",
606 seed,
607 cur.path,
608 inserted,
609 q.shuffled_indices()
610 );
611 }
612}
613
614// ---------- Seedable RNG ----------
615// The queue uses a seeded PRNG (rand::rngs::StdRng) instead of the OS-seeded
616// thread RNG, so shuffle order is reproducible via Queue::with_seed. Shuffle
617// is not a security domain — a PRNG suffices, and deterministic tests need a
618// fixed seed.
619
620#[test]
621fn test_same_seed_produces_same_shuffle_order() {
622 let mut q1 = Queue::with_seed(0x5EED);
623 let mut q2 = Queue::with_seed(0x5EED);
624 for i in 0..10 {
625 add_track(&mut q1, &format!("/music/t{}.mp3", i));
626 add_track(&mut q2, &format!("/music/t{}.mp3", i));
627 }
628 q1.set_shuffle(ShuffleMode::All);
629 q2.set_shuffle(ShuffleMode::All);
630 assert_eq!(q1.shuffled_indices(), q2.shuffled_indices());
631}
632
633#[test]
634fn test_different_seed_produces_different_shuffle_order() {
635 let mut q1 = Queue::with_seed(1);
636 let mut q2 = Queue::with_seed(2);
637 for i in 0..20 {
638 add_track(&mut q1, &format!("/music/t{}.mp3", i));
639 add_track(&mut q2, &format!("/music/t{}.mp3", i));
640 }
641 q1.set_shuffle(ShuffleMode::All);
642 q2.set_shuffle(ShuffleMode::All);
643 // 20! permutations — identical order for two seeds is practically impossible.
644 assert_ne!(q1.shuffled_indices(), q2.shuffled_indices());
645}
646
570// ---------- Track struct ---------- 647// ---------- Track struct ----------
571 648
572#[test] 649#[test]