From b5301e5f42ed9eea417713a7ed1e3a5e37c827b0 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 30 May 2023 14:26:12 +0200 Subject: [PATCH 1/4] Test lag with a channel capacity of 2 It's not intuitive, but the stream output is correct. It may be changed later. --- Cargo.toml | 1 + eyeball-im/Cargo.toml | 1 + eyeball-im/tests/it.rs | 17 ++++++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index fd38ac7..54fdd20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = ["eyeball", "eyeball-im"] [workspace.dependencies] +assert_matches = "1.5.0" futures-core = "0.3.26" readlock = "0.1.5" tokio = { version = "1.25.0", features = ["sync"] } diff --git a/eyeball-im/Cargo.toml b/eyeball-im/Cargo.toml index 9ef7b62..0ff326a 100644 --- a/eyeball-im/Cargo.toml +++ b/eyeball-im/Cargo.toml @@ -17,4 +17,5 @@ tokio-stream.workspace = true tracing = { workspace = true, optional = true } [dev-dependencies] +assert_matches.workspace = true tokio = { workspace = true, features = ["macros", "rt"] } diff --git a/eyeball-im/tests/it.rs b/eyeball-im/tests/it.rs index 781183b..6b2054d 100644 --- a/eyeball-im/tests/it.rs +++ b/eyeball-im/tests/it.rs @@ -1,4 +1,5 @@ -use imbl::Vector; +use assert_matches::assert_matches; +use imbl::{vector, Vector}; use tokio_stream::StreamExt as _; use eyeball_im::{ObservableVector, VectorDiff}; @@ -21,3 +22,17 @@ async fn lag() { }) ); } + +#[tokio::test] +async fn lag2() { + let mut ob: ObservableVector = ObservableVector::with_capacity(2); + let mut sub = ob.subscribe(); + + ob.push_back(0); + ob.append(vector![1, 2]); + ob.push_back(3); + assert_matches!(sub.next().await, Some(VectorDiff::Reset { values }) => { + assert_eq!(values, vector![0, 1, 2]); + }); + assert_matches!(sub.next().await, Some(VectorDiff::PushBack { value: 3 })); +} From a268337408aa13ad1268f54603b0aa9f9f717640 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 31 May 2023 10:15:40 +0200 Subject: [PATCH 2/4] Use new stream_assert crate for eyeball-im tests --- Cargo.toml | 1 + eyeball-im/Cargo.toml | 3 +-- eyeball-im/tests/it.rs | 29 ++++++++++++----------------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54fdd20..97423b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = ["eyeball", "eyeball-im"] assert_matches = "1.5.0" futures-core = "0.3.26" readlock = "0.1.5" +stream_assert = "0.1.0" tokio = { version = "1.25.0", features = ["sync"] } tokio-stream = { version = "0.1.11", default-features = false, features = ["sync"] } tracing = { version = "0.1.37", default-features = false, features = ["std"] } diff --git a/eyeball-im/Cargo.toml b/eyeball-im/Cargo.toml index 0ff326a..33e2201 100644 --- a/eyeball-im/Cargo.toml +++ b/eyeball-im/Cargo.toml @@ -17,5 +17,4 @@ tokio-stream.workspace = true tracing = { workspace = true, optional = true } [dev-dependencies] -assert_matches.workspace = true -tokio = { workspace = true, features = ["macros", "rt"] } +stream_assert.workspace = true diff --git a/eyeball-im/tests/it.rs b/eyeball-im/tests/it.rs index 6b2054d..798fe49 100644 --- a/eyeball-im/tests/it.rs +++ b/eyeball-im/tests/it.rs @@ -1,38 +1,33 @@ -use assert_matches::assert_matches; use imbl::{vector, Vector}; -use tokio_stream::StreamExt as _; +use stream_assert::assert_next_eq; use eyeball_im::{ObservableVector, VectorDiff}; -#[tokio::test] -async fn lag() { +#[test] +fn lag() { let mut ob = ObservableVector::with_capacity(1); let mut rx1 = ob.subscribe(); let mut rx2 = ob.subscribe(); ob.push_back("hello".to_owned()); - assert_eq!(rx1.next().await, Some(VectorDiff::PushBack { value: "hello".to_owned() })); + assert_next_eq!(rx1, VectorDiff::PushBack { value: "hello".to_owned() }); ob.push_back("world".to_owned()); - assert_eq!(rx1.next().await, Some(VectorDiff::PushBack { value: "world".to_owned() })); - assert_eq!( - rx2.next().await, - Some(VectorDiff::Reset { - values: Vector::from_iter(["hello".to_owned(), "world".to_owned()]) - }) + assert_next_eq!(rx1, VectorDiff::PushBack { value: "world".to_owned() }); + assert_next_eq!( + rx2, + VectorDiff::Reset { values: Vector::from_iter(["hello".to_owned(), "world".to_owned()]) } ); } -#[tokio::test] -async fn lag2() { +#[test] +fn lag2() { let mut ob: ObservableVector = ObservableVector::with_capacity(2); let mut sub = ob.subscribe(); ob.push_back(0); ob.append(vector![1, 2]); ob.push_back(3); - assert_matches!(sub.next().await, Some(VectorDiff::Reset { values }) => { - assert_eq!(values, vector![0, 1, 2]); - }); - assert_matches!(sub.next().await, Some(VectorDiff::PushBack { value: 3 })); + assert_next_eq!(sub, VectorDiff::Reset { values: vector![0, 1, 2] }); + assert_next_eq!(sub, VectorDiff::PushBack { value: 3 }); } From f9a38e120ca8c3787c818cda7275c780fa36a3a0 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 26 May 2023 17:12:29 +0200 Subject: [PATCH 3/4] Create eyeball-im-util with FilteredVectorSubscriber API --- Cargo.toml | 3 +- eyeball-im-util/Cargo.toml | 19 +++ eyeball-im-util/src/lib.rs | 5 + eyeball-im-util/src/vector.rs | 223 ++++++++++++++++++++++++++++ eyeball-im-util/tests/it.rs | 272 ++++++++++++++++++++++++++++++++++ 5 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 eyeball-im-util/Cargo.toml create mode 100644 eyeball-im-util/src/lib.rs create mode 100644 eyeball-im-util/src/vector.rs create mode 100644 eyeball-im-util/tests/it.rs diff --git a/Cargo.toml b/Cargo.toml index 97423b2..b42e66d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [workspace] -members = ["eyeball", "eyeball-im"] +members = ["eyeball", "eyeball-im", "eyeball-im-util"] [workspace.dependencies] assert_matches = "1.5.0" futures-core = "0.3.26" +futures-util = { version = "0.3.26", default-features = false } readlock = "0.1.5" stream_assert = "0.1.0" tokio = { version = "1.25.0", features = ["sync"] } diff --git a/eyeball-im-util/Cargo.toml b/eyeball-im-util/Cargo.toml new file mode 100644 index 0000000..b1eaf12 --- /dev/null +++ b/eyeball-im-util/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "eyeball-im-util" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" +description = "Helpful utilities for `eyeball-im`." +repository = "https://github.com/jplatte/eyeball" + +[package.metadata.docs.rs] +all-features = true + +[dependencies] +eyeball-im = { version = "0.2.0", path = "../eyeball-im" } +futures-core.workspace = true +imbl = "2.0.0" +pin-project-lite = "0.2.9" + +[dev-dependencies] +stream_assert.workspace = true diff --git a/eyeball-im-util/src/lib.rs b/eyeball-im-util/src/lib.rs new file mode 100644 index 0000000..f33d18b --- /dev/null +++ b/eyeball-im-util/src/lib.rs @@ -0,0 +1,5 @@ +//! Helpful utilities for [`eyeball-im`][eyeball_im]. + +mod vector; + +pub use vector::{FilteredVectorSubscriber, VectorExt}; diff --git a/eyeball-im-util/src/vector.rs b/eyeball-im-util/src/vector.rs new file mode 100644 index 0000000..e98c144 --- /dev/null +++ b/eyeball-im-util/src/vector.rs @@ -0,0 +1,223 @@ +use std::{ + collections::VecDeque, + ops::Not, + pin::Pin, + task::{self, ready, Poll}, +}; + +use eyeball_im::{ObservableVector, Vector, VectorDiff, VectorSubscriber}; +use futures_core::Stream; +use pin_project_lite::pin_project; + +pub trait VectorExt +where + T: Clone + Send + Sync + 'static, +{ + fn subscribe_filtered(&self, filter: F) -> (Vector, FilteredVectorSubscriber) + where + F: Fn(&T) -> bool + Unpin; +} + +impl VectorExt for ObservableVector +where + T: Clone + Send + Sync + 'static, +{ + fn subscribe_filtered(&self, filter: F) -> (Vector, FilteredVectorSubscriber) + where + F: Fn(&T) -> bool + Unpin, + { + let mut filtered_indices = VecDeque::new(); + let mut v = (*self).clone(); + + let mut original_idx = 0; + v.retain(|val| { + let keep = filter(val); + if keep { + filtered_indices.push_back(original_idx); + } + original_idx += 1; + keep + }); + + let inner = self.subscribe(); + let original_len = self.len(); + let sub = FilteredVectorSubscriber { inner, filter, filtered_indices, original_len }; + + (v, sub) + } +} + +pin_project! { + pub struct FilteredVectorSubscriber { + #[pin] + inner: VectorSubscriber, + filter: F, + filtered_indices: VecDeque, + original_len: usize, + } +} + +impl FilteredVectorSubscriber +where + T: Clone + Send + Sync + 'static, + F: Fn(&T) -> bool, +{ + fn append(mut self: Pin<&mut Self>, mut values: Vector) -> Option> { + let mut original_idx = self.original_len; + self.original_len += values.len(); + values.retain(|value| { + let keep = (self.filter)(value); + if keep { + self.filtered_indices.push_back(original_idx); + } + original_idx += 1; + keep + }); + + values.is_empty().not().then_some(values) + } + + fn handle_append(self: Pin<&mut Self>, values: Vector) -> Option> { + self.append(values).map(|values| VectorDiff::Append { values }) + } + + fn handle_clear(mut self: Pin<&mut Self>) -> Option> { + self.filtered_indices.clear(); + self.original_len = 0; + Some(VectorDiff::Clear) + } + + fn handle_push_front(mut self: Pin<&mut Self>, value: T) -> Option> { + self.original_len += 1; + for idx in &mut self.filtered_indices { + *idx += 1; + } + + (self.filter)(&value).then(|| { + self.filtered_indices.push_front(0); + VectorDiff::PushFront { value } + }) + } + + fn handle_push_back(mut self: Pin<&mut Self>, value: T) -> Option> { + let original_idx = self.original_len; + self.original_len += 1; + (self.filter)(&value).then(|| { + self.filtered_indices.push_back(original_idx); + VectorDiff::PushBack { value } + }) + } + + fn handle_pop_front(mut self: Pin<&mut Self>) -> Option> { + self.original_len -= 1; + let result = self.filtered_indices.front().map_or(false, |&idx| idx == 0).then(|| { + assert!(self.filtered_indices.pop_front().is_some()); + VectorDiff::PopFront + }); + for idx in &mut self.filtered_indices { + *idx -= 1; + } + + result + } + + fn handle_pop_back(mut self: Pin<&mut Self>) -> Option> { + self.original_len -= 1; + self.filtered_indices.back().map_or(false, |&idx| idx == self.original_len).then(|| { + assert!(self.filtered_indices.pop_back().is_some()); + VectorDiff::PopBack + }) + } + + fn handle_insert(mut self: Pin<&mut Self>, index: usize, value: T) -> Option> { + let original_idx = index; + let index = self.filtered_indices.partition_point(|&i| i < original_idx); + for idx in self.filtered_indices.iter_mut().skip(index) { + *idx += 1; + } + + (self.filter)(&value).then(|| { + self.filtered_indices.insert(index, original_idx); + VectorDiff::Insert { index, value } + }) + } + + fn handle_set(mut self: Pin<&mut Self>, index: usize, value: T) -> Option> { + let original_idx = index; + let new_value_matches = (self.filter)(&value); + + let index = self.filtered_indices.partition_point(|&i| i < original_idx); + if self.filtered_indices.get(index).map_or(false, |&i| i == original_idx) { + // The previous value matched the filter + Some(if new_value_matches { + VectorDiff::Set { index, value } + } else { + self.filtered_indices.remove(index); + VectorDiff::Remove { index } + }) + } else { + // The previous value didn't match the filter + new_value_matches.then(|| { + self.filtered_indices.insert(index, original_idx); + VectorDiff::Insert { index, value } + }) + } + } + + fn handle_remove(mut self: Pin<&mut Self>, index: usize) -> Option> { + let original_idx = index; + self.original_len -= 1; + + let index = self.filtered_indices.partition_point(|&i| i < original_idx); + let result = + self.filtered_indices.get(index).map_or(false, |&i| i == original_idx).then(|| { + // The value that was removed matched the filter + self.filtered_indices.remove(index); + VectorDiff::Remove { index } + }); + + for idx in self.filtered_indices.iter_mut().skip(index) { + *idx -= 1; + } + + result + } + + fn handle_reset(mut self: Pin<&mut Self>, values: Vector) -> Option> { + self.filtered_indices.clear(); + self.original_len = 0; + self.append(values).map(|values| VectorDiff::Reset { values }) + } +} + +impl Stream for FilteredVectorSubscriber +where + F: Fn(&T) -> bool + Unpin, +{ + type Item = VectorDiff; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + loop { + let Some(diff) = ready!(self.as_mut().project().inner.poll_next(cx)) else { + return Poll::Ready(None); + }; + + let result = match diff { + VectorDiff::Append { values } => self.as_mut().handle_append(values), + VectorDiff::Clear => self.as_mut().handle_clear(), + VectorDiff::PushFront { value } => self.as_mut().handle_push_front(value), + VectorDiff::PushBack { value } => self.as_mut().handle_push_back(value), + VectorDiff::PopFront => self.as_mut().handle_pop_front(), + VectorDiff::PopBack => self.as_mut().handle_pop_back(), + VectorDiff::Insert { index, value } => self.as_mut().handle_insert(index, value), + VectorDiff::Set { index, value } => self.as_mut().handle_set(index, value), + VectorDiff::Remove { index } => self.as_mut().handle_remove(index), + VectorDiff::Reset { values } => self.as_mut().handle_reset(values), + }; + + if let Some(diff) = result { + return Poll::Ready(Some(diff)); + } + } + } +} diff --git a/eyeball-im-util/tests/it.rs b/eyeball-im-util/tests/it.rs new file mode 100644 index 0000000..3896b3d --- /dev/null +++ b/eyeball-im-util/tests/it.rs @@ -0,0 +1,272 @@ +use eyeball_im::{ObservableVector, VectorDiff}; +use eyeball_im_util::VectorExt; +use imbl::vector; +use stream_assert::{assert_closed, assert_next_eq, assert_pending}; + +#[test] +fn append() { + let mut ob: ObservableVector<&str> = ObservableVector::new(); + let (_, mut sub) = ob.subscribe_filtered(|s| s.len() < 8); + + ob.append(vector!["hello", "world"]); + assert_next_eq!(sub, VectorDiff::Append { values: vector!["hello", "world"] }); + + ob.append(vector!["hello, world!"]); + assert_pending!(sub); + + ob.append(vector!["goodbye"]); + assert_next_eq!(sub, VectorDiff::Append { values: vector!["goodbye"] }); + + drop(ob); + assert_closed!(sub); +} + +#[test] +fn append_clear() { + let mut ob: ObservableVector = ObservableVector::new(); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.append(vector![1024]); + assert_pending!(sub); + + ob.append(vector![1, 2, 3]); + assert_next_eq!(sub, VectorDiff::Append { values: vector![1, 2, 3] }); + + ob.clear(); + assert_next_eq!(sub, VectorDiff::Clear); + + ob.append(vector![999, 256, 1234]); + assert_pending!(sub); + + ob.append(vector![255, 127]); + assert_next_eq!(sub, VectorDiff::Append { values: vector![255, 127] }); + assert_pending!(sub); +} + +#[test] +fn buffering() { + let mut ob: ObservableVector = ObservableVector::new(); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.append(vector![1024]); + ob.append(vector![1, 2, 3]); + ob.clear(); + ob.append(vector![255, 127]); + ob.append(vector![999, 256, 1234]); + + assert_next_eq!(sub, VectorDiff::Append { values: vector![1, 2, 3] }); + assert_next_eq!(sub, VectorDiff::Clear); + assert_next_eq!(sub, VectorDiff::Append { values: vector![255, 127] }); + assert_pending!(sub); +} + +#[test] +fn push_front() { + let mut ob: ObservableVector = ObservableVector::new(); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.push_front(1); + assert_next_eq!(sub, VectorDiff::PushFront { value: 1 }); + ob.push_front(1024); + assert_pending!(sub); + ob.push_front(2); + assert_next_eq!(sub, VectorDiff::PushFront { value: 2 }); + + ob.clear(); + ob.push_front(256); + assert_next_eq!(sub, VectorDiff::Clear); + assert_pending!(sub); + ob.push_front(55); + assert_next_eq!(sub, VectorDiff::PushFront { value: 55 }); +} + +#[test] +fn push_back() { + let mut ob: ObservableVector = ObservableVector::new(); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.push_back(1); + assert_next_eq!(sub, VectorDiff::PushBack { value: 1 }); + ob.push_back(1024); + assert_pending!(sub); + ob.push_back(2); + assert_next_eq!(sub, VectorDiff::PushBack { value: 2 }); + + ob.clear(); + ob.push_back(256); + ob.push_back(55); + assert_next_eq!(sub, VectorDiff::Clear); + assert_next_eq!(sub, VectorDiff::PushBack { value: 55 }); +} + +#[test] +fn push_both() { + let mut ob: ObservableVector = ObservableVector::new(); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.push_back(1); + ob.push_front(2); + assert_next_eq!(sub, VectorDiff::PushBack { value: 1 }); + assert_next_eq!(sub, VectorDiff::PushFront { value: 2 }); + + ob.push_front(777); + ob.push_front(511); + assert_pending!(sub); + + ob.push_front(4); + ob.push_front(511); + ob.push_back(123); + assert_next_eq!(sub, VectorDiff::PushFront { value: 4 }); + assert_next_eq!(sub, VectorDiff::PushBack { value: 123 }); +} + +#[test] +fn pop_front() { + let mut ob: ObservableVector = ObservableVector::from(vector![1, 2, 3]); + let (items, mut sub) = ob.subscribe_filtered(|&i| i < 256); + assert_eq!(items, vector![1, 2, 3]); + + ob.pop_front(); + assert_next_eq!(sub, VectorDiff::PopFront); + ob.pop_front(); + ob.pop_front(); + assert_next_eq!(sub, VectorDiff::PopFront); + assert_next_eq!(sub, VectorDiff::PopFront); + assert_pending!(sub); + + let mut ob: ObservableVector = ObservableVector::from(vector![1000, 2, 3000, 4]); + let (items, mut sub) = ob.subscribe_filtered(|&i| i < 256); + assert_eq!(items, vector![2, 4]); + + ob.pop_front(); + assert_pending!(sub); + ob.pop_front(); + assert_next_eq!(sub, VectorDiff::PopFront); + ob.pop_front(); + ob.pop_front(); + assert_next_eq!(sub, VectorDiff::PopFront); + assert_pending!(sub); +} + +#[test] +fn pop_back() { + let mut ob: ObservableVector = ObservableVector::from(vector![1, 2, 3]); + let (items, mut sub) = ob.subscribe_filtered(|&i| i < 256); + assert_eq!(items, vector![1, 2, 3]); + + ob.pop_back(); + assert_next_eq!(sub, VectorDiff::PopBack); + ob.pop_back(); + ob.pop_back(); + assert_next_eq!(sub, VectorDiff::PopBack); + assert_next_eq!(sub, VectorDiff::PopBack); + assert_pending!(sub); + + let mut ob: ObservableVector = ObservableVector::from(vector![1000, 2, 3000, 4]); + let (items, mut sub) = ob.subscribe_filtered(|&i| i < 256); + assert_eq!(items, vector![2, 4]); + + ob.pop_back(); + assert_next_eq!(sub, VectorDiff::PopBack); + ob.pop_back(); + assert_pending!(sub); + ob.pop_back(); + ob.pop_back(); + assert_next_eq!(sub, VectorDiff::PopBack); + assert_pending!(sub); +} + +#[test] +fn insert() { + let mut ob: ObservableVector = ObservableVector::new(); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.insert(0, 256); + assert_pending!(sub); + ob.insert(1, 123); + assert_next_eq!(sub, VectorDiff::Insert { index: 0, value: 123 }); + ob.insert(0, 234); + assert_next_eq!(sub, VectorDiff::Insert { index: 0, value: 234 }); + ob.insert(1, 1000); + ob.insert(2, 255); + assert_eq!(*ob, vector![234, 1000, 255, 256, 123]); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 255 }); + assert_pending!(sub); + + let mut ob: ObservableVector = + ObservableVector::from(vector![1, 2000, 3000, 4000, 5000, 6]); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + ob.insert(3, 30); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 30 }); + assert_pending!(sub); + ob.insert(4, 400); + assert_pending!(sub); + assert_eq!(*ob, vector![1, 2000, 3000, 30, 400, 4000, 5000, 6]); + ob.insert(8, 80); + assert_next_eq!(sub, VectorDiff::Insert { index: 3, value: 80 }); + assert_pending!(sub); +} + +#[test] +fn set() { + let mut ob: ObservableVector = + ObservableVector::from(vector![0, 1000, 2000, 3000, 4, 5000]); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.set(2, 200); + assert_next_eq!(sub, VectorDiff::Insert { index: 1, value: 200 }); + ob.set(2, 20); + assert_next_eq!(sub, VectorDiff::Set { index: 1, value: 20 }); + ob.set(0, 255); + assert_next_eq!(sub, VectorDiff::Set { index: 0, value: 255 }); + ob.set(4, 4000); + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + ob.set(5, 50); + assert_next_eq!(sub, VectorDiff::Insert { index: 2, value: 50 }); +} + +#[test] +fn remove() { + let mut ob: ObservableVector = + ObservableVector::from(vector![0, 1, 2000, 3000, 4000, 5, 6000]); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.remove(3); // 3000 + assert_pending!(sub); + ob.remove(3); // 4000 + assert_pending!(sub); + ob.remove(3); // 5 + assert_next_eq!(sub, VectorDiff::Remove { index: 2 }); + ob.remove(0); // 0 + assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); + ob.remove(2); // 6000 + assert_pending!(sub); + ob.remove(1); // 2000 + assert_pending!(sub); + ob.remove(0); // 2000 + assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); +} + +#[test] +fn reset() { + let mut ob: ObservableVector = ObservableVector::with_capacity(1); + let (_, mut sub) = ob.subscribe_filtered(|&i| i < 256); + + ob.push_front(0); + ob.append(vector![1000, 2, 3000, 4]); + assert_next_eq!(sub, VectorDiff::Reset { values: vector![0, 2, 4] }); + ob.remove(2); + assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); + ob.remove(1); + assert_pending!(sub); + + ob.pop_front(); + ob.insert(2, 5); + assert_next_eq!(sub, VectorDiff::Reset { values: vector![4, 5] }); + ob.remove(2); + assert_next_eq!(sub, VectorDiff::Remove { index: 1 }); + ob.remove(1); + assert_next_eq!(sub, VectorDiff::Remove { index: 0 }); + ob.remove(0); + assert_pending!(sub); +} From af832bcb5f863c01d29a5649f819f6fe120435b6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 30 May 2023 15:39:47 +0200 Subject: [PATCH 4/4] Don't run eyeball-im-util tests in miri --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2943bd3..715aa12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,9 @@ jobs: with: components: miri - uses: Swatinem/rust-cache@v2 - - run: cargo miri test + # Most eyeball-im-util tests are failing right now, due to Vector::retain + # usage which might be unsound: https://github.com/jneem/imbl/issues/59 + - run: cargo miri test --workspace --exclude eyeball-im-util clippy: name: Run clippy (Rust nightly)