Daemonize the musicbrainz thread #217
@ -53,11 +53,11 @@ pub struct EventChannel {
|
||||
receiver: mpsc::Receiver<Event>,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait IKeyEventSender {
|
||||
fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>;
|
||||
}
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait IFetchCompleteEventSender {
|
||||
fn send_fetch_complete(&self) -> Result<(), EventError>;
|
||||
}
|
||||
|
586
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
586
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
@ -21,6 +21,7 @@ struct JobQueue {
|
||||
background_queue: VecDeque<JobInstance>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Job {
|
||||
priority: JobPriority,
|
||||
instance: JobInstance,
|
||||
@ -32,21 +33,26 @@ impl Job {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum JobPriority {
|
||||
#[cfg(test)]
|
||||
Foreground,
|
||||
Background,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct JobInstance {
|
||||
result_sender: ResultSender,
|
||||
requests: VecDeque<MbParams>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum JobInstanceStatus {
|
||||
Continue,
|
||||
Complete,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum JobInstanceError {
|
||||
ReturnChannelDisconnected,
|
||||
EventChannelDisconnected,
|
||||
@ -61,6 +67,7 @@ impl JobInstance {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum JobError {
|
||||
JobQueueEmpty,
|
||||
EventChannelDisconnected,
|
||||
@ -130,6 +137,7 @@ impl JobSender {
|
||||
}
|
||||
|
||||
impl MusicBrainzDaemon {
|
||||
// GRCOV_EXCL_START
|
||||
pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>(
|
||||
musicbrainz: MB,
|
||||
job_receiver: JobReceiver,
|
||||
@ -163,6 +171,7 @@ impl MusicBrainzDaemon {
|
||||
}
|
||||
}
|
||||
}
|
||||
// GRCOV_EXCL_STOP
|
||||
|
||||
fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> {
|
||||
loop {
|
||||
@ -178,7 +187,7 @@ impl MusicBrainzDaemon {
|
||||
|
||||
fn execute_next_job(&mut self) -> Result<(), JobError> {
|
||||
if let Some(instance) = self.job_queue.front_mut() {
|
||||
let result = instance.execute_next(&mut self.musicbrainz, &mut self.event_sender);
|
||||
let result = instance.execute_next(&mut *self.musicbrainz, &mut *self.event_sender);
|
||||
match result {
|
||||
Ok(JobInstanceStatus::Continue) => {}
|
||||
Ok(JobInstanceStatus::Complete)
|
||||
@ -209,8 +218,8 @@ impl MusicBrainzDaemon {
|
||||
impl JobInstance {
|
||||
fn execute_next(
|
||||
&mut self,
|
||||
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
||||
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
||||
musicbrainz: &mut dyn IMusicBrainz,
|
||||
event_sender: &mut dyn IFetchCompleteEventSender,
|
||||
) -> Result<JobInstanceStatus, JobInstanceError> {
|
||||
// self.requests can be empty if the caller submits an empty job.
|
||||
if let Some(params) = self.requests.pop_front() {
|
||||
@ -226,8 +235,8 @@ impl JobInstance {
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
||||
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
||||
musicbrainz: &mut dyn IMusicBrainz,
|
||||
event_sender: &mut dyn IFetchCompleteEventSender,
|
||||
api_params: MbParams,
|
||||
) -> Result<(), JobInstanceError> {
|
||||
match api_params {
|
||||
@ -248,7 +257,7 @@ impl JobInstance {
|
||||
|
||||
fn return_result(
|
||||
&mut self,
|
||||
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
||||
event_sender: &mut dyn IFetchCompleteEventSender,
|
||||
result: Result<MatchStateInfo, ApiError>,
|
||||
) -> Result<(), JobInstanceError> {
|
||||
self.result_sender
|
||||
@ -291,6 +300,7 @@ impl JobQueue {
|
||||
|
||||
fn push_back(&mut self, job: Job) {
|
||||
match job.priority {
|
||||
#[cfg(test)]
|
||||
JobPriority::Foreground => self.foreground_queue.push_back(job.instance),
|
||||
JobPriority::Background => self.background_queue.push_back(job.instance),
|
||||
}
|
||||
@ -299,190 +309,388 @@ impl JobQueue {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// fn event_channel() -> (EventSender, EventReceiver) {
|
||||
// let event_channel = EventChannel::new();
|
||||
// let events_tx = event_channel.sender();
|
||||
// let events_rx = event_channel.receiver();
|
||||
// (events_tx, events_rx)
|
||||
// }
|
||||
use mockall::{predicate, Sequence};
|
||||
use musichoard::collection::{
|
||||
album::AlbumMeta,
|
||||
artist::ArtistMeta,
|
||||
musicbrainz::{IMusicBrainzRef, Mbid},
|
||||
};
|
||||
|
||||
use crate::tui::{
|
||||
event::{Event, EventError, MockIFetchCompleteEventSender},
|
||||
lib::interface::musicbrainz::api::{Match, MockIMusicBrainz},
|
||||
testmod::COLLECTION,
|
||||
};
|
||||
|
||||
// fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
// let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
// let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
// let album_match_1_1 = Match::new(100, album_1.clone());
|
||||
// let album_match_1_2 = Match::new(50, album_4.clone());
|
||||
// let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()];
|
||||
|
||||
// (album_1, matches_1)
|
||||
// }
|
||||
|
||||
// fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
// let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
// let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
// let album_match_4_1 = Match::new(100, album_4.clone());
|
||||
// let album_match_4_2 = Match::new(30, album_1.clone());
|
||||
// let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()];
|
||||
|
||||
// (album_4, matches_4)
|
||||
// }
|
||||
|
||||
// fn search_release_group_expectation(
|
||||
// api: &mut MockIMusicBrainz,
|
||||
// seq: &mut Sequence,
|
||||
// arid: &Mbid,
|
||||
// album: &AlbumMeta,
|
||||
// matches: &[Match<AlbumMeta>],
|
||||
// ) {
|
||||
// let result = Ok(matches.to_owned());
|
||||
// api.expect_search_release_group()
|
||||
// .with(predicate::eq(arid.clone()), predicate::eq(album.clone()))
|
||||
// .times(1)
|
||||
// .in_sequence(seq)
|
||||
// .return_once(|_, _| result);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn fetch_albums() {
|
||||
// let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
// let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||
|
||||
// let (album_1, matches_1) = album_expectations_1();
|
||||
// let (album_4, matches_4) = album_expectations_4();
|
||||
|
||||
// // Other albums have an MBID and so they will be skipped.
|
||||
// let mut seq = Sequence::new();
|
||||
|
||||
// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1);
|
||||
// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_4, &matches_4);
|
||||
|
||||
// let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
// let (events_tx, events_rx) = event_channel();
|
||||
// let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
// let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// // Use the second artist for this test.
|
||||
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx);
|
||||
// handle.join().unwrap();
|
||||
|
||||
// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
// let result = fetch_rx.try_recv().unwrap();
|
||||
// let expected = Ok(MatchStateInfo::Album(AlbumMatches {
|
||||
// matching: album_1.clone(),
|
||||
// list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||
// }));
|
||||
// assert_eq!(result, expected);
|
||||
|
||||
// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
// let result = fetch_rx.try_recv().unwrap();
|
||||
// let expected = Ok(MatchStateInfo::Album(AlbumMatches {
|
||||
// matching: album_4.clone(),
|
||||
// list: matches_4.iter().cloned().map(Into::into).collect(),
|
||||
// }));
|
||||
// assert_eq!(result, expected);
|
||||
// }
|
||||
|
||||
// fn artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) {
|
||||
// let artist = COLLECTION[3].meta.clone();
|
||||
|
||||
// let artist_match_1 = Match::new(100, artist.clone());
|
||||
// let artist_match_2 = Match::new(50, artist.clone());
|
||||
// let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||
|
||||
// (artist, matches)
|
||||
// }
|
||||
|
||||
// fn search_artist_expectation(
|
||||
// api: &mut MockIMusicBrainz,
|
||||
// seq: &mut Sequence,
|
||||
// artist: &ArtistMeta,
|
||||
// matches: &[Match<ArtistMeta>],
|
||||
// ) {
|
||||
// let result = Ok(matches.to_owned());
|
||||
// api.expect_search_artist()
|
||||
// .with(predicate::eq(artist.clone()))
|
||||
// .times(1)
|
||||
// .in_sequence(seq)
|
||||
// .return_once(|_| result);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn fetch_artist() {
|
||||
// let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
// let (artist, matches) = artist_expectations();
|
||||
// let mut seq = Sequence::new();
|
||||
// search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches);
|
||||
|
||||
// let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
// let (events_tx, events_rx) = event_channel();
|
||||
// let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
// let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// // Use the fourth artist for this test as they have no MBID.
|
||||
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx);
|
||||
// handle.join().unwrap();
|
||||
|
||||
// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
// let result = fetch_rx.try_recv().unwrap();
|
||||
// let expected = Ok(MatchStateInfo::Artist(ArtistMatches {
|
||||
// matching: artist.clone(),
|
||||
// list: matches.iter().cloned().map(Into::into).collect(),
|
||||
// }));
|
||||
// assert_eq!(result, expected);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn fetch_artist_fetch_disconnect() {
|
||||
// let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
// let (artist, matches) = artist_expectations();
|
||||
// let mut seq = Sequence::new();
|
||||
// search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches);
|
||||
|
||||
// let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
// let (events_tx, events_rx) = event_channel();
|
||||
// let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
// let (fetch_tx, _) = mpsc::channel();
|
||||
// // Use the fourth artist for this test as they have no MBID.
|
||||
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx);
|
||||
// handle.join().unwrap();
|
||||
|
||||
// assert!(events_rx.try_recv().is_err());
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn fetch_albums_event_disconnect() {
|
||||
// let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
// let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||
|
||||
// let (album_1, matches_1) = album_expectations_1();
|
||||
|
||||
// let mut seq = Sequence::new();
|
||||
// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1);
|
||||
|
||||
// let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
// let (events_tx, _) = event_channel();
|
||||
// let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
// let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// // Use the second artist for this test.
|
||||
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx);
|
||||
// handle.join().unwrap();
|
||||
|
||||
// let result = fetch_rx.try_recv().unwrap();
|
||||
// let expected = Ok(MatchStateInfo::Album(AlbumMatches {
|
||||
// matching: album_1.clone(),
|
||||
// list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||
// }));
|
||||
// assert_eq!(result, expected);
|
||||
|
||||
// assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected);
|
||||
// }
|
||||
use super::*;
|
||||
|
||||
fn musicbrainz() -> MockIMusicBrainz {
|
||||
MockIMusicBrainz::new()
|
||||
}
|
||||
|
||||
fn job_channel() -> (JobSender, JobReceiver) {
|
||||
let channel = JobChannel::new();
|
||||
let sender = channel.sender();
|
||||
let receiver = channel.receiver();
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
fn event_sender() -> MockIFetchCompleteEventSender {
|
||||
MockIFetchCompleteEventSender::new()
|
||||
}
|
||||
|
||||
fn fetch_complete_expectation(event_sender: &mut MockIFetchCompleteEventSender, times: usize) {
|
||||
event_sender
|
||||
.expect_send_fetch_complete()
|
||||
.times(times)
|
||||
.returning(|| Ok(()));
|
||||
}
|
||||
|
||||
fn daemon(job_receiver: JobReceiver) -> MusicBrainzDaemon {
|
||||
MusicBrainzDaemon {
|
||||
musicbrainz: Box::new(musicbrainz()),
|
||||
job_receiver: job_receiver.receiver,
|
||||
job_queue: JobQueue::new(),
|
||||
event_sender: Box::new(event_sender()),
|
||||
}
|
||||
}
|
||||
|
||||
fn daemon_with(
|
||||
musicbrainz: MockIMusicBrainz,
|
||||
job_receiver: JobReceiver,
|
||||
event_sender: MockIFetchCompleteEventSender,
|
||||
) -> MusicBrainzDaemon {
|
||||
MusicBrainzDaemon {
|
||||
musicbrainz: Box::new(musicbrainz),
|
||||
job_receiver: job_receiver.receiver,
|
||||
job_queue: JobQueue::new(),
|
||||
event_sender: Box::new(event_sender),
|
||||
}
|
||||
}
|
||||
|
||||
fn search_artist_requests() -> VecDeque<MbParams> {
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
VecDeque::from([MbParams::search_artist(artist)])
|
||||
}
|
||||
|
||||
fn artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) {
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
|
||||
let artist_match_1 = Match::new(100, artist.clone());
|
||||
let artist_match_2 = Match::new(50, artist.clone());
|
||||
let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||
|
||||
(artist, matches)
|
||||
}
|
||||
|
||||
fn search_albums_requests() -> VecDeque<MbParams> {
|
||||
let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
|
||||
let arid = mbref.unwrap().mbid().clone();
|
||||
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
VecDeque::from([
|
||||
MbParams::search_release_group(arid.clone(), album_1),
|
||||
MbParams::search_release_group(arid.clone(), album_4),
|
||||
])
|
||||
}
|
||||
|
||||
fn album_arid_expectation() -> Mbid {
|
||||
let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
|
||||
mbref.unwrap().mbid().clone()
|
||||
}
|
||||
|
||||
fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
let album_match_1_1 = Match::new(100, album_1.clone());
|
||||
let album_match_1_2 = Match::new(50, album_4.clone());
|
||||
let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()];
|
||||
|
||||
(album_1, matches_1)
|
||||
}
|
||||
|
||||
fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
let album_match_4_1 = Match::new(100, album_4.clone());
|
||||
let album_match_4_2 = Match::new(30, album_1.clone());
|
||||
let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()];
|
||||
|
||||
(album_4, matches_4)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enqueue_job_channel_disconnected() {
|
||||
let (_, job_receiver) = job_channel();
|
||||
let mut daemon = daemon(job_receiver);
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Err(Error::JobChannelDisconnected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_for_job_channel_disconnected() {
|
||||
let (_, job_receiver) = job_channel();
|
||||
let mut daemon = daemon(job_receiver);
|
||||
let result = daemon.wait_for_jobs();
|
||||
assert_eq!(result, Err(Error::JobChannelDisconnected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enqueue_job_queue_empty() {
|
||||
let (_job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon(job_receiver);
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enqueue_job() {
|
||||
let (job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon(job_receiver);
|
||||
|
||||
let requests = search_artist_requests();
|
||||
let (result_sender, _) = mpsc::channel();
|
||||
let result = job_sender.submit_background_job(result_sender, requests.clone());
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_for_jobs_job() {
|
||||
let (job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon(job_receiver);
|
||||
|
||||
let requests = search_artist_requests();
|
||||
let (result_sender, _) = mpsc::channel();
|
||||
let result = job_sender.submit_background_job(result_sender, requests.clone());
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.wait_for_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_empty() {
|
||||
let (_job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon(job_receiver);
|
||||
|
||||
let (result_sender, _) = mpsc::channel();
|
||||
let mut instance = JobInstance::new(result_sender, VecDeque::new());
|
||||
let result = instance.execute_next(&mut musicbrainz(), &mut event_sender());
|
||||
assert_eq!(result, Ok(JobInstanceStatus::Complete));
|
||||
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.execute_next_job();
|
||||
assert_eq!(result, Err(JobError::JobQueueEmpty));
|
||||
}
|
||||
|
||||
fn search_artist_expectation(
|
||||
musicbrainz: &mut MockIMusicBrainz,
|
||||
artist: &ArtistMeta,
|
||||
matches: &[Match<ArtistMeta>],
|
||||
) {
|
||||
let result = Ok(matches.to_owned());
|
||||
musicbrainz
|
||||
.expect_search_artist()
|
||||
.with(predicate::eq(artist.clone()))
|
||||
.times(1)
|
||||
.return_once(|_| result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_search_artist() {
|
||||
let mut musicbrainz = musicbrainz();
|
||||
let (artist, matches) = artist_expectations();
|
||||
search_artist_expectation(&mut musicbrainz, &artist, &matches);
|
||||
|
||||
let mut event_sender = event_sender();
|
||||
fetch_complete_expectation(&mut event_sender, 1);
|
||||
|
||||
let (job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||
|
||||
let requests = search_artist_requests();
|
||||
let (result_sender, result_receiver) = mpsc::channel();
|
||||
let result = job_sender.submit_background_job(result_sender, requests);
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.execute_next_job();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = result_receiver.try_recv().unwrap();
|
||||
assert_eq!(result, Ok(MatchStateInfo::artist(artist, matches)));
|
||||
}
|
||||
|
||||
fn search_release_group_expectation(
|
||||
musicbrainz: &mut MockIMusicBrainz,
|
||||
seq: &mut Sequence,
|
||||
arid: &Mbid,
|
||||
album: &AlbumMeta,
|
||||
matches: &[Match<AlbumMeta>],
|
||||
) {
|
||||
let result = Ok(matches.to_owned());
|
||||
musicbrainz
|
||||
.expect_search_release_group()
|
||||
.with(predicate::eq(arid.clone()), predicate::eq(album.clone()))
|
||||
.times(1)
|
||||
.in_sequence(seq)
|
||||
.return_once(|_, _| result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_search_release_groups() {
|
||||
let mut musicbrainz = musicbrainz();
|
||||
let arid = album_arid_expectation();
|
||||
let (album_1, matches_1) = album_expectations_1();
|
||||
let (album_4, matches_4) = album_expectations_4();
|
||||
|
||||
let mut seq = Sequence::new();
|
||||
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1);
|
||||
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_4, &matches_4);
|
||||
|
||||
let mut event_sender = event_sender();
|
||||
fetch_complete_expectation(&mut event_sender, 2);
|
||||
|
||||
let (job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||
|
||||
let requests = search_albums_requests();
|
||||
let (result_sender, result_receiver) = mpsc::channel();
|
||||
let result = job_sender.submit_background_job(result_sender, requests);
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.execute_next_job();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.execute_next_job();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = result_receiver.try_recv().unwrap();
|
||||
assert_eq!(result, Ok(MatchStateInfo::album(album_1, matches_1)));
|
||||
|
||||
let result = result_receiver.try_recv().unwrap();
|
||||
assert_eq!(result, Ok(MatchStateInfo::album(album_4, matches_4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_search_release_groups_result_disconnect() {
|
||||
let mut musicbrainz = musicbrainz();
|
||||
let arid = album_arid_expectation();
|
||||
let (album_1, matches_1) = album_expectations_1();
|
||||
|
||||
let mut seq = Sequence::new();
|
||||
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1);
|
||||
|
||||
let mut event_sender = event_sender();
|
||||
fetch_complete_expectation(&mut event_sender, 0);
|
||||
|
||||
let (job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||
|
||||
let requests = search_albums_requests();
|
||||
let (result_sender, _) = mpsc::channel();
|
||||
let result = job_sender.submit_background_job(result_sender, requests);
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.execute_next_job();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
// Two albums were submitted, but as one job. If the first one fails due to the
|
||||
// result_channel disconnecting, all remaining requests in that job are dropped.
|
||||
assert!(daemon.job_queue.pop_front().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execute_search_artist_event_disconnect() {
|
||||
let mut musicbrainz = musicbrainz();
|
||||
let (artist, matches) = artist_expectations();
|
||||
search_artist_expectation(&mut musicbrainz, &artist, &matches);
|
||||
|
||||
let mut event_sender = event_sender();
|
||||
event_sender
|
||||
.expect_send_fetch_complete()
|
||||
.times(1)
|
||||
.return_once(|| Err(EventError::Send(Event::FetchComplete)));
|
||||
|
||||
let (job_sender, job_receiver) = job_channel();
|
||||
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||
|
||||
let requests = search_artist_requests();
|
||||
let (result_sender, _result_receiver) = mpsc::channel();
|
||||
let result = job_sender.submit_background_job(result_sender, requests);
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.enqueue_all_pending_jobs();
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
let result = daemon.execute_next_job();
|
||||
assert_eq!(result, Err(JobError::EventChannelDisconnected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn job_queue() {
|
||||
let mut queue = JobQueue::new();
|
||||
assert!(queue.is_empty());
|
||||
assert_eq!(queue.foreground_queue.len(), 0);
|
||||
assert_eq!(queue.background_queue.len(), 0);
|
||||
|
||||
let (result_sender, _) = mpsc::channel();
|
||||
let bg = Job::new(
|
||||
JobPriority::Background,
|
||||
JobInstance::new(result_sender, VecDeque::new()),
|
||||
);
|
||||
queue.push_back(bg);
|
||||
|
||||
assert!(!queue.is_empty());
|
||||
assert_eq!(queue.foreground_queue.len(), 0);
|
||||
assert_eq!(queue.background_queue.len(), 1);
|
||||
|
||||
let (result_sender, _) = mpsc::channel();
|
||||
let fg = Job::new(
|
||||
JobPriority::Foreground,
|
||||
JobInstance::new(result_sender, VecDeque::new()),
|
||||
);
|
||||
queue.push_back(fg);
|
||||
|
||||
assert!(!queue.is_empty());
|
||||
assert_eq!(queue.foreground_queue.len(), 1);
|
||||
assert_eq!(queue.background_queue.len(), 1);
|
||||
|
||||
let instance = queue.pop_front();
|
||||
assert!(instance.is_some());
|
||||
assert!(!queue.is_empty());
|
||||
assert_eq!(queue.foreground_queue.len(), 0);
|
||||
assert_eq!(queue.background_queue.len(), 1);
|
||||
|
||||
let instance = queue.pop_front();
|
||||
assert!(instance.is_some());
|
||||
assert!(queue.is_empty());
|
||||
assert_eq!(queue.foreground_queue.len(), 0);
|
||||
assert_eq!(queue.background_queue.len(), 0);
|
||||
|
||||
let instance = queue.pop_front();
|
||||
assert!(instance.is_none());
|
||||
assert!(queue.is_empty());
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
EventChannelDisconnected,
|
||||
JobChannelDisconnected,
|
||||
@ -33,23 +34,23 @@ pub trait IMbJobSender {
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MbParams {
|
||||
Search(SearchParams),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum SearchParams {
|
||||
Artist(SearchArtistParams),
|
||||
ReleaseGroup(SearchReleaseGroupParams),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SearchArtistParams {
|
||||
pub artist: ArtistMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct SearchReleaseGroupParams {
|
||||
pub arid: Mbid,
|
||||
pub album: AlbumMeta,
|
||||
@ -67,3 +68,14 @@ impl MbParams {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn errors() {
|
||||
assert!(!format!("{}", Error::EventChannelDisconnected).is_empty());
|
||||
assert!(!format!("{}", Error::JobChannelDisconnected).is_empty());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user