use super::*; #[test] fn port_selection_prefers_exact_names_and_rejects_ambiguity() { let names = ["Other", "Micro-Freak MIDI", "Micro-Freak MIDI extra"].map(String::from); assert_eq!(select_port(&names, Some("Micro-Freak MIDI")).unwrap(), 1); assert_eq!(select_port(&names, Some("EXTRA")).unwrap(), 2); for requested in [None, Some("micro"), Some("missing")] { let error = select_port(&names, requested).unwrap_err().to_string(); assert!(error.contains("--port NAME")); assert!(error.contains("Micro-Freak MIDI extra")); } assert_eq!(select_port(&names[..2], None).unwrap(), 1); assert!( select_port(&[], None) .unwrap_err() .to_string() .contains("(none)") ); } #[test] fn requests_use_firmware_ids_and_midi_safe_numeric_writes() { assert_eq!( query(127, 0x41, 0x070a), [0xf0, 0, 0x20, 0x6b, 7, 1, 127, 3, 0x41, 0, 7, 10, 0xf7,] ); let packet = write_packet(12, param::global::CHORD_OFFSET3, StoredValue(65535)); assert_eq!(&packet[..9], &[0xf0, 0, 0x20, 0x6b, 7, 1, 12, 22, 0x53]); assert_eq!(&packet[9..packet.len() - 1], b"synth param 1802 65535"); assert_eq!(packet.last(), Some(&0xf7)); assert!(packet[1..packet.len() - 1].iter().all(|byte| *byte < 128)); } fn reply(raw: u16) -> [u8; 15] { let [high, low] = raw.to_be_bytes(); [ 0xf0, 0, 0x20, 0x6b, 7, 0x7f, 99, 5, 0x40, ((high >> 7) << 4) | ((low >> 7) << 3), 1, 1, high & 127, low & 127, 0xf7, ] } #[test] fn parameter_replies_restore_both_msbs_and_ignore_independent_counters() { for raw in [0, 127, 128, 255, 32767, 32768, 65535] { let mut bytes = reply(raw); assert_eq!( parameter_reply(&bytes, param::filter::CUTOFF_HZ), Some(StoredValue(raw)) ); bytes[5] = 1; bytes[6] = 0; assert_eq!( parameter_reply(&bytes, param::filter::CUTOFF_HZ), Some(StoredValue(raw)) ); assert_eq!(parameter_reply(&bytes, param::filter::RESONANCE), None); } } #[test] fn malformed_parameter_replies_are_not_accepted() { let bytes = reply(0xffff); for length in 0..bytes.len() { assert_eq!( parameter_reply(&bytes[..length], param::filter::CUTOFF_HZ), None ); } for (index, value) in [ (0, 0), (4, 8), (5, 2), (7, 4), (8, 0x41), (12, 128), (14, 0), ] { let mut bad = bytes; bad[index] = value; assert_eq!( parameter_reply(&bad, param::filter::CUTOFF_HZ), None, "byte {index}" ); } } #[test] fn identity_requires_microfreak_and_firmware_five() { let good = [ 0xf0, 0x7e, 0x7f, 6, 2, 0, 0x20, 0x6b, 6, 0, 6, 1, 5, 0, 0, 1, 0xf7, ]; assert!(identity_reply(&good).unwrap().validate().is_ok()); let mut old = good; old[12] = 4; assert!( identity_reply(&old) .unwrap() .validate() .unwrap_err() .to_string() .contains("firmware 5") ); let mut other = good; other[8] = 7; assert!( identity_reply(&other) .unwrap() .validate() .unwrap_err() .to_string() .contains("not an Arturia") ); for length in 0..15 { assert!(identity_reply(&good[..length]).is_none()); } let mut bad = good; bad[13] = 128; assert!(identity_reply(&bad).is_none()); } #[test] fn wake_response_requires_the_expected_header_and_seven_bit_data() { let mut bytes = [0; 45]; bytes[..5].copy_from_slice(PREFIX); bytes[5] = 1; bytes[7..9].copy_from_slice(&[35, 0x52]); bytes[44] = 0xf7; assert_eq!(wake_reply(&bytes), Some(())); assert_eq!(wake_reply(&bytes[..44]), None); bytes[30] = 128; assert_eq!(wake_reply(&bytes), None); } #[test] fn receive_skips_stale_and_unrelated_messages() { let (sender, receiver) = mpsc::channel(); let sent = Instant::now(); sender .send((sent - Duration::from_secs(1), vec![1])) .unwrap(); sender.send((sent, vec![2])).unwrap(); sender.send((sent, vec![1])).unwrap(); let value = receive(&receiver, sent, Duration::from_secs(1), |bytes| { (bytes == [1]).then_some(42) }) .unwrap(); assert_eq!(value, 42); } #[test] fn receive_distinguishes_deadline_from_disconnection() { let (sender, receiver) = mpsc::channel::(); let error = receive(&receiver, Instant::now(), Duration::ZERO, |_| Some(())).unwrap_err(); assert_eq!(error.kind(), io::ErrorKind::TimedOut); drop(sender); let error = receive(&receiver, Instant::now(), Duration::from_secs(1), |_| { Some(()) }) .unwrap_err(); assert_eq!(error.kind(), io::ErrorKind::BrokenPipe); } #[derive(Default)] struct FakeDevice { registers: Registers, reads: Vec, writes: Vec<(ParamDescriptor, StoredValue)>, fail_read: Option, fail_write: bool, ignore_writes: bool, side_effect: Option<(ParamDescriptor, StoredValue)>, } impl RegisterReader for FakeDevice { fn read(&mut self, parameter: ParamDescriptor) -> io::Result { self.reads.push(parameter); if self.fail_read == Some(parameter) { return Err(io::Error::other("read failed")); } Ok(self.registers[¶meter]) } } impl RegisterWriter for FakeDevice { fn write(&mut self, parameter: ParamDescriptor, value: StoredValue) -> io::Result<()> { self.writes.push((parameter, value)); if self.fail_write { return Err(io::Error::other("write failed")); } if !self.ignore_writes { self.registers.insert(parameter, value); } if let Some((parameter, value)) = self.side_effect { self.registers.insert(parameter, value); } Ok(()) } } fn targets() -> Registers { [ (param::filter::CUTOFF_HZ, StoredValue(200)), (param::filter::RESONANCE, StoredValue(300)), ] .into() } #[test] fn apply_reads_every_baseline_writes_only_differences_then_verifies_every_target() { let targets = targets(); let mut device = FakeDevice { registers: targets.clone(), ..Default::default() }; device .registers .insert(param::filter::CUTOFF_HZ, StoredValue(100)); apply_registers(&mut device, &targets).unwrap(); assert_eq!( device.writes, [(param::filter::CUTOFF_HZ, StoredValue(200))] ); assert_eq!( device.reads, [ param::filter::CUTOFF_HZ, param::filter::RESONANCE, param::filter::CUTOFF_HZ, param::filter::RESONANCE ] ); assert_eq!(device.registers, targets); } #[test] fn baseline_failure_prevents_all_writes() { let mut device = FakeDevice { registers: targets(), fail_read: Some(param::filter::RESONANCE), ..Default::default() }; device .registers .insert(param::filter::CUTOFF_HZ, StoredValue(100)); let error = apply_registers(&mut device, &targets()).unwrap_err(); assert!(error.to_string().contains("parameter 0x0102")); assert!(device.writes.is_empty()); } #[test] fn apply_detects_ignored_writes_and_side_effects_on_initially_matching_targets() { for side_effect in [false, true] { let mut device = FakeDevice { registers: targets(), ..Default::default() }; device .registers .insert(param::filter::CUTOFF_HZ, StoredValue(100)); if side_effect { device.side_effect = Some((param::filter::RESONANCE, StoredValue(0))); } else { device.ignore_writes = true; } let error = apply_registers(&mut device, &targets()) .unwrap_err() .to_string(); assert!(error.contains("read-back mismatch")); assert!(error.contains(if side_effect { "0x0102" } else { "0x0101" })); assert!(error.contains("partially changed")); } } #[test] fn apply_stops_at_the_first_failed_write_with_parameter_context() { let mut device = FakeDevice { registers: targets(), fail_write: true, ..Default::default() }; device .registers .values_mut() .for_each(|value| *value = StoredValue(0)); let error = apply_registers(&mut device, &targets()).unwrap_err(); assert!(error.to_string().contains("parameter 0x0101: write failed")); assert_eq!(device.writes.len(), 1); assert_eq!(device.reads.len(), 2); }