StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use crate::Event; |
| 2 | use instant::Instant; |
| 3 | use std::{io::Write, path::Path, time::Duration}; |
| 4 | |
| 5 | /// Well-known key used to store the `ActionLog` inside `StepDataMap`. |
| 6 | pub const ACTION_LOG_KEY: &str = "action_log"; |
| 7 | |
| 8 | /// A single event recorded in the action log. |
| 9 | pub struct ActionEntry { |
| 10 | /// Wall-clock instant this entry was recorded. |
| 11 | recorded_at: Instant, |
| 12 | /// Human-readable description of the event. |
| 13 | description: String, |
| 14 | } |
| 15 | |
| 16 | /// Returns a concise, human-readable description of an event for the action log. |
| 17 | pub fn event_description(event: &Event) -> String { |
| 18 | match event { |
| 19 | Event::KeyDown { chars, .. } => format!("KeyDown '{chars}'"), |
| 20 | Event::TypedCharacters { chars } => format!("TypedCharacters '{chars}'"), |
| 21 | Event::LeftMouseDown { .. } => "LeftMouseDown".to_string(), |
| 22 | Event::LeftMouseUp { .. } => "LeftMouseUp".to_string(), |
| 23 | Event::LeftMouseDragged { .. } => "LeftMouseDragged".to_string(), |
| 24 | Event::RightMouseDown { .. } => "RightMouseDown".to_string(), |
| 25 | Event::MiddleMouseDown { .. } => "MiddleMouseDown".to_string(), |
| 26 | Event::MouseMoved { .. } => "MouseMoved".to_string(), |
| 27 | Event::ScrollWheel { .. } => "ScrollWheel".to_string(), |
| 28 | Event::ModifierStateChanged { .. } => "ModifierStateChanged".to_string(), |
| 29 | Event::ModifierKeyChanged { .. } => "ModifierKeyChanged".to_string(), |
| 30 | Event::DragAndDropFiles { .. } => "DragAndDropFiles".to_string(), |
| 31 | Event::DragFiles { .. } => "DragFiles".to_string(), |
| 32 | Event::DragFileExit => "DragFileExit".to_string(), |
| 33 | Event::SetMarkedText { .. } => "SetMarkedText".to_string(), |
| 34 | Event::ClearMarkedText => "ClearMarkedText".to_string(), |
| 35 | Event::ForwardMouseDown { .. } => "ForwardMouseDown".to_string(), |
| 36 | Event::BackMouseDown { .. } => "BackMouseDown".to_string(), |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | /// Accumulates timestamped test events during an integration test run. |
| 41 | /// |
| 42 | /// When recording is active, `write_to_file` renders each entry with its |
| 43 | /// offset into the recording (e.g. `[+00:03.142]`). If recording was never |
| 44 | /// started the offset is computed relative to the first entry instead so |
| 45 | /// the log is still useful. |
| 46 | #[derive(Default)] |
| 47 | pub struct ActionLog { |
| 48 | entries: Vec<ActionEntry>, |
| 49 | recording_start: Option<Instant>, |
| 50 | } |
| 51 | |
| 52 | impl ActionLog { |
| 53 | pub fn new() -> Self { |
| 54 | Self::default() |
| 55 | } |
| 56 | |
| 57 | /// Marks the instant at which recording started. All log entries |
| 58 | /// will be displayed with offsets relative to this instant. |
| 59 | pub fn set_recording_start(&mut self, start: Instant) { |
| 60 | self.recording_start = Some(start); |
| 61 | } |
| 62 | |
| 63 | /// Appends an entry with the current wall-clock time. |
| 64 | pub fn record(&mut self, description: impl Into<String>) { |
| 65 | self.entries.push(ActionEntry { |
| 66 | recorded_at: Instant::now(), |
| 67 | description: description.into(), |
| 68 | }); |
| 69 | } |
| 70 | |
| 71 | /// Writes the action log to a plain-text file. |
| 72 | /// |
| 73 | /// Each line has the form: |
| 74 | /// ```text |
| 75 | /// [+MM:SS.mmm] description |
| 76 | /// ``` |
| 77 | /// The offset is relative to `recording_start` (or to the first entry's |
| 78 | /// timestamp if recording was never explicitly started). |
| 79 | pub fn write_to_file(&self, path: &Path) -> anyhow::Result<()> { |
| 80 | if self.entries.is_empty() { |
| 81 | return Ok(()); |
| 82 | } |
| 83 | |
| 84 | if let Some(parent) = path.parent() { |
| 85 | std::fs::create_dir_all(parent)?; |
| 86 | } |
| 87 | |
| 88 | let base = self.recording_start.unwrap_or(self.entries[0].recorded_at); |
| 89 | |
| 90 | let file = std::fs::File::create(path)?; |
| 91 | let mut writer = std::io::BufWriter::new(file); |
| 92 | |
| 93 | for entry in &self.entries { |
| 94 | let offset = entry |
| 95 | .recorded_at |
| 96 | .checked_duration_since(base) |
| 97 | .unwrap_or(Duration::ZERO); |
| 98 | let total_secs = offset.as_secs(); |
| 99 | let minutes = total_secs / 60; |
| 100 | let seconds = total_secs % 60; |
| 101 | let millis = offset.subsec_millis(); |
| 102 | writeln!( |
| 103 | writer, |
| 104 | "[+{minutes:02}:{seconds:02}.{millis:03}] {}", |
| 105 | entry.description |
| 106 | )?; |
| 107 | } |
| 108 | |
| 109 | log::info!( |
| 110 | "ActionLog: wrote {} entries to {}", |
| 111 | self.entries.len(), |
| 112 | path.display() |
| 113 | ); |
| 114 | Ok(()) |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | /// Helper to retrieve a mutable reference to the log from a `StepDataMap`. |
| 119 | pub fn get_action_log_mut(step_data_map: &mut super::step::StepDataMap) -> Option<&mut ActionLog> { |
| 120 | step_data_map.get_mut::<_, ActionLog>(ACTION_LOG_KEY) |
| 121 | } |
| 122 | |
| 123 | /// Helper to retrieve a shared reference to the log from a `StepDataMap`. |
| 124 | pub fn get_action_log(step_data_map: &super::step::StepDataMap) -> Option<&ActionLog> { |
| 125 | step_data_map.get::<_, ActionLog>(ACTION_LOG_KEY) |
| 126 | } |
| 127 |