Daemonize the musicbrainz thread #217

Merged
wojtek merged 15 commits from 188---add-option-for-manual-input-during-fetch into main 2024-09-21 23:03:47 +02:00
3 changed files with 377 additions and 157 deletions
Showing only changes of commit f48db09300 - Show all commits

View File

@ -53,11 +53,11 @@ pub struct EventChannel {
receiver: mpsc::Receiver<Event>, receiver: mpsc::Receiver<Event>,
} }
#[cfg_attr(test, automock)]
pub trait IKeyEventSender { pub trait IKeyEventSender {
fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>; fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>;
} }
#[cfg_attr(test, automock)]
pub trait IFetchCompleteEventSender { pub trait IFetchCompleteEventSender {
fn send_fetch_complete(&self) -> Result<(), EventError>; fn send_fetch_complete(&self) -> Result<(), EventError>;
} }

View File

@ -21,6 +21,7 @@ struct JobQueue {
background_queue: VecDeque<JobInstance>, background_queue: VecDeque<JobInstance>,
} }
#[derive(Debug)]
struct Job { struct Job {
priority: JobPriority, priority: JobPriority,
instance: JobInstance, instance: JobInstance,
@ -32,21 +33,26 @@ impl Job {
} }
} }
#[derive(Debug)]
enum JobPriority { enum JobPriority {
#[cfg(test)]
Foreground, Foreground,
Background, Background,
} }
#[derive(Debug)]
struct JobInstance { struct JobInstance {
result_sender: ResultSender, result_sender: ResultSender,
requests: VecDeque<MbParams>, requests: VecDeque<MbParams>,
} }
#[derive(Debug, PartialEq, Eq)]
enum JobInstanceStatus { enum JobInstanceStatus {
Continue, Continue,
Complete, Complete,
} }
#[derive(Debug, PartialEq, Eq)]
enum JobInstanceError { enum JobInstanceError {
ReturnChannelDisconnected, ReturnChannelDisconnected,
EventChannelDisconnected, EventChannelDisconnected,
@ -61,6 +67,7 @@ impl JobInstance {
} }
} }
#[derive(Debug, PartialEq, Eq)]
enum JobError { enum JobError {
JobQueueEmpty, JobQueueEmpty,
EventChannelDisconnected, EventChannelDisconnected,
@ -130,6 +137,7 @@ impl JobSender {
} }
impl MusicBrainzDaemon { impl MusicBrainzDaemon {
// GRCOV_EXCL_START
pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>( pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>(
musicbrainz: MB, musicbrainz: MB,
job_receiver: JobReceiver, job_receiver: JobReceiver,
@ -163,6 +171,7 @@ impl MusicBrainzDaemon {
} }
} }
} }
// GRCOV_EXCL_STOP
fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> { fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> {
loop { loop {
@ -178,7 +187,7 @@ impl MusicBrainzDaemon {
fn execute_next_job(&mut self) -> Result<(), JobError> { fn execute_next_job(&mut self) -> Result<(), JobError> {
if let Some(instance) = self.job_queue.front_mut() { 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 { match result {
Ok(JobInstanceStatus::Continue) => {} Ok(JobInstanceStatus::Continue) => {}
Ok(JobInstanceStatus::Complete) Ok(JobInstanceStatus::Complete)
@ -209,8 +218,8 @@ impl MusicBrainzDaemon {
impl JobInstance { impl JobInstance {
fn execute_next( fn execute_next(
&mut self, &mut self,
musicbrainz: &mut Box<dyn IMusicBrainz>, musicbrainz: &mut dyn IMusicBrainz,
event_sender: &mut Box<dyn IFetchCompleteEventSender>, event_sender: &mut dyn IFetchCompleteEventSender,
) -> Result<JobInstanceStatus, JobInstanceError> { ) -> Result<JobInstanceStatus, JobInstanceError> {
// self.requests can be empty if the caller submits an empty job. // self.requests can be empty if the caller submits an empty job.
if let Some(params) = self.requests.pop_front() { if let Some(params) = self.requests.pop_front() {
@ -226,8 +235,8 @@ impl JobInstance {
fn execute( fn execute(
&mut self, &mut self,
musicbrainz: &mut Box<dyn IMusicBrainz>, musicbrainz: &mut dyn IMusicBrainz,
event_sender: &mut Box<dyn IFetchCompleteEventSender>, event_sender: &mut dyn IFetchCompleteEventSender,
api_params: MbParams, api_params: MbParams,
) -> Result<(), JobInstanceError> { ) -> Result<(), JobInstanceError> {
match api_params { match api_params {
@ -248,7 +257,7 @@ impl JobInstance {
fn return_result( fn return_result(
&mut self, &mut self,
event_sender: &mut Box<dyn IFetchCompleteEventSender>, event_sender: &mut dyn IFetchCompleteEventSender,
result: Result<MatchStateInfo, ApiError>, result: Result<MatchStateInfo, ApiError>,
) -> Result<(), JobInstanceError> { ) -> Result<(), JobInstanceError> {
self.result_sender self.result_sender
@ -291,6 +300,7 @@ impl JobQueue {
fn push_back(&mut self, job: Job) { fn push_back(&mut self, job: Job) {
match job.priority { match job.priority {
#[cfg(test)]
JobPriority::Foreground => self.foreground_queue.push_back(job.instance), JobPriority::Foreground => self.foreground_queue.push_back(job.instance),
JobPriority::Background => self.background_queue.push_back(job.instance), JobPriority::Background => self.background_queue.push_back(job.instance),
} }
@ -299,190 +309,388 @@ impl JobQueue {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// fn event_channel() -> (EventSender, EventReceiver) { use mockall::{predicate, Sequence};
// let event_channel = EventChannel::new(); use musichoard::collection::{
// let events_tx = event_channel.sender(); album::AlbumMeta,
// let events_rx = event_channel.receiver(); artist::ArtistMeta,
// (events_tx, events_rx) 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>>) { use super::*;
// 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()); fn musicbrainz() -> MockIMusicBrainz {
// let album_match_1_2 = Match::new(50, album_4.clone()); MockIMusicBrainz::new()
// let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()]; }
// (album_1, matches_1) fn job_channel() -> (JobSender, JobReceiver) {
// } let channel = JobChannel::new();
let sender = channel.sender();
let receiver = channel.receiver();
(sender, receiver)
}
// fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) { fn event_sender() -> MockIFetchCompleteEventSender {
// let album_1 = COLLECTION[1].albums[0].meta.clone(); MockIFetchCompleteEventSender::new()
// let album_4 = COLLECTION[1].albums[3].meta.clone(); }
// let album_match_4_1 = Match::new(100, album_4.clone()); fn fetch_complete_expectation(event_sender: &mut MockIFetchCompleteEventSender, times: usize) {
// let album_match_4_2 = Match::new(30, album_1.clone()); event_sender
// let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()]; .expect_send_fetch_complete()
.times(times)
.returning(|| Ok(()));
}
// (album_4, matches_4) 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 search_release_group_expectation( fn daemon_with(
// api: &mut MockIMusicBrainz, musicbrainz: MockIMusicBrainz,
// seq: &mut Sequence, job_receiver: JobReceiver,
// arid: &Mbid, event_sender: MockIFetchCompleteEventSender,
// album: &AlbumMeta, ) -> MusicBrainzDaemon {
// matches: &[Match<AlbumMeta>], MusicBrainzDaemon {
// ) { musicbrainz: Box::new(musicbrainz),
// let result = Ok(matches.to_owned()); job_receiver: job_receiver.receiver,
// api.expect_search_release_group() job_queue: JobQueue::new(),
// .with(predicate::eq(arid.clone()), predicate::eq(album.clone())) event_sender: Box::new(event_sender),
// .times(1) }
// .in_sequence(seq) }
// .return_once(|_, _| result);
// }
// #[test] fn search_artist_requests() -> VecDeque<MbParams> {
// fn fetch_albums() { let artist = COLLECTION[3].meta.clone();
// let mut mb_api = MockIMusicBrainz::new(); VecDeque::from([MbParams::search_artist(artist)])
}
// let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); fn artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) {
let artist = COLLECTION[3].meta.clone();
// let (album_1, matches_1) = album_expectations_1(); let artist_match_1 = Match::new(100, artist.clone());
// let (album_4, matches_4) = album_expectations_4(); let artist_match_2 = Match::new(50, artist.clone());
let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
// // Other albums have an MBID and so they will be skipped. (artist, matches)
// let mut seq = Sequence::new(); }
// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); fn search_albums_requests() -> VecDeque<MbParams> {
// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_4, &matches_4); let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
let arid = mbref.unwrap().mbid().clone();
// let music_hoard = music_hoard(COLLECTION.to_owned()); let album_1 = COLLECTION[1].albums[0].meta.clone();
// let (events_tx, events_rx) = event_channel(); let album_4 = COLLECTION[1].albums[3].meta.clone();
// let inner = AppInner::new(music_hoard, mb_api, events_tx);
// let (fetch_tx, fetch_rx) = mpsc::channel(); VecDeque::from([
// // Use the second artist for this test. MbParams::search_release_group(arid.clone(), album_1),
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); MbParams::search_release_group(arid.clone(), album_4),
// handle.join().unwrap(); ])
}
// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); fn album_arid_expectation() -> Mbid {
// let result = fetch_rx.try_recv().unwrap(); let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
// let expected = Ok(MatchStateInfo::Album(AlbumMatches { mbref.unwrap().mbid().clone()
// 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); fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
// let result = fetch_rx.try_recv().unwrap(); let album_1 = COLLECTION[1].albums[0].meta.clone();
// let expected = Ok(MatchStateInfo::Album(AlbumMatches { let album_4 = COLLECTION[1].albums[3].meta.clone();
// 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 album_match_1_1 = Match::new(100, album_1.clone());
// let artist = COLLECTION[3].meta.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()];
// let artist_match_1 = Match::new(100, artist.clone()); (album_1, matches_1)
// let artist_match_2 = Match::new(50, artist.clone()); }
// let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
// (artist, matches) 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();
// fn search_artist_expectation( let album_match_4_1 = Match::new(100, album_4.clone());
// api: &mut MockIMusicBrainz, let album_match_4_2 = Match::new(30, album_1.clone());
// seq: &mut Sequence, let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()];
// 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] (album_4, matches_4)
// fn fetch_artist() { }
// let mut mb_api = MockIMusicBrainz::new();
// let (artist, matches) = artist_expectations(); #[test]
// let mut seq = Sequence::new(); fn enqueue_job_channel_disconnected() {
// search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches); let (_, job_receiver) = job_channel();
let mut daemon = daemon(job_receiver);
let result = daemon.enqueue_all_pending_jobs();
assert_eq!(result, Err(Error::JobChannelDisconnected));
}
// let music_hoard = music_hoard(COLLECTION.to_owned()); #[test]
// let (events_tx, events_rx) = event_channel(); fn wait_for_job_channel_disconnected() {
// let inner = AppInner::new(music_hoard, mb_api, events_tx); let (_, job_receiver) = job_channel();
let mut daemon = daemon(job_receiver);
let result = daemon.wait_for_jobs();
assert_eq!(result, Err(Error::JobChannelDisconnected));
}
// let (fetch_tx, fetch_rx) = mpsc::channel(); #[test]
// // Use the fourth artist for this test as they have no MBID. fn enqueue_job_queue_empty() {
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); let (_job_sender, job_receiver) = job_channel();
// handle.join().unwrap(); let mut daemon = daemon(job_receiver);
let result = daemon.enqueue_all_pending_jobs();
assert_eq!(result, Ok(()));
}
// assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady); #[test]
// let result = fetch_rx.try_recv().unwrap(); fn enqueue_job() {
// let expected = Ok(MatchStateInfo::Artist(ArtistMatches { let (job_sender, job_receiver) = job_channel();
// matching: artist.clone(), let mut daemon = daemon(job_receiver);
// list: matches.iter().cloned().map(Into::into).collect(),
// }));
// assert_eq!(result, expected);
// }
// #[test] let requests = search_artist_requests();
// fn fetch_artist_fetch_disconnect() { let (result_sender, _) = mpsc::channel();
// let mut mb_api = MockIMusicBrainz::new(); let result = job_sender.submit_background_job(result_sender, requests.clone());
assert_eq!(result, Ok(()));
// let (artist, matches) = artist_expectations(); let result = daemon.enqueue_all_pending_jobs();
// let mut seq = Sequence::new(); assert_eq!(result, Ok(()));
// search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches);
// let music_hoard = music_hoard(COLLECTION.to_owned()); assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests);
// let (events_tx, events_rx) = event_channel(); }
// let inner = AppInner::new(music_hoard, mb_api, events_tx);
// let (fetch_tx, _) = mpsc::channel(); #[test]
// // Use the fourth artist for this test as they have no MBID. fn wait_for_jobs_job() {
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx); let (job_sender, job_receiver) = job_channel();
// handle.join().unwrap(); let mut daemon = daemon(job_receiver);
// assert!(events_rx.try_recv().is_err()); 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(()));
// #[test] let result = daemon.wait_for_jobs();
// fn fetch_albums_event_disconnect() { assert_eq!(result, Ok(()));
// let mut mb_api = MockIMusicBrainz::new();
// let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap(); assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests);
}
// let (album_1, matches_1) = album_expectations_1(); #[test]
fn execute_empty() {
let (_job_sender, job_receiver) = job_channel();
let mut daemon = daemon(job_receiver);
// let mut seq = Sequence::new(); let (result_sender, _) = mpsc::channel();
// search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1); 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 music_hoard = music_hoard(COLLECTION.to_owned()); let result = daemon.enqueue_all_pending_jobs();
// let (events_tx, _) = event_channel(); assert_eq!(result, Ok(()));
// let inner = AppInner::new(music_hoard, mb_api, events_tx);
// let (fetch_tx, fetch_rx) = mpsc::channel(); let result = daemon.execute_next_job();
// // Use the second artist for this test. assert_eq!(result, Err(JobError::JobQueueEmpty));
// let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx); }
// handle.join().unwrap();
// let result = fetch_rx.try_recv().unwrap(); fn search_artist_expectation(
// let expected = Ok(MatchStateInfo::Album(AlbumMatches { musicbrainz: &mut MockIMusicBrainz,
// matching: album_1.clone(), artist: &ArtistMeta,
// list: matches_1.iter().cloned().map(Into::into).collect(), matches: &[Match<ArtistMeta>],
// })); ) {
// assert_eq!(result, expected); let result = Ok(matches.to_owned());
musicbrainz
.expect_search_artist()
.with(predicate::eq(artist.clone()))
.times(1)
.return_once(|_| result);
}
// assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected); #[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());
}
} }

View File

@ -7,6 +7,7 @@ use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;
#[derive(Debug, PartialEq, Eq)]
pub enum Error { pub enum Error {
EventChannelDisconnected, EventChannelDisconnected,
JobChannelDisconnected, JobChannelDisconnected,
@ -33,23 +34,23 @@ pub trait IMbJobSender {
) -> Result<(), Error>; ) -> Result<(), Error>;
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum MbParams { pub enum MbParams {
Search(SearchParams), Search(SearchParams),
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum SearchParams { pub enum SearchParams {
Artist(SearchArtistParams), Artist(SearchArtistParams),
ReleaseGroup(SearchReleaseGroupParams), ReleaseGroup(SearchReleaseGroupParams),
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct SearchArtistParams { pub struct SearchArtistParams {
pub artist: ArtistMeta, pub artist: ArtistMeta,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct SearchReleaseGroupParams { pub struct SearchReleaseGroupParams {
pub arid: Mbid, pub arid: Mbid,
pub album: AlbumMeta, 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());
}
}