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());
// 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);
// }
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());
}
} }

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());
}
}