Skip to content

Commit

Permalink
Add support for (some) SystemCommand to TestContest (rerun-io#7611)
Browse files Browse the repository at this point in the history
### What

This PR adds best-effort support for some `SystemCommand`s in
`TestContext`, the mocking test tool for `ViewerContext`. This primarily
enable writes to the blueprint store, which enables testing e.g. wrapper
over `ViewProperties`. This PR includes a simple example of that.

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7611?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7611?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!
* [x] If have noted any breaking changes to the log API in
`CHANGELOG.md` and the migration guide

- [PR Build Summary](https://build.rerun.io/pr/7611)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.

---------

Co-authored-by: Andreas Reich <[email protected]>
  • Loading branch information
abey79 and Wumpf authored Oct 7, 2024
1 parent 2945950 commit d3f637f
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 7 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6084,6 +6084,7 @@ dependencies = [
"serde",
"slotmap",
"smallvec",
"strum_macros",
"thiserror",
"uuid",
"wasm-bindgen-futures",
Expand Down
25 changes: 25 additions & 0 deletions crates/viewer/re_space_view_dataframe/src/view_query/blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,28 @@ impl Query {
Ok(())
}
}

#[cfg(test)]
mod test {
use super::Query;
use re_viewer_context::test_context::TestContext;
use re_viewer_context::SpaceViewId;

/// Simple test to demo round-trip testing using [`TestContext::run_and_handle_system_commands`].
#[test]
fn test_latest_at_enabled() {
let mut test_context = TestContext::default();

let view_id = SpaceViewId::random();

test_context.run_and_handle_system_commands(|ctx, _| {
let query = Query::from_blueprint(ctx, view_id);
query.save_latest_at_enabled(ctx, true);
});

test_context.run(|ctx, _| {
let query = Query::from_blueprint(ctx, view_id);
assert!(query.latest_at_enabled().unwrap());
});
}
}
1 change: 1 addition & 0 deletions crates/viewer/re_viewer_context/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ rfd.workspace = true
serde.workspace = true
slotmap.workspace = true
smallvec.workspace = true
strum_macros.workspace = true
thiserror.workspace = true
uuid = { workspace = true, features = ["serde", "v4", "js"] }
wgpu.workspace = true
Expand Down
8 changes: 8 additions & 0 deletions crates/viewer/re_viewer_context/src/command_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use re_ui::{UICommand, UICommandSender};

/// Commands used by internal system components
// TODO(jleibs): Is there a better crate for this?
#[derive(strum_macros::IntoStaticStr)]
pub enum SystemCommand {
/// Make this the active application.
ActivateApp(re_log_types::ApplicationId),
Expand Down Expand Up @@ -86,6 +87,13 @@ pub enum SystemCommand {
FileSaver(Box<dyn FnOnce() -> anyhow::Result<std::path::PathBuf> + Send + 'static>),
}

impl std::fmt::Debug for SystemCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// not all variant contents can be made `Debug`, so we only output the variant name
f.write_str(self.into())
}
}

/// Interface for sending [`SystemCommand`] messages.
pub trait SystemCommandSender {
fn send_system(&self, command: SystemCommand);
Expand Down
93 changes: 86 additions & 7 deletions crates/viewer/re_viewer_context/src/test_context.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::{
command_channel, ApplicationSelectionState, ComponentUiRegistry, RecordingConfig,
SpaceViewClassRegistry, StoreContext, ViewerContext,
};
use std::sync::Arc;

use re_chunk_store::LatestAtQuery;
use re_entity_db::EntityDb;
use re_log_types::{StoreId, StoreKind, Timeline};

use crate::{
blueprint_timeline, command_channel, ApplicationSelectionState, CommandReceiver, CommandSender,
ComponentUiRegistry, RecordingConfig, SpaceViewClassRegistry, StoreContext, SystemCommand,
ViewerContext,
};

/// Harness to execute code that rely on [`crate::ViewerContext`].
///
/// Example:
Expand All @@ -25,19 +28,26 @@ pub struct TestContext {
pub space_view_class_registry: SpaceViewClassRegistry,
pub selection_state: ApplicationSelectionState,
pub active_timeline: Timeline,

command_sender: CommandSender,
command_receiver: CommandReceiver,
}

impl Default for TestContext {
fn default() -> Self {
let recording_store = EntityDb::new(StoreId::random(StoreKind::Recording));
let blueprint_store = EntityDb::new(StoreId::random(StoreKind::Blueprint));
let active_timeline = Timeline::new("time", re_log_types::TimeType::Time);

let (command_sender, command_receiver) = command_channel();
Self {
recording_store,
blueprint_store,
space_view_class_registry: Default::default(),
selection_state: Default::default(),
active_timeline,
command_sender,
command_receiver,
}
}
}
Expand All @@ -50,12 +60,16 @@ impl TestContext {
self.selection_state.on_frame_start(|_| true, None);
}

/// Run the given function with a [`ViewerContext`] produced by the [`Self`].
///
/// Note: there is a possibility that the closure will be called more than once, see
/// [`egui::Context::run`].
pub fn run(&self, mut func: impl FnMut(&ViewerContext<'_>, &mut egui::Ui)) {
egui::__run_test_ctx(|ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
re_ui::apply_style_and_install_loaders(ui.ctx());
let blueprint_query = LatestAtQuery::latest(self.active_timeline);
let (command_sender, _) = command_channel();
let blueprint_query = LatestAtQuery::latest(blueprint_timeline());

let component_ui_registry = ComponentUiRegistry::new(Box::new(
|_ctx, _ui, _ui_layout, _query, _db, _entity_path, _row_id, _component| {},
));
Expand Down Expand Up @@ -90,14 +104,79 @@ impl TestContext {
blueprint_query: &blueprint_query,
egui_ctx: &egui_context,
render_ctx: None,
command_sender: &command_sender,
command_sender: &self.command_sender,
focused_item: &None,
};

func(&ctx, ui);
});
});
}

/// Run the given function with a [`ViewerContext`] produced by the [`Self`] and handle any
/// system commands issued during execution (see [`Self::handle_system_command`]).
pub fn run_and_handle_system_commands(
&mut self,
func: impl FnMut(&ViewerContext<'_>, &mut egui::Ui),
) {
self.run(func);
self.handle_system_command();
}

/// Best-effort attempt to meaningfully handle some of the system commands.
pub fn handle_system_command(&mut self) {
while let Some(command) = self.command_receiver.recv_system() {
let mut handled = true;
let command_name = format!("{command:?}");
match command {
SystemCommand::UpdateBlueprint(store_id, chunks) => {
assert_eq!(&store_id, self.blueprint_store.store_id());

for chunk in chunks {
self.blueprint_store
.add_chunk(&Arc::new(chunk))
.expect("Updating the blueprint chunk store failed");
}
}

SystemCommand::DropEntity(store_id, entity_path) => {
assert_eq!(&store_id, self.blueprint_store.store_id());
self.blueprint_store
.drop_entity_path_recursive(&entity_path);
}

SystemCommand::SetSelection(item) => {
self.selection_state.set_selection(item);
}

// not implemented
SystemCommand::SetFocus(_)
| SystemCommand::ActivateApp(_)
| SystemCommand::CloseApp(_)
| SystemCommand::LoadDataSource(_)
| SystemCommand::ClearSourceAndItsStores(_)
| SystemCommand::AddReceiver(_)
| SystemCommand::ResetViewer
| SystemCommand::ClearActiveBlueprint
| SystemCommand::ClearAndGenerateBlueprint
| SystemCommand::ActivateRecording(_)
| SystemCommand::CloseStore(_)
| SystemCommand::CloseAllRecordings
| SystemCommand::EnableExperimentalDataframeSpaceView(_) => handled = false,

#[cfg(debug_assertions)]
SystemCommand::EnableInspectBlueprintTimeline(_) => handled = false,

#[cfg(not(target_arch = "wasm32"))]
SystemCommand::FileSaver(_) => handled = false,
}

eprintln!(
"{} system command: {command_name:?}",
if handled { "Handled" } else { "Ignored" }
);
}
}
}

#[cfg(test)]
Expand Down

0 comments on commit d3f637f

Please sign in to comment.