Skip to content
File

Blob: firmware/crates/radio-core/tests/playlist.rs

rust277 lines
1use radio_core::{
2 music::{Catalog, Error, ReadAt},
3 now_playing::{Message, NowPlaying, Snapshot},
4 playback::Playback,
5 protocol::Action,
6};
7use std::num::NonZeroU32;
8 
9const FIXTURE: &[u8] = include_bytes!("fixtures/playlist-v4.pack");
10 
11#[test]
12fn catalog_preserves_order_unicode_metadata_and_individual_frames() {
13 let mut source = FIXTURE;
14 let catalog = Catalog::parse(&mut source).unwrap();
15 assert_eq!(catalog.songs().len(), 2);
16 assert_eq!(
17 catalog.songs()[0].metadata.as_ref().unwrap().title,
18 "First café"
19 );
20 assert_eq!(
21 catalog.songs()[1].metadata.as_ref().unwrap().title,
22 "第二首"
23 );
24 assert_eq!(
25 catalog.songs()[1].metadata.as_ref().unwrap().duration_ms,
26 60
27 );
28 let mut packet = [0; 1275];
29 assert_eq!(
30 catalog.read_frame(&mut source, 1, 2, &mut packet).unwrap(),
31 3
32 );
33 assert_eq!(&packet[..3], [0xf8, 0xff, 0xfe]);
34 assert!(catalog.read_frame(&mut source, 1, 3, &mut packet).is_err());
35 assert!(catalog.read_frame(&mut source, 2, 0, &mut packet).is_err());
36}
37 
38#[test]
39fn catalog_rejects_partial_corrupt_and_hostile_track_tables() {
40 for end in 0..FIXTURE.len() {
41 assert!(Catalog::parse(&mut &FIXTURE[..end]).is_err());
42 }
43 let mut bytes = FIXTURE.to_vec();
44 *bytes.last_mut().unwrap() ^= 1;
45 assert_eq!(
46 Catalog::parse(&mut bytes.as_slice()).unwrap_err(),
47 Error::Checksum
48 );
49 for count in [0u32, 33, u32::MAX] {
50 let mut bytes = FIXTURE.to_vec();
51 bytes[12..16].copy_from_slice(&count.to_le_bytes());
52 assert!(Catalog::parse(&mut bytes.as_slice()).is_err());
53 }
54 let mut bytes = FIXTURE.to_vec();
55 bytes[64..68].copy_from_slice(&u32::MAX.to_le_bytes());
56 let crc = crc32fast::hash(&bytes[64..]);
57 bytes[28..32].copy_from_slice(&crc.to_le_bytes());
58 assert!(Catalog::parse(&mut bytes.as_slice()).is_err());
59}
60 
61#[test]
62fn catalog_reads_beyond_four_mib_without_loading_the_partition() {
63 let mut song = vec![0; 64];
64 song[..8].copy_from_slice(b"S3MUSIC\0");
65 for (offset, value) in [(8, 2u32), (12, 7000), (16, 48000), (20, 2), (24, 20)] {
66 song[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
67 }
68 for _ in 0..7000 {
69 song.extend(1275u16.to_le_bytes());
70 song.extend([7; 1275]);
71 }
72 let crc = crc32fast::hash(&song[64..]);
73 song[28..32].copy_from_slice(&crc.to_le_bytes());
74 struct Storage {
75 bytes: Vec<u8>,
76 largest: usize,
77 }
78 impl ReadAt for Storage {
79 fn size(&self) -> usize {
80 self.bytes.len()
81 }
82 fn read_exact(&mut self, offset: usize, out: &mut [u8]) -> Result<(), Error> {
83 self.largest = self.largest.max(out.len());
84 out.copy_from_slice(
85 self.bytes
86 .get(offset..offset + out.len())
87 .ok_or(Error::Truncated)?,
88 );
89 Ok(())
90 }
91 }
92 let mut storage = Storage {
93 bytes: song,
94 largest: 0,
95 };
96 let catalog = Catalog::parse(&mut storage).unwrap();
97 let mut frame = [0; 1275];
98 catalog
99 .read_frame(&mut storage, 0, 6999, &mut frame)
100 .unwrap();
101 assert!(frame.iter().all(|&n| n == 7));
102 assert!(storage.largest <= 1275);
103 assert!(catalog.index_bytes() <= 7000 * 4);
104}
105 
106fn player() -> Playback {
107 Playback::playlist(
108 [2, 3]
109 .into_iter()
110 .map(|n| NonZeroU32::new(n).unwrap())
111 .collect(),
112 0,
113 )
114 .unwrap()
115}
116 
117#[test]
118fn automatic_transitions_and_next_preserve_the_transport_clock() {
119 let mut p = player();
120 assert_eq!(p.due(0).unwrap().track_index, 0);
121 p.due(20000);
122 let next = p.due(40000).unwrap();
123 assert_eq!(
124 (next.track_index, next.index, next.pts_ms, next.revision),
125 (1, 0, 40, 1)
126 );
127 p.apply(Action::Next);
128 let skipped = p.due(60000).unwrap();
129 assert_eq!(
130 (
131 skipped.track_index,
132 skipped.index,
133 skipped.pts_ms,
134 skipped.revision
135 ),
136 (0, 0, 60, 2)
137 );
138 p.apply(Action::Pause);
139 p.apply(Action::Next);
140 let paused = p.due(80000).unwrap();
141 assert_eq!(
142 (paused.track_index, paused.index, paused.paused),
143 (1, 0, true)
144 );
145 p.apply(Action::Play);
146 assert_eq!(p.due(100000).unwrap().pts_ms, 100);
147}
148 
149#[test]
150fn long_stalls_skip_across_song_boundaries_without_bursting() {
151 let mut p = player();
152 p.due(0);
153 let due = p.due(100_060_000).unwrap();
154 assert_eq!((due.track_index, due.index, due.pts_ms), (1, 1, 100060));
155 assert!(due.revision > 1000);
156 assert!(p.due(100_060_001).is_none());
157}
158 
159fn final_frame_player() -> Playback {
160 let mut p = Playback::playlist(
161 [2, 3, 4]
162 .into_iter()
163 .map(|n| NonZeroU32::new(n).unwrap())
164 .collect(),
165 0,
166 )
167 .unwrap();
168 p.due(0);
169 let last = p.due(20_000).unwrap();
170 assert_eq!((last.track_index, last.index), (0, 1));
171 assert_eq!(p.epoch(), last.epoch);
172 p
173}
174 
175#[test]
176fn commands_between_the_final_packet_and_next_deadline_target_the_announced_song() {
177 for (action, track, revision) in [(Action::Next, 1, 1), (Action::Restart, 0, 1)] {
178 let mut p = final_frame_player();
179 p.apply(action);
180 assert!(p.due(39_999).is_none());
181 let due = p.due(40_000).unwrap();
182 assert_eq!(
183 (due.track_index, due.index, due.revision, due.pts_ms),
184 (track, 0, revision, 40)
185 );
186 }
187 let mut p = final_frame_player();
188 p.apply(Action::Pause);
189 let held = p.due(40_000).unwrap();
190 assert_eq!(
191 (held.track_index, held.index, held.revision, held.paused),
192 (0, 1, 0, true)
193 );
194 p.apply(Action::Play);
195 let next = p.due(60_000).unwrap();
196 assert_eq!(
197 (next.track_index, next.index, next.revision, next.pts_ms),
198 (1, 0, 1, 60)
199 );
200 
201 let mut p = final_frame_player();
202 p.apply(Action::Pause);
203 p.apply(Action::Next);
204 let next = p.due(40_000).unwrap();
205 assert_eq!(
206 (next.track_index, next.index, next.revision, next.paused),
207 (1, 0, 1, true)
208 );
209}
210 
211#[test]
212fn pending_transition_and_a_stall_advance_once_then_skip_elapsed_packets() {
213 let mut p = final_frame_player();
214 let due = p.due(1_040_000).unwrap();
215 assert_eq!(
216 (due.track_index, due.index, due.revision, due.pts_ms),
217 (2, 2, 17, 1040)
218 );
219 assert_eq!(p.skipped_frames(), 50);
220 assert!(p.due(1_040_001).is_none());
221}
222 
223#[test]
224fn published_metadata_position_and_duration_follow_the_emitted_song() {
225 let mut source = FIXTURE;
226 let catalog = Catalog::parse(&mut source).unwrap();
227 let mut snapshot = Snapshot::new(&catalog);
228 let mut p = player();
229 snapshot.record(&catalog, p.due(0).unwrap()).unwrap();
230 assert!(!snapshot.record(&catalog, p.due(20_000).unwrap()).unwrap());
231 assert_eq!(
232 (
233 snapshot.now_playing.track_index,
234 snapshot.position_ms,
235 snapshot.duration_ms
236 ),
237 (0, 20, 40)
238 );
239 let next = p.due(40_000).unwrap();
240 assert!(snapshot.record(&catalog, next).unwrap());
241 assert_eq!(snapshot.now_playing.track.as_ref().unwrap().title, "第二首");
242 assert_eq!(
243 (
244 snapshot.now_playing.revision,
245 snapshot.position_ms,
246 snapshot.duration_ms
247 ),
248 (1, 0, 60)
249 );
250 assert!(
251 snapshot
252 .record(&catalog, radio_core::playback::Due { index: 3, ..next })
253 .is_err()
254 );
255 assert_eq!(snapshot.position_ms, 0);
256}
257 
258#[test]
259fn maximal_escaped_metadata_fits_the_transport_data_limit() {
260 let state = NowPlaying {
261 revision: u32::MAX,
262 track_index: 31,
263 track_count: 32,
264 track: Some(radio_core::music::Track {
265 title: "\\".repeat(256),
266 artist: "\"".repeat(256),
267 duration_ms: 600000,
268 }),
269 };
270 let bytes = serde_json::to_vec(&Message {
271 event: "nowPlaying",
272 now_playing: &state,
273 })
274 .unwrap();
275 assert!(bytes.len() <= 2048);
276}