Bug 1759175 pt10 - Add additional documentation for the crashreporter client r=gsvelto

Differential Revision: https://phabricator.services.mozilla.com/D201993
This commit is contained in:
Alex Franchuk 2024-03-07 14:03:58 +00:00
Родитель f8b9feb941
Коммит d541a9f856
14 изменённых файлов: 236 добавлений и 4 удалений

Просмотреть файл

@ -2,6 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Data binding types are used to implement dynamic behaviors in UIs. [`Event`] is the primitive
//! type underlying most others. [Properties](Property) are what should usually be used in UI
//! models, since they have `From` impls allowing different binding behaviors to be set.
use std::cell::RefCell;
use std::rc::Rc;
@ -141,7 +145,11 @@ impl<T> Synchronized<T> {
/// A runtime value that can be fetched on-demand (read-only).
///
/// Consumers call `read` or `get` to retrieve the value.
/// Consumers call [`read`] or [`get`] to retrieve the value, while producers call [`register`] to
/// set the function which is called to retrieve the value. This is of most use for things like
/// editable text strings, where it would be unnecessarily expensive to e.g. update a
/// `Synchronized` property as the text string is changed (debouncing could be used, but if change
/// notification isn't needed then it's still unnecessary).
pub struct OnDemand<T> {
get: Rc<RefCell<Option<Box<dyn Fn(&mut T) + 'static>>>>,
}
@ -213,8 +221,8 @@ impl<T> OnDemand<T> {
/// * `T` can be converted to static bindings.
/// * `Synchronized<T>` can be converted to dynamic bindings which will be updated
/// bidirectionally.
/// * `OnDemand<T>` can be converted to dynamic bindings which can be read from the property
/// owner.
/// * `OnDemand<T>` can be converted to dynamic bindings which can be queried on an as-needed
/// basis.
#[derive(Clone, Debug)]
pub enum Property<T> {
Static(T),

Просмотреть файл

@ -2,6 +2,32 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! The crash reporter application.
//!
//! # Architecture
//! The application uses a simple declarative [UI model](ui::model) to define the UI. This model
//! contains [data bindings](data) which provide the dynamic behaviors of the UI. Separate UI
//! implementations for linux (gtk), macos (cocoa), and windows (win32) exist, as well as a test UI
//! which is virtual (no actual interface is presented) but allows runtime introspection.
//!
//! # Mocking
//! This application contains mock interfaces for all the `std` functions it uses which interact
//! with the host system. You can see their implementation in [`crate::std`]. To enable mocking,
//! use the `mock` feature or build with `MOZ_CRASHREPORTER_MOCK` set (which, in `build.rs`, is
//! translated to a `cfg` option). *Note* that this cfg _must_ be enabled when running tests.
//! Unfortunately it is not possible to detect whether tests are being built in `build.rs, which
//! is why a feature needed to be made in the first place (it is enabled automatically when running
//! `mach rusttests`).
//!
//! Currently the input program configuration which is mocked when running the application is fixed
//! (see the [`main`] implementation in this file). If needed in the future, it would be nice to
//! extend this to allow runtime tweaking.
//!
//! # Development
//! Because of the mocking support previously mentioned, in generally any `std` imports should
//! actually use `crate::std`. If mocked functions/types are missing, they should be added with
//! appropriate mocking hooks.
// Use the WINDOWS windows subsystem. This prevents a console window from opening with the
// application.
#![cfg_attr(windows, windows_subsystem = "windows")]

Просмотреть файл

@ -2,6 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Support for legacy telemetry ping creation. The ping support serialization which should be used
//! when submitting.
use anyhow::Context;
use serde::Serialize;
use std::collections::BTreeMap;

Просмотреть файл

@ -2,6 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
//! Support for crash report creation and upload.
//!
//! Upload currently uses the system libcurl or curl binary rather than a rust network stack (as
//! curl is more mature, albeit the code to interact with it must be a bit more careful).
use crate::std::{ffi::OsStr, path::Path, process::Child};
use anyhow::Context;

Просмотреть файл

@ -10,6 +10,7 @@ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
/// Mock filesystem file content.
#[derive(Debug, Default, Clone)]
pub struct MockFileContent(Arc<Mutex<Vec<u8>>>);
@ -57,10 +58,14 @@ impl From<&[u8]> for MockFileContent {
}
}
/// Mocked filesystem directory entries.
pub type MockDirEntries = HashMap<OsString, MockFSItem>;
/// The content of a mock filesystem item.
pub enum MockFSContent {
/// File content.
File(Result<MockFileContent>),
/// A directory with the given entries.
Dir(MockDirEntries),
}
@ -73,9 +78,12 @@ impl std::fmt::Debug for MockFSContent {
}
}
/// A mock filesystem item.
#[derive(Debug)]
pub struct MockFSItem {
/// The content of the item (file/dir).
pub content: MockFSContent,
/// The modification time of the item.
pub modified: SystemTime,
}
@ -88,6 +96,7 @@ impl From<MockFSContent> for MockFSItem {
}
}
/// A mock filesystem.
#[derive(Debug, Clone)]
pub struct MockFiles {
root: Arc<Mutex<MockFSItem>>,
@ -102,19 +111,27 @@ impl Default for MockFiles {
}
impl MockFiles {
/// Create a new, empty filesystem.
pub fn new() -> Self {
Self::default()
}
/// Add a mocked file with the given content. The modification time will be the unix epoch.
///
/// Pancis if the parent directory is not already mocked.
pub fn add_file<P: AsRef<Path>, C: Into<MockFileContent>>(&self, path: P, content: C) -> &Self {
self.add_file_result(path, Ok(content.into()), SystemTime::UNIX_EPOCH)
}
/// Add a mocked directory.
pub fn add_dir<P: AsRef<Path>>(&self, path: P) -> &Self {
self.path(path, true, |_| ()).unwrap();
self
}
/// Add a mocked file that returns the given result and has the given modification time.
///
/// Pancis if the parent directory is not already mocked.
pub fn add_file_result<P: AsRef<Path>>(
&self,
path: P,
@ -173,6 +190,8 @@ impl MockFiles {
Ok(f(cur_entry))
}
/// Get the mocked parent directory of the given path and call a callback on the mocked
/// directory's entries.
pub fn parent_dir<P: AsRef<Path>, F, R>(&self, path: P, f: F) -> Result<R>
where
F: FnOnce(&mut MockDirEntries) -> R,
@ -188,6 +207,7 @@ impl MockFiles {
.and_then(|r| r)
}
/// Return a file assertion helper for the mocked filesystem.
pub fn assert_files(&self) -> AssertFiles {
let mut files = HashMap::new();
let root = self.root.lock().unwrap();
@ -210,6 +230,10 @@ impl MockFiles {
}
}
/// A utility for asserting the state of the mocked filesystem.
///
/// All files must be accounted for; when dropped, a panic will occur if some files remain which
/// weren't checked.
#[derive(Debug)]
pub struct AssertFiles {
files: HashMap<PathBuf, MockFileContent>,
@ -228,6 +252,7 @@ fn remove_prefix(p: &Path) -> &Path {
}
impl AssertFiles {
/// Assert that the given path contains the given content (as a utf8 string).
pub fn check<P: AsRef<Path>, S: AsRef<str>>(&mut self, path: P, content: S) -> &mut Self {
let p = remove_prefix(path.as_ref());
let Some(mfc) = self.files.remove(p) else {
@ -243,6 +268,7 @@ impl AssertFiles {
self
}
/// Assert that the given path contains the given byte content.
pub fn check_bytes<P: AsRef<Path>, B: AsRef<[u8]>>(
&mut self,
path: P,
@ -262,11 +288,13 @@ impl AssertFiles {
self
}
/// Ignore the given file (whether it exists or not).
pub fn ignore<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self.files.remove(remove_prefix(path.as_ref()));
self
}
/// Assert that the given path exists without checking its content.
pub fn check_exists<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
let p = remove_prefix(path.as_ref());
if self.files.remove(p).is_none() {
@ -275,6 +303,11 @@ impl AssertFiles {
self
}
/// Finish checking files.
///
/// This panics if all files were not checked.
///
/// This is also called when the value is dropped.
pub fn finish(&mut self) {
let files = std::mem::take(&mut self.files);
if !files.is_empty() {

Просмотреть файл

@ -2,6 +2,24 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Mocking utilities.
//!
//! Mock data is set on a per-thread basis. [`crate::std::thread`] handles this automatically for
//! scoped threads, and warns about creating threads otherwise (which won't be able to
//! automatically share mocked data, but it can be easily done with [`SharedMockData`] when
//! appropriate).
//!
//! Mock data is stored using type erasure, with a [`MockKey`] indexing arbitrary values. Use
//! [`mock_key!`] to define keys and the values to which they map. This approach was taken as a
//! matter of covenience for programmers, and the resulting creation and consumption APIs are
//! succinct yet extensible.
//!
//! Consumers should define keys (and expose them for mockers), and at runtime create a mock key
//! instance and call [`MockKey::get`] or [`MockKey::try_get`] to retrieve mocked values to use.
//!
//! Mockers should call [`builder`] to create a builder, [`set`](Builder::set) key/value mappings,
//! and call [`run`](Builder::run) to execute code with the mock data set.
use std::any::{Any, TypeId};
use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::{Hash, Hasher};
@ -13,6 +31,7 @@ thread_local! {
static MOCK_DATA: AtomicPtr<MockDataMap> = Default::default();
}
/// A trait intended to be used as a trait object interface for mock keys.
pub trait MockKeyStored: Any + std::fmt::Debug + Sync {
fn eq(&self, other: &dyn MockKeyStored) -> bool;
fn hash(&self, state: &mut DefaultHasher);
@ -45,9 +64,12 @@ impl dyn MockKeyStored {
}
}
/// A type which can be used as a mock key.
pub trait MockKey: MockKeyStored + Sized {
/// The value to which the key maps.
type Value: Any + Send + Sync;
/// Get the value set for this key, returning `None` if no data is set.
fn try_get<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(&Self::Value) -> R,
@ -64,6 +86,9 @@ pub trait MockKey: MockKeyStored + Sized {
})
}
/// Get the value set for this key.
///
/// Panics if no mock data is set for the key.
fn get<F, R>(&self, f: F) -> R
where
F: FnOnce(&Self::Value) -> R,
@ -75,6 +100,7 @@ pub trait MockKey: MockKeyStored + Sized {
}
}
/// Mock data which can be shared amongst threads.
pub struct SharedMockData(AtomicPtr<MockDataMap>);
impl Clone for SharedMockData {
@ -84,6 +110,7 @@ impl Clone for SharedMockData {
}
impl SharedMockData {
/// Create a `SharedMockData` which stores the mock data from the current thread.
pub fn new() -> Self {
MOCK_DATA.with(|ptr| SharedMockData(AtomicPtr::new(ptr.load(Relaxed))))
}
@ -103,21 +130,25 @@ pub fn builder() -> Builder {
Builder::new()
}
/// A mock data builder.
#[derive(Default)]
pub struct Builder {
data: MockDataMap,
}
impl Builder {
/// Create a new, empty builder.
pub fn new() -> Self {
Default::default()
}
/// Set a mock data key/value mapping.
pub fn set<K: MockKey>(&mut self, key: K, value: K::Value) -> &mut Self {
self.data.insert(Box::new(key), Box::new(value));
self
}
/// Run the given function with mock data set.
pub fn run<F, R>(&mut self, f: F) -> R
where
F: FnOnce() -> R,
@ -129,6 +160,9 @@ impl Builder {
}
}
/// A general-purpose [`MockKey`] keyed by an identifier string and the stored type.
///
/// Use [`hook`] or [`try_hook`] in code accessing the values.
pub struct MockHook<T> {
name: &'static str,
_p: std::marker::PhantomData<fn() -> T>,
@ -157,6 +191,7 @@ impl<T: Any + Send + Sync + 'static> MockKey for MockHook<T> {
}
impl<T> MockHook<T> {
/// Create a new mock hook key with the given name.
pub fn new(name: &'static str) -> Self {
MockHook {
name,
@ -165,16 +200,26 @@ impl<T> MockHook<T> {
}
}
/// Create a mock hook with the given name. When mocking isn't enabled, the given value will be
/// used instead. Panics if the hook isn't set.
pub fn hook<T: Any + Send + Sync + Clone>(_normally: T, name: &'static str) -> T {
MockHook::new(name).get(|v: &T| v.clone())
}
/// Create a mock hook with the given name. When mocking isn't enabled or the hook hasn't been set,
/// the given value will be used instead.
pub fn try_hook<T: Any + Send + Sync + Clone>(fallback: T, name: &'static str) -> T {
MockHook::new(name)
.try_get(|v: &T| v.clone())
.unwrap_or(fallback)
}
/// Create a mock key with an associated value type.
///
/// Supports the following syntaxes:
/// * Unit struct: `<visibility> struct NAME => VALUE_TYPE`
/// * Tuple struct: `<visibility> struct NAME(ITEMS) => VALUE_TYPE`
/// * Normal struct: `<visibility> struct NAME { FIELDS } => VALUE_TYPE`
macro_rules! mock_key {
( $vis:vis struct $name:ident => $value:ty ) => {
$crate::std::mock::mock_key! { @structdef[$vis struct $name;] $name $value }

Просмотреть файл

@ -3,11 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#![allow(dead_code)]
//! Stubs used when mocking isn't enabled.
/// Create a mock hook with the given name. When mocking isn't enabled, the given value will be
/// used instead. Panics if the hook isn't set.
#[inline(always)]
pub fn hook<T: std::any::Any + Send + Sync + Clone>(normally: T, _name: &'static str) -> T {
normally
}
/// Create a mock hook with the given name. When mocking isn't enabled or the hook hasn't been set,
/// the given value will be used instead.
#[inline(always)]
pub fn try_hook<T: std::any::Any + Send + Sync + Clone>(fallback: T, _name: &'static str) -> T {
fallback

Просмотреть файл

@ -3,6 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Standard library wrapper (for mocking in tests).
//!
//! In general this should always be used rather than `std` directly, and _especially_ when using
//! `std` functions and types which interact with the runtime host environment.
//!
//! Note that, in some cases, this wrapper extends the `std` library. Notably, the [`mock`] module
//! adds mocking functions.
pub use std::*;

Просмотреть файл

@ -2,6 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Tests here mostly interact with the [test UI](crate::ui::test). As such, most tests read a bit
//! more like integration tests than unit tests, testing the behavior of the application as a
//! whole.
use super::*;
use crate::config::{test::MINIDUMP_PRUNE_SAVE_COUNT, Config};
use crate::settings::Settings;
@ -18,22 +22,28 @@ use crate::std::{
};
use crate::ui::{self, test::model, ui_impl::Interact};
/// A simple thread-safe counter which can be used in tests to mark that certain code paths were
/// hit.
#[derive(Clone, Default)]
struct Counter(Arc<AtomicUsize>);
impl Counter {
/// Create a new zero counter.
pub fn new() -> Self {
Self::default()
}
/// Increment the counter.
pub fn inc(&self) {
self.0.fetch_add(1, Relaxed);
}
/// Get the current count.
pub fn count(&self) -> usize {
self.0.load(Relaxed)
}
/// Assert that the current count is 1.
pub fn assert_one(&self) {
assert_eq!(self.count(), 1);
}
@ -119,6 +129,7 @@ fn current_system_time() -> ::std::time::SystemTime {
current_datetime().into()
}
/// A basic configuration which populates some necessary/useful fields.
fn test_config() -> Config {
let mut cfg = Config::default();
cfg.data_dir = Some("data_dir".into());
@ -129,13 +140,19 @@ fn test_config() -> Config {
cfg
}
/// A test fixture to make configuration, mocking, and assertions easier.
struct GuiTest {
/// The configuration used in the test. Initialized to [`test_config`].
pub config: Config,
/// The mock builder used in the test, initialized with a basic set of mocked values to ensure
/// most things will work out of the box.
pub mock: mock::Builder,
/// The mocked filesystem, which can be used for mock setup and assertions after completion.
pub files: MockFiles,
}
impl GuiTest {
/// Create a new GuiTest with enough configured for the application to run
pub fn new() -> Self {
// Create a default set of files which allow successful operation.
let mock_files = MockFiles::new();
@ -184,6 +201,9 @@ impl GuiTest {
}
}
/// Run the test as configured, using the given function to interact with the GUI.
///
/// Returns the final result of the application logic.
pub fn try_run<F: FnOnce(Interact) + Send + 'static>(
&mut self,
interact: F,
@ -199,6 +219,10 @@ impl GuiTest {
mock.run(move || gui_interact(move || try_run(&mut config), interact))
}
/// Run the test as configured, using the given function to interact with the GUI.
///
/// Panics if the application logic returns an error (which would normally be displayed to the
/// user).
pub fn run<F: FnOnce(Interact) + Send + 'static>(&mut self, interact: F) {
if let Err(e) = self.try_run(interact) {
panic!(
@ -208,6 +232,7 @@ impl GuiTest {
}
}
/// Get the file assertion helper.
pub fn assert_files(&self) -> AssertFiles {
AssertFiles {
data_dir: "data_dir".into(),
@ -217,6 +242,11 @@ impl GuiTest {
}
}
/// A wrapper around the mock [`AssertFiles`](crate::std::fs::AssertFiles).
///
/// This implements higher-level assertions common across tests, but also supports the lower-level
/// assertions (though those return the [`AssertFiles`](crate::std::fs::AssertFiles) reference so
/// higher-level assertions must be chained first).
struct AssertFiles {
data_dir: String,
events_dir: String,
@ -232,6 +262,7 @@ impl AssertFiles {
format!("{}/{rest}", &self.events_dir)
}
/// Set the data dir if not the default.
pub fn set_data_dir<S: ToString>(&mut self, data_dir: S) -> &mut Self {
let data_dir = data_dir.to_string();
// Data dir should be relative to root.
@ -239,11 +270,13 @@ impl AssertFiles {
self
}
/// Ignore the generated log file.
pub fn ignore_log(&mut self) -> &mut Self {
self.inner.ignore(self.data("submit.log"));
self
}
/// Assert that the crash report was submitted according to the filesystem.
pub fn submitted(&mut self) -> &mut Self {
self.inner.check(
self.data(&format!("submitted/{MOCK_REMOTE_CRASH_ID}.txt")),
@ -252,6 +285,7 @@ impl AssertFiles {
self
}
/// Assert that the given settings where saved.
pub fn saved_settings(&mut self, settings: Settings) -> &mut Self {
self.inner.check(
self.data("crashreporter_settings.json"),
@ -260,6 +294,7 @@ impl AssertFiles {
self
}
/// Assert that a crash is pending according to the filesystem.
pub fn pending(&mut self) -> &mut Self {
let dmp = self.data("pending/minidump.dmp");
self.inner
@ -268,6 +303,7 @@ impl AssertFiles {
self
}
/// Assert that a crash ping was sent according to the filesystem.
pub fn ping(&mut self) -> &mut Self {
self.inner.check(
format!("ping_dir/{MOCK_PING_UUID}.json"),
@ -310,6 +346,7 @@ impl AssertFiles {
self
}
/// Assert that a crash submission event was written with the given submission status.
pub fn submission_event(&mut self, success: bool) -> &mut Self {
self.inner.check(
self.events("minidump-submission"),
@ -1039,11 +1076,20 @@ fn response_stop_sending_reports() {
.check_exists("data_dir/EndOfLife100.0");
}
/// A real temporary directory in the host filesystem.
///
/// The directory is guaranteed to be unique to the test suite process (in case of crash, it can be
/// inspected).
///
/// When dropped, the directory is deleted.
struct TempDir {
path: ::std::path::PathBuf,
}
impl TempDir {
/// Create a new directory with the given identifying name.
///
/// The name should be unique to deconflict amongst concurrent tests.
pub fn new(name: &str) -> Self {
let path = ::std::env::temp_dir().join(format!(
"{}-test-{}-{name}",
@ -1054,6 +1100,7 @@ impl TempDir {
TempDir { path }
}
/// Get the temporary directory path.
pub fn path(&self) -> &::std::path::Path {
&self.path
}
@ -1066,6 +1113,9 @@ impl Drop for TempDir {
}
}
/// A mock crash report server.
///
/// When dropped, the server is shutdown.
struct TestCrashReportServer {
addr: ::std::net::SocketAddr,
shutdown_and_thread: Option<(
@ -1075,6 +1125,8 @@ struct TestCrashReportServer {
}
impl TestCrashReportServer {
/// Create and start a mock crash report server on an ephemeral port, returning a handle to the
/// server.
pub fn run() -> Self {
let (shutdown, rx) = tokio::sync::oneshot::channel();
@ -1130,6 +1182,7 @@ impl TestCrashReportServer {
}
}
/// Get the url to which to submit crash reports for this mocked server.
pub fn submit_url(&self) -> String {
format!("http://{}/submit", self.addr)
}

Просмотреть файл

@ -5,6 +5,7 @@
use super::{Element, ElementBuilder};
use crate::data::Event;
/// A clickable button.
#[derive(Default, Debug)]
pub struct Button {
pub content: Option<Box<Element>>,

Просмотреть файл

@ -4,6 +4,7 @@
use crate::data::Property;
/// A checkbox (with optional label).
#[derive(Default, Debug)]
pub struct Checkbox {
pub checked: Property<bool>,

Просмотреть файл

@ -3,6 +3,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! The UI model.
//!
//! Model elements should generally be declared as types with all fields `pub` (to be accessed by
//! UI implementations), though accessor methods are acceptable if needed. An
//! `ElementBuilder<TYPE>` impl should be provided to create methods that will be used in the
//! [`ui!`] macro. The model types are accessible when being _consumed_ by a UI implementation,
//! whereas the `ElementBuilder` types are accessible when the model is being _created_.
//!
//! All elements should be listed in the `element_types!` macro in this file (note that [`Window`],
//! while an element, isn't listed here as it cannot be a child element). This populates the
//! `ElementType` enum and generates `From<Element>` for `ElementType`, and `TryFrom<ElementType>`
//! for the element (as well as reference `TryFrom`).
//!
//! The model is written to accommodate layout and text direction differences (e.g. for RTL
//! languages), and UI implementations are expected to account for this correctly.
use crate::data::Property;
pub use button::Button;
@ -26,6 +40,8 @@ mod vbox;
mod window;
/// A GUI element, including general style attributes and a more specific type.
///
/// `From<ElementBuilder<...>>` is implemented for all elements listed in `element_types!`.
#[derive(Debug)]
pub struct Element {
pub style: ElementStyle,
@ -119,6 +135,8 @@ impl Default for ElementStyle {
}
/// A builder for `Element`s.
///
/// Each element should add an `impl ElementBuilder<TYPE>` to add methods to their builder.
#[derive(Debug, Default)]
pub struct ElementBuilder<T> {
pub style: ElementStyle,
@ -215,7 +233,11 @@ impl<T> ElementBuilder<T> {
}
}
/// A typed `Element`.
/// A typed [`Element`].
///
/// This is useful for the [`ui!`] macro when a method should accept a specific element type, since
/// the macro always creates [`ElementBuilder<T>`](ElementBuilder) and ends with a `.into()` (and this implements
/// `From<ElementBuilder<T>>`).
#[derive(Debug, Default)]
pub struct TypedElement<T> {
pub style: ElementStyle,
@ -232,6 +254,9 @@ impl<T> From<ElementBuilder<T>> for TypedElement<T> {
}
/// The alignment of an element in one direction.
///
/// Note that rather than `Left`/`Right`, this class has `Start`/`End` as it is meant to be
/// layout-direction-aware.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
pub enum Alignment {
@ -272,6 +297,9 @@ pub struct Margin {
/// mutable reference. This means that element types must implement Default and must implement
/// builder methods on `ElementBuilder<ElementTypeName>`. The children block is optional, and calls
/// `add_child(child: Element)` for each provided child (so implement this method if desired).
///
/// For testing, a string identifier can be set on any element with a `["my_identifier"]` following
/// the element type.
macro_rules! ui {
( $el:ident
$([ $id:literal ])?

Просмотреть файл

@ -4,6 +4,14 @@
//! A renderer for use in tests, which doesn't actually render a GUI but allows programmatic
//! interaction.
//!
//! The [`ui!`](super::ui) macro supports labeling any element with a string identifier, which can
//! be used to access the element in this UI.
//!
//! The [`Interact`] hook must be created to interact with the test UI, before the UI is run and on
//! the same thread as the UI.
//!
//! See how this UI is used in [`crate::test`].
use super::model::{self, Application, Element};
use std::cell::RefCell;

Просмотреть файл

@ -258,6 +258,15 @@ Environment variables used internally
- ``MOZ_CRASHREPORTER_STRINGS_OVERRIDE`` - Overrides the path used to load the
.ini file holding the strings used in the crash reporter client UI.
Environment variables used for development
------------------------------------------
Set these at build time (e.g. ``ac_add_options`` in ``.mozconfig``).
- ``MOZ_CRASHREPORTER_MOCK`` - When set, causes the crash reporter client to
mock its interfaces to the system so that you can test the GUI behavior. The
GUI will not interact with the host system at all when this is set.
Other topics
============