Uncomment flaky tests

This commit is contained in:
Piotr Osiewicz 2023-12-01 19:21:30 +01:00
parent 53f3f960d2
commit 1c52b936bc
6 changed files with 666 additions and 639 deletions

View file

@ -1026,337 +1026,334 @@ fn consolidate_wrap_edits(edits: &mut Vec<WrapEdit>) {
} }
} }
// #[cfg(test)] #[cfg(test)]
// mod tests { mod tests {
// use super::*; use super::*;
// use crate::{ use crate::{
// display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
// MultiBuffer, MultiBuffer,
// }; };
// use gpui::test::observe; use gpui::{font, px, test::observe, Platform};
// use rand::prelude::*; use rand::prelude::*;
// use settings::SettingsStore; use settings::SettingsStore;
// use smol::stream::StreamExt; use smol::stream::StreamExt;
// use std::{cmp, env, num::NonZeroU32}; use std::{cmp, env, num::NonZeroU32};
// use text::Rope; use text::Rope;
use theme::LoadThemes;
// #[gpui::test(iterations = 100)] #[gpui::test(iterations = 100)]
// async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
// init_test(cx); // todo!() this test is flaky
init_test(cx);
// cx.background_executor.set_block_on_ticks(0..=50); cx.background_executor.set_block_on_ticks(0..=50);
// let operations = env::var("OPERATIONS") let operations = env::var("OPERATIONS")
// .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
// .unwrap_or(10); .unwrap_or(10);
// let font_cache = cx.read(|cx| cx.font_cache().clone()); let text_system = cx.test_platform.text_system();
// let font_system = cx.platform().fonts(); let mut wrap_width = if rng.gen_bool(0.1) {
// let mut wrap_width = if rng.gen_bool(0.1) { None
// None } else {
// } else { Some(px(rng.gen_range(0.0..=1000.0)))
// Some(rng.gen_range(0.0..=1000.0)) };
// }; let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
// let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); let font = font("Helvetica");
// let family_id = font_cache let font_id = text_system.font_id(&font).unwrap();
// .load_family(&["Helvetica"], &Default::default()) let font_size = px(14.0);
// .unwrap();
// let font_id = font_cache
// .select_font(family_id, &Default::default())
// .unwrap();
// let font_size = 14.0;
// log::info!("Tab size: {}", tab_size); log::info!("Tab size: {}", tab_size);
// log::info!("Wrap width: {:?}", wrap_width); log::info!("Wrap width: {:?}", wrap_width);
// let buffer = cx.update(|cx| { let buffer = cx.update(|cx| {
// if rng.gen() { if rng.gen() {
// MultiBuffer::build_random(&mut rng, cx) MultiBuffer::build_random(&mut rng, cx)
// } else { } else {
// let len = rng.gen_range(0..10); let len = rng.gen_range(0..10);
// let text = util::RandomCharIter::new(&mut rng) let text = util::RandomCharIter::new(&mut rng)
// .take(len) .take(len)
// .collect::<String>(); .collect::<String>();
// MultiBuffer::build_simple(&text, cx) MultiBuffer::build_simple(&text, cx)
// } }
// }); });
// let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
// log::info!("Buffer text: {:?}", buffer_snapshot.text()); log::info!("Buffer text: {:?}", buffer_snapshot.text());
// let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
// log::info!("InlayMap text: {:?}", inlay_snapshot.text()); log::info!("InlayMap text: {:?}", inlay_snapshot.text());
// let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone()); let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
// log::info!("FoldMap text: {:?}", fold_snapshot.text()); log::info!("FoldMap text: {:?}", fold_snapshot.text());
// let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
// let tabs_snapshot = tab_map.set_max_expansion_column(32); let tabs_snapshot = tab_map.set_max_expansion_column(32);
// log::info!("TabMap text: {:?}", tabs_snapshot.text()); log::info!("TabMap text: {:?}", tabs_snapshot.text());
// let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system); let mut line_wrapper = LineWrapper::new(font_id, font_size, text_system);
// let unwrapped_text = tabs_snapshot.text(); let unwrapped_text = tabs_snapshot.text();
// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
// let (wrap_map, _) = let (wrap_map, _) =
// cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)); cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font, font_size, wrap_width, cx));
// let mut notifications = observe(&wrap_map, cx); let mut notifications = observe(&wrap_map, cx);
// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
// notifications.next().await.unwrap(); notifications.next().await.unwrap();
// } }
// let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| { let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| {
// assert!(!map.is_rewrapping()); assert!(!map.is_rewrapping());
// map.sync(tabs_snapshot.clone(), Vec::new(), cx) map.sync(tabs_snapshot.clone(), Vec::new(), cx)
// }); });
// let actual_text = initial_snapshot.text(); let actual_text = initial_snapshot.text();
// assert_eq!( assert_eq!(
// actual_text, expected_text, actual_text, expected_text,
// "unwrapped text is: {:?}", "unwrapped text is: {:?}",
// unwrapped_text unwrapped_text
// ); );
// log::info!("Wrapped text: {:?}", actual_text); log::info!("Wrapped text: {:?}", actual_text);
// let mut next_inlay_id = 0; let mut next_inlay_id = 0;
// let mut edits = Vec::new(); let mut edits = Vec::new();
// for _i in 0..operations { for _i in 0..operations {
// log::info!("{} ==============================================", _i); log::info!("{} ==============================================", _i);
// let mut buffer_edits = Vec::new(); let mut buffer_edits = Vec::new();
// match rng.gen_range(0..=100) { match rng.gen_range(0..=100) {
// 0..=19 => { 0..=19 => {
// wrap_width = if rng.gen_bool(0.2) { wrap_width = if rng.gen_bool(0.2) {
// None None
// } else { } else {
// Some(rng.gen_range(0.0..=1000.0)) Some(px(rng.gen_range(0.0..=1000.0)))
// }; };
// log::info!("Setting wrap width to {:?}", wrap_width); log::info!("Setting wrap width to {:?}", wrap_width);
// wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
// } }
// 20..=39 => { 20..=39 => {
// for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
// let (tabs_snapshot, tab_edits) = let (tabs_snapshot, tab_edits) =
// tab_map.sync(fold_snapshot, fold_edits, tab_size); tab_map.sync(fold_snapshot, fold_edits, tab_size);
// let (mut snapshot, wrap_edits) = let (mut snapshot, wrap_edits) =
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
// snapshot.check_invariants(); snapshot.check_invariants();
// snapshot.verify_chunks(&mut rng); snapshot.verify_chunks(&mut rng);
// edits.push((snapshot, wrap_edits)); edits.push((snapshot, wrap_edits));
// } }
// } }
// 40..=59 => { 40..=59 => {
// let (inlay_snapshot, inlay_edits) = let (inlay_snapshot, inlay_edits) =
// inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
// let (tabs_snapshot, tab_edits) = let (tabs_snapshot, tab_edits) =
// tab_map.sync(fold_snapshot, fold_edits, tab_size); tab_map.sync(fold_snapshot, fold_edits, tab_size);
// let (mut snapshot, wrap_edits) = let (mut snapshot, wrap_edits) =
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
// snapshot.check_invariants(); snapshot.check_invariants();
// snapshot.verify_chunks(&mut rng); snapshot.verify_chunks(&mut rng);
// edits.push((snapshot, wrap_edits)); edits.push((snapshot, wrap_edits));
// } }
// _ => { _ => {
// buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
// let subscription = buffer.subscribe(); let subscription = buffer.subscribe();
// let edit_count = rng.gen_range(1..=5); let edit_count = rng.gen_range(1..=5);
// buffer.randomly_mutate(&mut rng, edit_count, cx); buffer.randomly_mutate(&mut rng, edit_count, cx);
// buffer_snapshot = buffer.snapshot(cx); buffer_snapshot = buffer.snapshot(cx);
// buffer_edits.extend(subscription.consume()); buffer_edits.extend(subscription.consume());
// }); });
// } }
// } }
// log::info!("Buffer text: {:?}", buffer_snapshot.text()); log::info!("Buffer text: {:?}", buffer_snapshot.text());
// let (inlay_snapshot, inlay_edits) = let (inlay_snapshot, inlay_edits) =
// inlay_map.sync(buffer_snapshot.clone(), buffer_edits); inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
// log::info!("InlayMap text: {:?}", inlay_snapshot.text()); log::info!("InlayMap text: {:?}", inlay_snapshot.text());
// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
// log::info!("FoldMap text: {:?}", fold_snapshot.text()); log::info!("FoldMap text: {:?}", fold_snapshot.text());
// let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
// log::info!("TabMap text: {:?}", tabs_snapshot.text()); log::info!("TabMap text: {:?}", tabs_snapshot.text());
// let unwrapped_text = tabs_snapshot.text(); let unwrapped_text = tabs_snapshot.text();
// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
// let (mut snapshot, wrap_edits) = let (mut snapshot, wrap_edits) =
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx)); wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx));
// snapshot.check_invariants(); snapshot.check_invariants();
// snapshot.verify_chunks(&mut rng); snapshot.verify_chunks(&mut rng);
// edits.push((snapshot, wrap_edits)); edits.push((snapshot, wrap_edits));
// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) { if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
// log::info!("Waiting for wrapping to finish"); log::info!("Waiting for wrapping to finish");
// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
// notifications.next().await.unwrap(); notifications.next().await.unwrap();
// } }
// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty())); wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
// } }
// if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
// let (mut wrapped_snapshot, wrap_edits) = let (mut wrapped_snapshot, wrap_edits) =
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
// let actual_text = wrapped_snapshot.text(); let actual_text = wrapped_snapshot.text();
// let actual_longest_row = wrapped_snapshot.longest_row(); let actual_longest_row = wrapped_snapshot.longest_row();
// log::info!("Wrapping finished: {:?}", actual_text); log::info!("Wrapping finished: {:?}", actual_text);
// wrapped_snapshot.check_invariants(); wrapped_snapshot.check_invariants();
// wrapped_snapshot.verify_chunks(&mut rng); wrapped_snapshot.verify_chunks(&mut rng);
// edits.push((wrapped_snapshot.clone(), wrap_edits)); edits.push((wrapped_snapshot.clone(), wrap_edits));
// assert_eq!( assert_eq!(
// actual_text, expected_text, actual_text, expected_text,
// "unwrapped text is: {:?}", "unwrapped text is: {:?}",
// unwrapped_text unwrapped_text
// ); );
// let mut summary = TextSummary::default(); let mut summary = TextSummary::default();
// for (ix, item) in wrapped_snapshot for (ix, item) in wrapped_snapshot
// .transforms .transforms
// .items(&()) .items(&())
// .into_iter() .into_iter()
// .enumerate() .enumerate()
// { {
// summary += &item.summary.output; summary += &item.summary.output;
// log::info!("{} summary: {:?}", ix, item.summary.output,); log::info!("{} summary: {:?}", ix, item.summary.output,);
// } }
// if tab_size.get() == 1 if tab_size.get() == 1
// || !wrapped_snapshot || !wrapped_snapshot
// .tab_snapshot .tab_snapshot
// .fold_snapshot .fold_snapshot
// .text() .text()
// .contains('\t') .contains('\t')
// { {
// let mut expected_longest_rows = Vec::new(); let mut expected_longest_rows = Vec::new();
// let mut longest_line_len = -1; let mut longest_line_len = -1;
// for (row, line) in expected_text.split('\n').enumerate() { for (row, line) in expected_text.split('\n').enumerate() {
// let line_char_count = line.chars().count() as isize; let line_char_count = line.chars().count() as isize;
// if line_char_count > longest_line_len { if line_char_count > longest_line_len {
// expected_longest_rows.clear(); expected_longest_rows.clear();
// longest_line_len = line_char_count; longest_line_len = line_char_count;
// } }
// if line_char_count >= longest_line_len { if line_char_count >= longest_line_len {
// expected_longest_rows.push(row as u32); expected_longest_rows.push(row as u32);
// } }
// } }
// assert!( assert!(
// expected_longest_rows.contains(&actual_longest_row), expected_longest_rows.contains(&actual_longest_row),
// "incorrect longest row {}. expected {:?} with length {}", "incorrect longest row {}. expected {:?} with length {}",
// actual_longest_row, actual_longest_row,
// expected_longest_rows, expected_longest_rows,
// longest_line_len, longest_line_len,
// ) )
// } }
// } }
// } }
// let mut initial_text = Rope::from(initial_snapshot.text().as_str()); let mut initial_text = Rope::from(initial_snapshot.text().as_str());
// for (snapshot, patch) in edits { for (snapshot, patch) in edits {
// let snapshot_text = Rope::from(snapshot.text().as_str()); let snapshot_text = Rope::from(snapshot.text().as_str());
// for edit in &patch { for edit in &patch {
// let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0)); let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
// let old_end = initial_text.point_to_offset(cmp::min( let old_end = initial_text.point_to_offset(cmp::min(
// Point::new(edit.new.start + edit.old.len() as u32, 0), Point::new(edit.new.start + edit.old.len() as u32, 0),
// initial_text.max_point(), initial_text.max_point(),
// )); ));
// let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0)); let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
// let new_end = snapshot_text.point_to_offset(cmp::min( let new_end = snapshot_text.point_to_offset(cmp::min(
// Point::new(edit.new.end, 0), Point::new(edit.new.end, 0),
// snapshot_text.max_point(), snapshot_text.max_point(),
// )); ));
// let new_text = snapshot_text let new_text = snapshot_text
// .chunks_in_range(new_start..new_end) .chunks_in_range(new_start..new_end)
// .collect::<String>(); .collect::<String>();
// initial_text.replace(old_start..old_end, &new_text); initial_text.replace(old_start..old_end, &new_text);
// } }
// assert_eq!(initial_text.to_string(), snapshot_text.to_string()); assert_eq!(initial_text.to_string(), snapshot_text.to_string());
// } }
// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
// log::info!("Waiting for wrapping to finish"); log::info!("Waiting for wrapping to finish");
// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) { while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
// notifications.next().await.unwrap(); notifications.next().await.unwrap();
// } }
// } }
// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty())); wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
// } }
// fn init_test(cx: &mut gpui::TestAppContext) { fn init_test(cx: &mut gpui::TestAppContext) {
// cx.foreground_executor().forbid_parking(); cx.update(|cx| {
// cx.update(|cx| { let settings = SettingsStore::test(cx);
// cx.set_global(SettingsStore::test(cx)); cx.set_global(settings);
// theme::init((), cx); theme::init(LoadThemes::JustBase, cx);
// }); });
// } }
// fn wrap_text( fn wrap_text(
// unwrapped_text: &str, unwrapped_text: &str,
// wrap_width: Option<f32>, wrap_width: Option<Pixels>,
// line_wrapper: &mut LineWrapper, line_wrapper: &mut LineWrapper,
// ) -> String { ) -> String {
// if let Some(wrap_width) = wrap_width { if let Some(wrap_width) = wrap_width {
// let mut wrapped_text = String::new(); let mut wrapped_text = String::new();
// for (row, line) in unwrapped_text.split('\n').enumerate() { for (row, line) in unwrapped_text.split('\n').enumerate() {
// if row > 0 { if row > 0 {
// wrapped_text.push('\n') wrapped_text.push('\n')
// } }
// let mut prev_ix = 0; let mut prev_ix = 0;
// for boundary in line_wrapper.wrap_line(line, wrap_width) { for boundary in line_wrapper.wrap_line(line, wrap_width) {
// wrapped_text.push_str(&line[prev_ix..boundary.ix]); wrapped_text.push_str(&line[prev_ix..boundary.ix]);
// wrapped_text.push('\n'); wrapped_text.push('\n');
// wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize)); wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
// prev_ix = boundary.ix; prev_ix = boundary.ix;
// } }
// wrapped_text.push_str(&line[prev_ix..]); wrapped_text.push_str(&line[prev_ix..]);
// } }
// wrapped_text wrapped_text
// } else { } else {
// unwrapped_text.to_string() unwrapped_text.to_string()
// } }
// } }
// impl WrapSnapshot { impl WrapSnapshot {
// pub fn text(&self) -> String { pub fn text(&self) -> String {
// self.text_chunks(0).collect() self.text_chunks(0).collect()
// } }
// pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> { pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
// self.chunks( self.chunks(
// wrap_row..self.max_point().row() + 1, wrap_row..self.max_point().row() + 1,
// false, false,
// Highlights::default(), Highlights::default(),
// ) )
// .map(|h| h.text) .map(|h| h.text)
// } }
// fn verify_chunks(&mut self, rng: &mut impl Rng) { fn verify_chunks(&mut self, rng: &mut impl Rng) {
// for _ in 0..5 { for _ in 0..5 {
// let mut end_row = rng.gen_range(0..=self.max_point().row()); let mut end_row = rng.gen_range(0..=self.max_point().row());
// let start_row = rng.gen_range(0..=end_row); let start_row = rng.gen_range(0..=end_row);
// end_row += 1; end_row += 1;
// let mut expected_text = self.text_chunks(start_row).collect::<String>(); let mut expected_text = self.text_chunks(start_row).collect::<String>();
// if expected_text.ends_with('\n') { if expected_text.ends_with('\n') {
// expected_text.push('\n'); expected_text.push('\n');
// } }
// let mut expected_text = expected_text let mut expected_text = expected_text
// .lines() .lines()
// .take((end_row - start_row) as usize) .take((end_row - start_row) as usize)
// .collect::<Vec<_>>() .collect::<Vec<_>>()
// .join("\n"); .join("\n");
// if end_row <= self.max_point().row() { if end_row <= self.max_point().row() {
// expected_text.push('\n'); expected_text.push('\n');
// } }
// let actual_text = self let actual_text = self
// .chunks(start_row..end_row, true, Highlights::default()) .chunks(start_row..end_row, true, Highlights::default())
// .map(|c| c.text) .map(|c| c.text)
// .collect::<String>(); .collect::<String>();
// assert_eq!( assert_eq!(
// expected_text, expected_text,
// actual_text, actual_text,
// "chunks != highlighted_chunks for rows {:?}", "chunks != highlighted_chunks for rows {:?}",
// start_row..end_row start_row..end_row
// ); );
// } }
// } }
// } }
// } }

View file

@ -2401,346 +2401,347 @@ pub mod tests {
}); });
} }
// #[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
// async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) { async fn test_multiple_excerpts_large_multibuffer(cx: &mut gpui::TestAppContext) {
// init_test(cx, |settings| { // todo!() this test is flaky
// settings.defaults.inlay_hints = Some(InlayHintSettings { init_test(cx, |settings| {
// enabled: true, settings.defaults.inlay_hints = Some(InlayHintSettings {
// show_type_hints: true, enabled: true,
// show_parameter_hints: true, show_type_hints: true,
// show_other_hints: true, show_parameter_hints: true,
// }) show_other_hints: true,
// }); })
});
// let mut language = Language::new( let mut language = Language::new(
// LanguageConfig { LanguageConfig {
// name: "Rust".into(), name: "Rust".into(),
// path_suffixes: vec!["rs".to_string()], path_suffixes: vec!["rs".to_string()],
// ..Default::default() ..Default::default()
// }, },
// Some(tree_sitter_rust::language()), Some(tree_sitter_rust::language()),
// ); );
// let mut fake_servers = language let mut fake_servers = language
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
// capabilities: lsp::ServerCapabilities { capabilities: lsp::ServerCapabilities {
// inlay_hint_provider: Some(lsp::OneOf::Left(true)), inlay_hint_provider: Some(lsp::OneOf::Left(true)),
// ..Default::default() ..Default::default()
// }, },
// ..Default::default() ..Default::default()
// })) }))
// .await; .await;
// let language = Arc::new(language); let language = Arc::new(language);
// let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
// fs.insert_tree( fs.insert_tree(
// "/a", "/a",
// json!({ json!({
// "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")), "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
// "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")), "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
// }), }),
// ) )
// .await; .await;
// let project = Project::test(fs, ["/a".as_ref()], cx).await; let project = Project::test(fs, ["/a".as_ref()], cx).await;
// project.update(cx, |project, _| { project.update(cx, |project, _| {
// project.languages().add(Arc::clone(&language)) project.languages().add(Arc::clone(&language))
// }); });
// let worktree_id = project.update(cx, |project, cx| { let worktree_id = project.update(cx, |project, cx| {
// project.worktrees().next().unwrap().read(cx).id() project.worktrees().next().unwrap().read(cx).id()
// }); });
// let buffer_1 = project let buffer_1 = project
// .update(cx, |project, cx| { .update(cx, |project, cx| {
// project.open_buffer((worktree_id, "main.rs"), cx) project.open_buffer((worktree_id, "main.rs"), cx)
// }) })
// .await .await
// .unwrap(); .unwrap();
// let buffer_2 = project let buffer_2 = project
// .update(cx, |project, cx| { .update(cx, |project, cx| {
// project.open_buffer((worktree_id, "other.rs"), cx) project.open_buffer((worktree_id, "other.rs"), cx)
// }) })
// .await .await
// .unwrap(); .unwrap();
// let multibuffer = cx.build_model(|cx| { let multibuffer = cx.build_model(|cx| {
// let mut multibuffer = MultiBuffer::new(0); let mut multibuffer = MultiBuffer::new(0);
// multibuffer.push_excerpts( multibuffer.push_excerpts(
// buffer_1.clone(), buffer_1.clone(),
// [ [
// ExcerptRange { ExcerptRange {
// context: Point::new(0, 0)..Point::new(2, 0), context: Point::new(0, 0)..Point::new(2, 0),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(4, 0)..Point::new(11, 0), context: Point::new(4, 0)..Point::new(11, 0),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(22, 0)..Point::new(33, 0), context: Point::new(22, 0)..Point::new(33, 0),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(44, 0)..Point::new(55, 0), context: Point::new(44, 0)..Point::new(55, 0),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(56, 0)..Point::new(66, 0), context: Point::new(56, 0)..Point::new(66, 0),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(67, 0)..Point::new(77, 0), context: Point::new(67, 0)..Point::new(77, 0),
// primary: None, primary: None,
// }, },
// ], ],
// cx, cx,
// ); );
// multibuffer.push_excerpts( multibuffer.push_excerpts(
// buffer_2.clone(), buffer_2.clone(),
// [ [
// ExcerptRange { ExcerptRange {
// context: Point::new(0, 1)..Point::new(2, 1), context: Point::new(0, 1)..Point::new(2, 1),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(4, 1)..Point::new(11, 1), context: Point::new(4, 1)..Point::new(11, 1),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(22, 1)..Point::new(33, 1), context: Point::new(22, 1)..Point::new(33, 1),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(44, 1)..Point::new(55, 1), context: Point::new(44, 1)..Point::new(55, 1),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(56, 1)..Point::new(66, 1), context: Point::new(56, 1)..Point::new(66, 1),
// primary: None, primary: None,
// }, },
// ExcerptRange { ExcerptRange {
// context: Point::new(67, 1)..Point::new(77, 1), context: Point::new(67, 1)..Point::new(77, 1),
// primary: None, primary: None,
// }, },
// ], ],
// cx, cx,
// ); );
// multibuffer multibuffer
// }); });
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// let editor = let editor =
// cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
// let editor_edited = Arc::new(AtomicBool::new(false)); let editor_edited = Arc::new(AtomicBool::new(false));
// let fake_server = fake_servers.next().await.unwrap(); let fake_server = fake_servers.next().await.unwrap();
// let closure_editor_edited = Arc::clone(&editor_edited); let closure_editor_edited = Arc::clone(&editor_edited);
// fake_server fake_server
// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| { .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
// let task_editor_edited = Arc::clone(&closure_editor_edited); let task_editor_edited = Arc::clone(&closure_editor_edited);
// async move { async move {
// let hint_text = if params.text_document.uri let hint_text = if params.text_document.uri
// == lsp::Url::from_file_path("/a/main.rs").unwrap() == lsp::Url::from_file_path("/a/main.rs").unwrap()
// { {
// "main hint" "main hint"
// } else if params.text_document.uri } else if params.text_document.uri
// == lsp::Url::from_file_path("/a/other.rs").unwrap() == lsp::Url::from_file_path("/a/other.rs").unwrap()
// { {
// "other hint" "other hint"
// } else { } else {
// panic!("unexpected uri: {:?}", params.text_document.uri); panic!("unexpected uri: {:?}", params.text_document.uri);
// }; };
// // one hint per excerpt // one hint per excerpt
// let positions = [ let positions = [
// lsp::Position::new(0, 2), lsp::Position::new(0, 2),
// lsp::Position::new(4, 2), lsp::Position::new(4, 2),
// lsp::Position::new(22, 2), lsp::Position::new(22, 2),
// lsp::Position::new(44, 2), lsp::Position::new(44, 2),
// lsp::Position::new(56, 2), lsp::Position::new(56, 2),
// lsp::Position::new(67, 2), lsp::Position::new(67, 2),
// ]; ];
// let out_of_range_hint = lsp::InlayHint { let out_of_range_hint = lsp::InlayHint {
// position: lsp::Position::new( position: lsp::Position::new(
// params.range.start.line + 99, params.range.start.line + 99,
// params.range.start.character + 99, params.range.start.character + 99,
// ), ),
// label: lsp::InlayHintLabel::String( label: lsp::InlayHintLabel::String(
// "out of excerpt range, should be ignored".to_string(), "out of excerpt range, should be ignored".to_string(),
// ), ),
// kind: None, kind: None,
// text_edits: None, text_edits: None,
// tooltip: None, tooltip: None,
// padding_left: None, padding_left: None,
// padding_right: None, padding_right: None,
// data: None, data: None,
// }; };
// let edited = task_editor_edited.load(Ordering::Acquire); let edited = task_editor_edited.load(Ordering::Acquire);
// Ok(Some( Ok(Some(
// std::iter::once(out_of_range_hint) std::iter::once(out_of_range_hint)
// .chain(positions.into_iter().enumerate().map(|(i, position)| { .chain(positions.into_iter().enumerate().map(|(i, position)| {
// lsp::InlayHint { lsp::InlayHint {
// position, position,
// label: lsp::InlayHintLabel::String(format!( label: lsp::InlayHintLabel::String(format!(
// "{hint_text}{} #{i}", "{hint_text}{} #{i}",
// if edited { "(edited)" } else { "" }, if edited { "(edited)" } else { "" },
// )), )),
// kind: None, kind: None,
// text_edits: None, text_edits: None,
// tooltip: None, tooltip: None,
// padding_left: None, padding_left: None,
// padding_right: None, padding_right: None,
// data: None, data: None,
// } }
// })) }))
// .collect(), .collect(),
// )) ))
// } }
// }) })
// .next() .next()
// .await; .await;
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// let expected_hints = vec![ let expected_hints = vec![
// "main hint #0".to_string(), "main hint #0".to_string(),
// "main hint #1".to_string(), "main hint #1".to_string(),
// "main hint #2".to_string(), "main hint #2".to_string(),
// "main hint #3".to_string(), "main hint #3".to_string(),
// // todo!() there used to be no these hints, but new gpui2 presumably scrolls a bit farther // todo!() there used to be no these hints, but new gpui2 presumably scrolls a bit farther
// // (or renders less?) note that tests below pass // (or renders less?) note that tests below pass
// "main hint #4".to_string(), "main hint #4".to_string(),
// "main hint #5".to_string(), "main hint #5".to_string(),
// ]; ];
// assert_eq!( assert_eq!(
// expected_hints, expected_hints,
// cached_hint_labels(editor), cached_hint_labels(editor),
// "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints" "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
// ); );
// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(expected_hints, visible_hint_labels(editor, cx));
// assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison"); assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
// }); });
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// editor.change_selections(Some(Autoscroll::Next), cx, |s| { editor.change_selections(Some(Autoscroll::Next), cx, |s| {
// s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
// }); });
// editor.change_selections(Some(Autoscroll::Next), cx, |s| { editor.change_selections(Some(Autoscroll::Next), cx, |s| {
// s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) s.select_ranges([Point::new(22, 0)..Point::new(22, 0)])
// }); });
// editor.change_selections(Some(Autoscroll::Next), cx, |s| { editor.change_selections(Some(Autoscroll::Next), cx, |s| {
// s.select_ranges([Point::new(50, 0)..Point::new(50, 0)]) s.select_ranges([Point::new(50, 0)..Point::new(50, 0)])
// }); });
// }); });
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// let expected_hints = vec![ let expected_hints = vec![
// "main hint #0".to_string(), "main hint #0".to_string(),
// "main hint #1".to_string(), "main hint #1".to_string(),
// "main hint #2".to_string(), "main hint #2".to_string(),
// "main hint #3".to_string(), "main hint #3".to_string(),
// "main hint #4".to_string(), "main hint #4".to_string(),
// "main hint #5".to_string(), "main hint #5".to_string(),
// "other hint #0".to_string(), "other hint #0".to_string(),
// "other hint #1".to_string(), "other hint #1".to_string(),
// "other hint #2".to_string(), "other hint #2".to_string(),
// ]; ];
// assert_eq!(expected_hints, cached_hint_labels(editor), assert_eq!(expected_hints, cached_hint_labels(editor),
// "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits"); "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(expected_hints, visible_hint_labels(editor, cx));
// assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
// "Due to every excerpt having one hint, we update cache per new excerpt scrolled"); "Due to every excerpt having one hint, we update cache per new excerpt scrolled");
// }); });
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// editor.change_selections(Some(Autoscroll::Next), cx, |s| { editor.change_selections(Some(Autoscroll::Next), cx, |s| {
// s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) s.select_ranges([Point::new(100, 0)..Point::new(100, 0)])
// }); });
// }); });
// cx.executor().advance_clock(Duration::from_millis( cx.executor().advance_clock(Duration::from_millis(
// INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100, INVISIBLE_RANGES_HINTS_REQUEST_DELAY_MILLIS + 100,
// )); ));
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// let last_scroll_update_version = editor.update(cx, |editor, cx| { let last_scroll_update_version = editor.update(cx, |editor, cx| {
// let expected_hints = vec![ let expected_hints = vec![
// "main hint #0".to_string(), "main hint #0".to_string(),
// "main hint #1".to_string(), "main hint #1".to_string(),
// "main hint #2".to_string(), "main hint #2".to_string(),
// "main hint #3".to_string(), "main hint #3".to_string(),
// "main hint #4".to_string(), "main hint #4".to_string(),
// "main hint #5".to_string(), "main hint #5".to_string(),
// "other hint #0".to_string(), "other hint #0".to_string(),
// "other hint #1".to_string(), "other hint #1".to_string(),
// "other hint #2".to_string(), "other hint #2".to_string(),
// "other hint #3".to_string(), "other hint #3".to_string(),
// "other hint #4".to_string(), "other hint #4".to_string(),
// "other hint #5".to_string(), "other hint #5".to_string(),
// ]; ];
// assert_eq!(expected_hints, cached_hint_labels(editor), assert_eq!(expected_hints, cached_hint_labels(editor),
// "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched"); "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(expected_hints, visible_hint_labels(editor, cx));
// assert_eq!(editor.inlay_hint_cache().version, expected_hints.len()); assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
// expected_hints.len() expected_hints.len()
// }).unwrap(); }).unwrap();
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// editor.change_selections(Some(Autoscroll::Next), cx, |s| { editor.change_selections(Some(Autoscroll::Next), cx, |s| {
// s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) s.select_ranges([Point::new(4, 0)..Point::new(4, 0)])
// }); });
// }); });
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// let expected_hints = vec![ let expected_hints = vec![
// "main hint #0".to_string(), "main hint #0".to_string(),
// "main hint #1".to_string(), "main hint #1".to_string(),
// "main hint #2".to_string(), "main hint #2".to_string(),
// "main hint #3".to_string(), "main hint #3".to_string(),
// "main hint #4".to_string(), "main hint #4".to_string(),
// "main hint #5".to_string(), "main hint #5".to_string(),
// "other hint #0".to_string(), "other hint #0".to_string(),
// "other hint #1".to_string(), "other hint #1".to_string(),
// "other hint #2".to_string(), "other hint #2".to_string(),
// "other hint #3".to_string(), "other hint #3".to_string(),
// "other hint #4".to_string(), "other hint #4".to_string(),
// "other hint #5".to_string(), "other hint #5".to_string(),
// ]; ];
// assert_eq!(expected_hints, cached_hint_labels(editor), assert_eq!(expected_hints, cached_hint_labels(editor),
// "After multibuffer was scrolled to the end, further scrolls up should not bring more hints"); "After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(expected_hints, visible_hint_labels(editor, cx));
// assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer"); assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
// }); });
// editor_edited.store(true, Ordering::Release); editor_edited.store(true, Ordering::Release);
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// editor.change_selections(None, cx, |s| { editor.change_selections(None, cx, |s| {
// s.select_ranges([Point::new(56, 0)..Point::new(56, 0)]) s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
// }); });
// editor.handle_input("++++more text++++", cx); editor.handle_input("++++more text++++", cx);
// }); });
// cx.executor().run_until_parked(); cx.executor().run_until_parked();
// editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
// let expected_hints = vec![ let expected_hints = vec![
// "main hint(edited) #0".to_string(), "main hint(edited) #0".to_string(),
// "main hint(edited) #1".to_string(), "main hint(edited) #1".to_string(),
// "main hint(edited) #2".to_string(), "main hint(edited) #2".to_string(),
// "main hint(edited) #3".to_string(), "main hint(edited) #3".to_string(),
// "main hint(edited) #4".to_string(), "main hint(edited) #4".to_string(),
// "main hint(edited) #5".to_string(), "main hint(edited) #5".to_string(),
// "other hint(edited) #0".to_string(), "other hint(edited) #0".to_string(),
// "other hint(edited) #1".to_string(), "other hint(edited) #1".to_string(),
// ]; ];
// assert_eq!( assert_eq!(
// expected_hints, expected_hints,
// cached_hint_labels(editor), cached_hint_labels(editor),
// "After multibuffer edit, editor gets scolled back to the last selection; \ "After multibuffer edit, editor gets scolled back to the last selection; \
// all hints should be invalidated and requeried for all of its visible excerpts" all hints should be invalidated and requeried for all of its visible excerpts"
// ); );
// assert_eq!(expected_hints, visible_hint_labels(editor, cx)); assert_eq!(expected_hints, visible_hint_labels(editor, cx));
// let current_cache_version = editor.inlay_hint_cache().version; let current_cache_version = editor.inlay_hint_cache().version;
// let minimum_expected_version = last_scroll_update_version + expected_hints.len(); let minimum_expected_version = last_scroll_update_version + expected_hints.len();
// assert!( assert!(
// current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1, current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1,
// "Due to every excerpt having one hint, cache should update per new excerpt received + 1 potential sporadic update" "Due to every excerpt having one hint, cache should update per new excerpt received + 1 potential sporadic update"
// ); );
// }); });
// } }
#[gpui::test] #[gpui::test]
async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) { async fn test_excerpts_removed(cx: &mut gpui::TestAppContext) {

View file

@ -21,7 +21,7 @@ mod subscription;
mod svg_renderer; mod svg_renderer;
mod taffy; mod taffy;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
mod test; pub mod test;
mod text_system; mod text_system;
mod util; mod util;
mod view; mod view;

View file

@ -44,7 +44,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
Rc::new(MacPlatform::new()) Rc::new(MacPlatform::new())
} }
pub(crate) trait Platform: 'static { pub trait Platform: 'static {
fn background_executor(&self) -> BackgroundExecutor; fn background_executor(&self) -> BackgroundExecutor;
fn foreground_executor(&self) -> ForegroundExecutor; fn foreground_executor(&self) -> ForegroundExecutor;
fn text_system(&self) -> Arc<dyn PlatformTextSystem>; fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
@ -128,7 +128,7 @@ impl Debug for DisplayId {
unsafe impl Send for DisplayId {} unsafe impl Send for DisplayId {}
pub(crate) trait PlatformWindow { pub trait PlatformWindow {
fn bounds(&self) -> WindowBounds; fn bounds(&self) -> WindowBounds;
fn content_size(&self) -> Size<Pixels>; fn content_size(&self) -> Size<Pixels>;
fn scale_factor(&self) -> f32; fn scale_factor(&self) -> f32;

View file

@ -198,7 +198,7 @@ impl SceneBuilder {
} }
} }
pub(crate) struct Scene { pub struct Scene {
pub shadows: Vec<Shadow>, pub shadows: Vec<Shadow>,
pub quads: Vec<Quad>, pub quads: Vec<Quad>,
pub paths: Vec<Path<ScaledPixels>>, pub paths: Vec<Path<ScaledPixels>>,
@ -214,7 +214,7 @@ impl Scene {
&self.paths &self.paths
} }
pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> { pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
BatchIterator { BatchIterator {
shadows: &self.shadows, shadows: &self.shadows,
shadows_start: 0, shadows_start: 0,

View file

@ -1,5 +1,7 @@
use crate::TestDispatcher; use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
use futures::StreamExt as _;
use rand::prelude::*; use rand::prelude::*;
use smol::channel;
use std::{ use std::{
env, env,
panic::{self, RefUnwindSafe}, panic::{self, RefUnwindSafe},
@ -49,3 +51,30 @@ pub fn run_test(
} }
} }
} }
pub struct Observation<T> {
rx: channel::Receiver<T>,
_subscription: Subscription,
}
impl<T: 'static> futures::Stream for Observation<T> {
type Item = T;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
self.rx.poll_next_unpin(cx)
}
}
pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
let (tx, rx) = smol::channel::unbounded();
let _subscription = cx.update(|cx| {
cx.observe(entity, move |_, _| {
let _ = smol::block_on(tx.send(()));
})
});
Observation { rx, _subscription }
}