summaryrefslogtreecommitdiffstats
path: root/third_party/rust/glean/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/glean/tests
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/glean/tests')
-rw-r--r--third_party/rust/glean/tests/common/mod.rs51
-rw-r--r--third_party/rust/glean/tests/init_fails.rs77
-rw-r--r--third_party/rust/glean/tests/never_init.rs66
-rw-r--r--third_party/rust/glean/tests/no_time_to_init.rs74
-rw-r--r--third_party/rust/glean/tests/overflowing_preinit.rs88
-rw-r--r--third_party/rust/glean/tests/persist_ping_lifetime.rs89
-rw-r--r--third_party/rust/glean/tests/persist_ping_lifetime_nopanic.rs37
-rw-r--r--third_party/rust/glean/tests/schema.rs211
-rw-r--r--third_party/rust/glean/tests/simple.rs77
-rwxr-xr-xthird_party/rust/glean/tests/test-shutdown-blocking.sh29
-rw-r--r--third_party/rust/glean/tests/upload_timing.rs225
11 files changed, 1024 insertions, 0 deletions
diff --git a/third_party/rust/glean/tests/common/mod.rs b/third_party/rust/glean/tests/common/mod.rs
new file mode 100644
index 0000000000..cc02946d2c
--- /dev/null
+++ b/third_party/rust/glean/tests/common/mod.rs
@@ -0,0 +1,51 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+// #[allow(dead_code)] is required on this module as a workaround for
+// https://github.com/rust-lang/rust/issues/46379
+#![allow(dead_code)]
+
+use std::{panic, process};
+
+use glean::{ClientInfoMetrics, Configuration};
+
+/// Initialize the env logger for a test environment.
+///
+/// When testing we want all logs to go to stdout/stderr by default.
+pub fn enable_test_logging() {
+ let _ = env_logger::builder().is_test(true).try_init();
+}
+
+/// Install a panic handler that exits the whole process when a panic occurs.
+///
+/// This causes the process to exit even if a thread panics.
+/// This is similar to the `panic=abort` configuration, but works in the default configuration
+/// (as used by `cargo test`).
+fn install_panic_handler() {
+ let orig_hook = panic::take_hook();
+ panic::set_hook(Box::new(move |panic_info| {
+ // invoke the default handler and exit the process
+ orig_hook(panic_info);
+ process::exit(1);
+ }));
+}
+
+/// Create a new instance of Glean.
+pub fn initialize(cfg: Configuration) {
+ // Ensure panics in threads, such as the init thread or the dispatcher, cause the process to
+ // exit.
+ //
+ // Otherwise in case of a panic in a thread the integration test will just hang.
+ // CI will terminate it after a timeout, but why stick around if we know nothing is happening?
+ install_panic_handler();
+
+ // Use some default values to make our life easier a bit.
+ let client_info = ClientInfoMetrics {
+ app_build: "1.0.0".to_string(),
+ app_display_version: "1.0.0".to_string(),
+ channel: Some("testing".to_string()),
+ };
+
+ glean::initialize(cfg, client_info);
+}
diff --git a/third_party/rust/glean/tests/init_fails.rs b/third_party/rust/glean/tests/init_fails.rs
new file mode 100644
index 0000000000..2269da89ff
--- /dev/null
+++ b/third_party/rust/glean/tests/init_fails.rs
@@ -0,0 +1,77 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+use std::{thread, time::Duration};
+
+use glean::ConfigurationBuilder;
+
+/// Some user metrics.
+mod metrics {
+ use glean::private::*;
+ use glean::{Lifetime, TimeUnit};
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static initialization: Lazy<TimespanMetric> = Lazy::new(|| {
+ TimespanMetric::new(
+ CommonMetricData {
+ name: "initialization".into(),
+ category: "sample".into(),
+ send_in_pings: vec!["validation".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ },
+ TimeUnit::Nanosecond,
+ )
+ });
+}
+
+mod pings {
+ use glean::private::PingType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static validation: Lazy<PingType> =
+ Lazy::new(|| glean::private::PingType::new("validation", true, true, vec![]));
+}
+
+/// Test scenario: Glean initialization fails.
+///
+/// App tries to initialize Glean, but that somehow fails.
+#[test]
+fn init_fails() {
+ common::enable_test_logging();
+
+ metrics::initialization.start();
+
+ // Create a custom configuration to use a validating uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+
+ let cfg = ConfigurationBuilder::new(true, tmpname, "")
+ .with_server_endpoint("invalid-test-host")
+ .build();
+ common::initialize(cfg);
+
+ metrics::initialization.stop();
+
+ pings::validation.submit(None);
+
+ // We don't test for data here, as that would block on the dispatcher.
+
+ // Give it a short amount of time to actually finish initialization.
+ thread::sleep(Duration::from_millis(500));
+
+ glean::shutdown();
+}
diff --git a/third_party/rust/glean/tests/never_init.rs b/third_party/rust/glean/tests/never_init.rs
new file mode 100644
index 0000000000..321662b327
--- /dev/null
+++ b/third_party/rust/glean/tests/never_init.rs
@@ -0,0 +1,66 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+/// Some user metrics.
+mod metrics {
+ use glean::private::*;
+ use glean::{Lifetime, TimeUnit};
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static initialization: Lazy<TimespanMetric> = Lazy::new(|| {
+ TimespanMetric::new(
+ CommonMetricData {
+ name: "initialization".into(),
+ category: "sample".into(),
+ send_in_pings: vec!["validation".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ },
+ TimeUnit::Nanosecond,
+ )
+ });
+}
+
+mod pings {
+ use glean::private::PingType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static validation: Lazy<PingType> =
+ Lazy::new(|| glean::private::PingType::new("validation", true, true, vec![]));
+}
+
+/// Test scenario: Glean is never initialized.
+///
+/// Glean is never initialized.
+/// Some data is recorded early on.
+/// And later the whole process is shutdown.
+#[test]
+fn never_initialize() {
+ common::enable_test_logging();
+
+ metrics::initialization.start();
+
+ // NOT calling `initialize` here.
+ // In apps this might happen for several reasons:
+ // 1. Process doesn't run long enough for Glean to be initialized.
+ // 2. Getting some early data used for initialize fails
+
+ pings::validation.submit(None);
+
+ // We can't test for data either, as that would panic because init was never called.
+
+ glean::shutdown();
+}
diff --git a/third_party/rust/glean/tests/no_time_to_init.rs b/third_party/rust/glean/tests/no_time_to_init.rs
new file mode 100644
index 0000000000..7d51e514d6
--- /dev/null
+++ b/third_party/rust/glean/tests/no_time_to_init.rs
@@ -0,0 +1,74 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+use glean::ConfigurationBuilder;
+
+/// Some user metrics.
+mod metrics {
+ use glean::private::*;
+ use glean::{Lifetime, TimeUnit};
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static initialization: Lazy<TimespanMetric> = Lazy::new(|| {
+ TimespanMetric::new(
+ CommonMetricData {
+ name: "initialization".into(),
+ category: "sample".into(),
+ send_in_pings: vec!["validation".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ },
+ TimeUnit::Nanosecond,
+ )
+ });
+}
+
+mod pings {
+ use glean::private::PingType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static validation: Lazy<PingType> =
+ Lazy::new(|| glean::private::PingType::new("validation", true, true, vec![]));
+}
+
+/// Test scenario: Glean initialization fails.
+///
+/// The app tries to initializate Glean, but that somehow fails.
+#[test]
+fn init_fails() {
+ common::enable_test_logging();
+
+ metrics::initialization.start();
+
+ // Create a custom configuration to use a validating uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+
+ let cfg = ConfigurationBuilder::new(true, tmpname, "firefox-desktop")
+ .with_server_endpoint("invalid-test-host")
+ .build();
+ common::initialize(cfg);
+
+ metrics::initialization.stop();
+
+ pings::validation.submit(None);
+
+ // We don't test for data here, as that would block on the dispatcher.
+
+ // Shut it down immediately; this might not be enough time to initialize.
+
+ glean::shutdown();
+}
diff --git a/third_party/rust/glean/tests/overflowing_preinit.rs b/third_party/rust/glean/tests/overflowing_preinit.rs
new file mode 100644
index 0000000000..6d4ec7f6ae
--- /dev/null
+++ b/third_party/rust/glean/tests/overflowing_preinit.rs
@@ -0,0 +1,88 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+use glean::ConfigurationBuilder;
+
+/// Some user metrics.
+mod metrics {
+ use glean::private::*;
+ use glean::Lifetime;
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static rapid_counting: Lazy<CounterMetric> = Lazy::new(|| {
+ CounterMetric::new(CommonMetricData {
+ name: "rapid_counting".into(),
+ category: "sample".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ // This is a hack, but a good one:
+ //
+ // To avoid reaching into RLB internals we re-create the metric so we can look at it.
+ #[allow(non_upper_case_globals)]
+ pub static preinit_tasks_overflow: Lazy<CounterMetric> = Lazy::new(|| {
+ CounterMetric::new(CommonMetricData {
+ category: "glean.error".into(),
+ name: "preinit_tasks_overflow".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+}
+
+/// Test scenario: Lots of metric recordings before init.
+///
+/// The app starts recording metrics before Glean is initialized.
+/// Once initialized the recordings are processed and data is persisted.
+/// The pre-init dispatcher queue records how many recordings over the limit it saw.
+///
+/// This is an integration test to avoid dealing with resetting the dispatcher.
+#[test]
+fn overflowing_the_task_queue_records_telemetry() {
+ common::enable_test_logging();
+
+ // Create a custom configuration to use a validating uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+
+ let cfg = ConfigurationBuilder::new(true, tmpname, "firefox-desktop")
+ .with_server_endpoint("invalid-test-host")
+ .build();
+
+ // Insert a bunch of tasks to overflow the queue.
+ for _ in 0..1010 {
+ metrics::rapid_counting.add(1);
+ }
+
+ // Now initialize Glean
+ common::initialize(cfg);
+
+ assert_eq!(Some(1000), metrics::rapid_counting.test_get_value(None));
+
+ // The metrics counts the total number of overflowing tasks,
+ // (and the count of tasks in the queue when we overflowed: bug 1764573)
+ // this might include Glean-internal tasks.
+ let val = metrics::preinit_tasks_overflow
+ .test_get_value(None)
+ .unwrap();
+ assert!(val >= 10);
+
+ glean::shutdown();
+}
diff --git a/third_party/rust/glean/tests/persist_ping_lifetime.rs b/third_party/rust/glean/tests/persist_ping_lifetime.rs
new file mode 100644
index 0000000000..f73673f46f
--- /dev/null
+++ b/third_party/rust/glean/tests/persist_ping_lifetime.rs
@@ -0,0 +1,89 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+use glean::{ClientInfoMetrics, Configuration, ConfigurationBuilder};
+use std::path::PathBuf;
+
+/// Some user metrics.
+mod metrics {
+ use glean::private::*;
+ use glean::Lifetime;
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static boo: Lazy<BooleanMetric> = Lazy::new(|| {
+ BooleanMetric::new(CommonMetricData {
+ name: "boo".into(),
+ category: "sample".into(),
+ send_in_pings: vec!["validation".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+}
+
+fn cfg_new(tmpname: PathBuf) -> Configuration {
+ ConfigurationBuilder::new(true, tmpname, "firefox-desktop")
+ .with_server_endpoint("invalid-test-host")
+ .with_delay_ping_lifetime_io(true)
+ .build()
+}
+
+/// Test scenario: Are ping-lifetime data persisted on shutdown when delayed?
+///
+/// delay_ping_lifetime_io: true has Glean put "ping"-lifetime data in-memory
+/// instead of the db. Ensure that, on orderly shutdowns, we correctly persist
+/// these in-memory data to the db.
+#[test]
+fn delayed_ping_data() {
+ common::enable_test_logging();
+
+ metrics::boo.set(true);
+
+ // Create a custom configuration to delay ping-lifetime io
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+
+ common::initialize(cfg_new(tmpname.clone()));
+
+ assert!(
+ metrics::boo.test_get_value(None).unwrap(),
+ "Data should be present. Doesn't mean it's persisted, though."
+ );
+
+ glean::test_reset_glean(
+ cfg_new(tmpname.clone()),
+ ClientInfoMetrics::unknown(),
+ false,
+ );
+
+ assert_eq!(
+ None,
+ metrics::boo.test_get_value(None),
+ "Data should not have made it to disk on unclean shutdown."
+ );
+ metrics::boo.set(true); // Let's try again
+
+ // This time, let's shut down cleanly
+ glean::shutdown();
+
+ // Now when we init, we should get the persisted data
+ glean::test_reset_glean(cfg_new(tmpname), ClientInfoMetrics::unknown(), false);
+ assert!(
+ metrics::boo.test_get_value(None).unwrap(),
+ "Data must be persisted between clean shutdown and init!"
+ );
+
+ glean::shutdown(); // Cleanly shut down at the end of the test.
+}
diff --git a/third_party/rust/glean/tests/persist_ping_lifetime_nopanic.rs b/third_party/rust/glean/tests/persist_ping_lifetime_nopanic.rs
new file mode 100644
index 0000000000..18d54e9033
--- /dev/null
+++ b/third_party/rust/glean/tests/persist_ping_lifetime_nopanic.rs
@@ -0,0 +1,37 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+use glean::{Configuration, ConfigurationBuilder};
+use std::path::PathBuf;
+
+fn cfg_new(tmpname: PathBuf) -> Configuration {
+ ConfigurationBuilder::new(true, tmpname, "firefox-desktop")
+ .with_server_endpoint("invalid-test-host")
+ .with_delay_ping_lifetime_io(true)
+ .build()
+}
+
+/// Test scenario: `persist_ping_lifetime_data` called after shutdown.
+#[test]
+fn delayed_ping_data() {
+ common::enable_test_logging();
+
+ // Create a custom configuration to delay ping-lifetime io
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+
+ common::initialize(cfg_new(tmpname));
+ glean::persist_ping_lifetime_data();
+
+ glean::shutdown();
+ glean::persist_ping_lifetime_data();
+}
diff --git a/third_party/rust/glean/tests/schema.rs b/third_party/rust/glean/tests/schema.rs
new file mode 100644
index 0000000000..bdcfb84185
--- /dev/null
+++ b/third_party/rust/glean/tests/schema.rs
@@ -0,0 +1,211 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+use std::collections::HashMap;
+use std::io::Read;
+
+use flate2::read::GzDecoder;
+use glean_core::TextMetric;
+use jsonschema_valid::{self, schemas::Draft};
+use serde_json::Value;
+
+use glean::net::UploadResult;
+use glean::private::*;
+use glean::{
+ traits, ClientInfoMetrics, CommonMetricData, ConfigurationBuilder, HistogramType, MemoryUnit,
+ TimeUnit,
+};
+
+const SCHEMA_JSON: &str = include_str!("../../../glean.1.schema.json");
+
+fn load_schema() -> Value {
+ serde_json::from_str(SCHEMA_JSON).unwrap()
+}
+
+const GLOBAL_APPLICATION_ID: &str = "org.mozilla.glean.test.app";
+
+struct SomeExtras {
+ extra1: Option<String>,
+ extra2: Option<bool>,
+}
+
+impl traits::ExtraKeys for SomeExtras {
+ const ALLOWED_KEYS: &'static [&'static str] = &["extra1", "extra2"];
+
+ fn into_ffi_extra(self) -> HashMap<String, String> {
+ let mut map = HashMap::new();
+
+ self.extra1
+ .and_then(|val| map.insert("extra1".to_string(), val));
+ self.extra2
+ .and_then(|val| map.insert("extra2".to_string(), val.to_string()));
+
+ map
+ }
+}
+
+#[test]
+fn validate_against_schema() {
+ let _ = env_logger::builder().try_init();
+
+ let schema = load_schema();
+
+ let (s, r) = crossbeam_channel::bounded::<Vec<u8>>(1);
+
+ // Define a fake uploader that reports back the submitted payload
+ // using a crossbeam channel.
+ #[derive(Debug)]
+ pub struct ValidatingUploader {
+ sender: crossbeam_channel::Sender<Vec<u8>>,
+ }
+ impl glean::net::PingUploader for ValidatingUploader {
+ fn upload(
+ &self,
+ _url: String,
+ body: Vec<u8>,
+ _headers: Vec<(String, String)>,
+ ) -> UploadResult {
+ self.sender.send(body).unwrap();
+ UploadResult::http_status(200)
+ }
+ }
+
+ // Create a custom configuration to use a validating uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+
+ let cfg = ConfigurationBuilder::new(true, tmpname, GLOBAL_APPLICATION_ID)
+ .with_server_endpoint("invalid-test-host")
+ .with_uploader(ValidatingUploader { sender: s })
+ .build();
+
+ let client_info = ClientInfoMetrics {
+ app_build: env!("CARGO_PKG_VERSION").to_string(),
+ app_display_version: env!("CARGO_PKG_VERSION").to_string(),
+ channel: Some("testing".to_string()),
+ };
+
+ glean::initialize(cfg, client_info);
+
+ const PING_NAME: &str = "test-ping";
+
+ let common = |name: &str| CommonMetricData {
+ category: "test".into(),
+ name: name.into(),
+ send_in_pings: vec![PING_NAME.into()],
+ ..Default::default()
+ };
+
+ // Test each of the metric types, just for basic smoke testing against the
+ // schema
+
+ // TODO: 1695762 Test all of the metric types against the schema from Rust
+
+ let counter_metric = CounterMetric::new(common("counter"));
+ counter_metric.add(42);
+
+ let bool_metric = BooleanMetric::new(common("bool"));
+ bool_metric.set(true);
+
+ let string_metric = StringMetric::new(common("string"));
+ string_metric.set("test".into());
+
+ let stringlist_metric = StringListMetric::new(common("stringlist"));
+ stringlist_metric.add("one".into());
+ stringlist_metric.add("two".into());
+
+ // Let's make sure an empty array is accepted.
+ let stringlist_metric2 = StringListMetric::new(common("stringlist2"));
+ stringlist_metric2.set(vec![]);
+
+ let timespan_metric = TimespanMetric::new(common("timespan"), TimeUnit::Nanosecond);
+ timespan_metric.start();
+ timespan_metric.stop();
+
+ let timing_dist = TimingDistributionMetric::new(common("timing_dist"), TimeUnit::Nanosecond);
+ let id = timing_dist.start();
+ timing_dist.stop_and_accumulate(id);
+
+ let memory_dist = MemoryDistributionMetric::new(common("memory_dist"), MemoryUnit::Byte);
+ memory_dist.accumulate(100);
+
+ let uuid_metric = UuidMetric::new(common("uuid"));
+ // chosen by fair dic roll (`uuidgen`)
+ uuid_metric.set("3ee4db5f-ee26-4557-9a66-bc7425d7893f".into());
+
+ // We can't test the URL metric,
+ // because the regex used in the schema uses a negative lookahead,
+ // which the regex crate doesn't handle.
+ //
+ //let url_metric = UrlMetric::new(common("url"));
+ //url_metric.set("https://mozilla.github.io/glean/");
+
+ let datetime_metric = DatetimeMetric::new(common("datetime"), TimeUnit::Day);
+ datetime_metric.set(None);
+
+ let event_metric = EventMetric::<SomeExtras>::new(common("event"));
+ event_metric.record(None);
+ event_metric.record(SomeExtras {
+ extra1: Some("test".into()),
+ extra2: Some(false),
+ });
+
+ let custom_dist =
+ CustomDistributionMetric::new(common("custom_dist"), 1, 100, 100, HistogramType::Linear);
+ custom_dist.accumulate_samples(vec![50, 51]);
+
+ let quantity_metric = QuantityMetric::new(common("quantity"));
+ quantity_metric.set(0);
+
+ let rate_metric = RateMetric::new(common("rate"));
+ rate_metric.add_to_numerator(1);
+ rate_metric.add_to_denominator(1);
+
+ let numerator_metric1 = NumeratorMetric::new(common("num1"));
+ let numerator_metric2 = NumeratorMetric::new(common("num2"));
+ let denominator_metric =
+ DenominatorMetric::new(common("den"), vec![common("num1"), common("num2")]);
+
+ numerator_metric1.add_to_numerator(1);
+ numerator_metric2.add_to_numerator(2);
+ denominator_metric.add(3);
+
+ let text_metric = TextMetric::new(common("text"));
+ text_metric.set("loooooong text".repeat(100));
+
+ // Define a new ping and submit it.
+ let custom_ping = glean::private::PingType::new(PING_NAME, true, true, vec![]);
+ custom_ping.submit(None);
+
+ // Wait for the ping to arrive.
+ let raw_body = r.recv().unwrap();
+
+ // Decode the gzipped body.
+ let mut gzip_decoder = GzDecoder::new(&raw_body[..]);
+ let mut s = String::with_capacity(raw_body.len());
+
+ let data = gzip_decoder
+ .read_to_string(&mut s)
+ .ok()
+ .map(|_| &s[..])
+ .or_else(|| std::str::from_utf8(&raw_body).ok())
+ .and_then(|payload| serde_json::from_str(payload).ok())
+ .unwrap();
+
+ // Now validate against the vendored schema
+ let cfg = jsonschema_valid::Config::from_schema(&schema, Some(Draft::Draft6)).unwrap();
+ let validation = cfg.validate(&data);
+ match validation {
+ Ok(()) => {}
+ Err(errors) => {
+ let mut msg = format!("Data: {data:#?}\n Errors:\n");
+ for (idx, error) in errors.enumerate() {
+ msg.push_str(&format!("Error {}: ", idx + 1));
+ msg.push_str(&error.to_string());
+ msg.push('\n');
+ }
+ panic!("{}", msg);
+ }
+ }
+}
diff --git a/third_party/rust/glean/tests/simple.rs b/third_party/rust/glean/tests/simple.rs
new file mode 100644
index 0000000000..efc8d9a0f8
--- /dev/null
+++ b/third_party/rust/glean/tests/simple.rs
@@ -0,0 +1,77 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+use glean::ConfigurationBuilder;
+
+/// Some user metrics.
+mod metrics {
+ use glean::private::*;
+ use glean::{Lifetime, TimeUnit};
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static initialization: Lazy<TimespanMetric> = Lazy::new(|| {
+ TimespanMetric::new(
+ CommonMetricData {
+ name: "initialization".into(),
+ category: "sample".into(),
+ send_in_pings: vec!["validation".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ },
+ TimeUnit::Nanosecond,
+ )
+ });
+}
+
+mod pings {
+ use glean::private::PingType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static validation: Lazy<PingType> =
+ Lazy::new(|| glean::private::PingType::new("validation", true, true, vec![]));
+}
+
+/// Test scenario: A clean run
+///
+/// The app is initialized, in turn Glean gets initialized without problems.
+/// Some data is recorded (before and after initialization).
+/// And later the whole process is shutdown.
+#[test]
+fn simple_lifecycle() {
+ common::enable_test_logging();
+
+ metrics::initialization.start();
+
+ // Create a custom configuration to use a validating uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+
+ let cfg = ConfigurationBuilder::new(true, tmpname, "firefox-desktop")
+ .with_server_endpoint("invalid-test-host")
+ .build();
+ common::initialize(cfg);
+
+ metrics::initialization.stop();
+
+ // This would never be called outside of tests,
+ // but it's the only way we can really test it's working right now.
+ assert!(metrics::initialization.test_get_value(None).is_some());
+
+ pings::validation.submit(None);
+ assert!(metrics::initialization.test_get_value(None).is_none());
+
+ glean::shutdown();
+}
diff --git a/third_party/rust/glean/tests/test-shutdown-blocking.sh b/third_party/rust/glean/tests/test-shutdown-blocking.sh
new file mode 100755
index 0000000000..2f5d82acf0
--- /dev/null
+++ b/third_party/rust/glean/tests/test-shutdown-blocking.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+# Test harness for testing the RLB processes from the outside.
+#
+# Some behavior can only be observed when properly exiting the process running Glean,
+# e.g. when an uploader runs in another thread.
+# On exit the threads will be killed, regardless of their state.
+
+# Remove the temporary data path on all exit conditions
+cleanup() {
+ if [ -n "$datapath" ]; then
+ rm -r "$datapath"
+ fi
+}
+trap cleanup INT ABRT TERM EXIT
+
+tmp="${TMPDIR:-/tmp}"
+datapath=$(mktemp -d "${tmp}/glean_long_running.XXXX")
+
+cargo run --example long-running -- "$datapath"
+count=$(ls -1q "$datapath/pending_pings" | wc -l)
+
+if [[ "$count" -eq 0 ]]; then
+ echo "test result: ok."
+ exit 0
+else
+ echo "test result: FAILED."
+ exit 101
+fi
diff --git a/third_party/rust/glean/tests/upload_timing.rs b/third_party/rust/glean/tests/upload_timing.rs
new file mode 100644
index 0000000000..1dd073bebb
--- /dev/null
+++ b/third_party/rust/glean/tests/upload_timing.rs
@@ -0,0 +1,225 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// 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/.
+
+//! This integration test should model how the RLB is used when embedded in another Rust application
+//! (e.g. FOG/Firefox Desktop).
+//!
+//! We write a single test scenario per file to avoid any state keeping across runs
+//! (different files run as different processes).
+
+mod common;
+
+use std::io::Read;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::thread;
+use std::time;
+
+use crossbeam_channel::{bounded, Sender};
+use flate2::read::GzDecoder;
+use serde_json::Value as JsonValue;
+
+use glean::net;
+use glean::ConfigurationBuilder;
+
+pub mod metrics {
+ #![allow(non_upper_case_globals)]
+
+ use glean::{
+ private::BooleanMetric, private::TimingDistributionMetric, CommonMetricData, Lifetime,
+ TimeUnit,
+ };
+
+ pub static sample_boolean: once_cell::sync::Lazy<BooleanMetric> =
+ once_cell::sync::Lazy::new(|| {
+ BooleanMetric::new(CommonMetricData {
+ name: "sample_boolean".into(),
+ category: "test.metrics".into(),
+ send_in_pings: vec!["validation".into()],
+ disabled: false,
+ lifetime: Lifetime::Ping,
+ ..Default::default()
+ })
+ });
+
+ // The following are duplicated from `glean-core/src/internal_metrics.rs`
+ // so we can use the test APIs to query them.
+
+ pub static send_success: once_cell::sync::Lazy<TimingDistributionMetric> =
+ once_cell::sync::Lazy::new(|| {
+ TimingDistributionMetric::new(
+ CommonMetricData {
+ name: "send_success".into(),
+ category: "glean.upload".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ dynamic_label: None,
+ },
+ TimeUnit::Millisecond,
+ )
+ });
+
+ pub static send_failure: once_cell::sync::Lazy<TimingDistributionMetric> =
+ once_cell::sync::Lazy::new(|| {
+ TimingDistributionMetric::new(
+ CommonMetricData {
+ name: "send_failure".into(),
+ category: "glean.upload".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ dynamic_label: None,
+ },
+ TimeUnit::Millisecond,
+ )
+ });
+
+ pub static shutdown_wait: once_cell::sync::Lazy<TimingDistributionMetric> =
+ once_cell::sync::Lazy::new(|| {
+ TimingDistributionMetric::new(
+ CommonMetricData {
+ name: "shutdown_wait".into(),
+ category: "glean.validation".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ dynamic_label: None,
+ },
+ TimeUnit::Millisecond,
+ )
+ });
+}
+
+mod pings {
+ use glean::private::PingType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ pub static validation: Lazy<PingType> =
+ Lazy::new(|| glean::private::PingType::new("validation", true, true, vec![]));
+}
+
+// Define a fake uploader that sleeps.
+#[derive(Debug)]
+struct FakeUploader {
+ calls: AtomicUsize,
+ sender: Sender<JsonValue>,
+}
+
+impl net::PingUploader for FakeUploader {
+ fn upload(
+ &self,
+ _url: String,
+ body: Vec<u8>,
+ _headers: Vec<(String, String)>,
+ ) -> net::UploadResult {
+ let calls = self.calls.fetch_add(1, Ordering::SeqCst);
+ let decode = |body: Vec<u8>| {
+ let mut gzip_decoder = GzDecoder::new(&body[..]);
+ let mut s = String::with_capacity(body.len());
+
+ gzip_decoder
+ .read_to_string(&mut s)
+ .ok()
+ .map(|_| &s[..])
+ .or_else(|| std::str::from_utf8(&body).ok())
+ .and_then(|payload| serde_json::from_str(payload).ok())
+ .unwrap()
+ };
+
+ match calls {
+ // First goes through as is.
+ 0 => net::UploadResult::http_status(200),
+ // Second briefly sleeps
+ 1 => {
+ thread::sleep(time::Duration::from_millis(100));
+ net::UploadResult::http_status(200)
+ }
+ // Third one fails
+ 2 => net::UploadResult::http_status(404),
+ // Fourth one fast again
+ 3 => {
+ self.sender.send(decode(body)).unwrap();
+ net::UploadResult::http_status(200)
+ }
+ // Last one is the metrics ping, a-ok.
+ _ => {
+ self.sender.send(decode(body)).unwrap();
+ net::UploadResult::http_status(200)
+ }
+ }
+ }
+}
+
+/// Test scenario: Different timings for upload on success and failure.
+///
+/// The app is initialized, in turn Glean gets initialized without problems.
+/// A custom ping is submitted multiple times to trigger upload.
+/// A metrics ping is submitted to get the upload timing data.
+///
+/// And later the whole process is shutdown.
+#[test]
+fn upload_timings() {
+ common::enable_test_logging();
+
+ // Create a custom configuration to use a validating uploader.
+ let dir = tempfile::tempdir().unwrap();
+ let tmpname = dir.path().to_path_buf();
+ let (tx, rx) = bounded(1);
+
+ let cfg = ConfigurationBuilder::new(true, tmpname.clone(), "glean-upload-timing")
+ .with_server_endpoint("invalid-test-host")
+ .with_use_core_mps(false)
+ .with_uploader(FakeUploader {
+ calls: AtomicUsize::new(0),
+ sender: tx,
+ })
+ .build();
+ common::initialize(cfg);
+
+ // Wait for init to finish,
+ // otherwise we might be to quick with calling `shutdown`.
+ let _ = metrics::sample_boolean.test_get_value(None);
+
+ // fast
+ pings::validation.submit(None);
+ // slow
+ pings::validation.submit(None);
+ // failed
+ pings::validation.submit(None);
+ // fast
+ pings::validation.submit(None);
+
+ // wait for the last ping
+ let _body = rx.recv().unwrap();
+
+ assert_eq!(
+ 3,
+ metrics::send_success.test_get_value(None).unwrap().count,
+ "Successful pings: two fast, one slow"
+ );
+ assert_eq!(
+ 1,
+ metrics::send_failure.test_get_value(None).unwrap().count,
+ "One failed ping"
+ );
+
+ // This is awkward, but it's what gets us very close to just starting a new process with a
+ // fresh Glean.
+ // This also calls `glean::shutdown();` internally, waiting on the uploader.
+ let data_path = Some(tmpname.display().to_string());
+ glean_core::glean_test_destroy_glean(false, data_path);
+
+ let cfg = ConfigurationBuilder::new(true, tmpname, "glean-upload-timing")
+ .with_server_endpoint("invalid-test-host")
+ .with_use_core_mps(false)
+ .build();
+ common::initialize(cfg);
+
+ assert_eq!(
+ 1,
+ metrics::shutdown_wait.test_get_value(None).unwrap().count,
+ "Measured time waiting for shutdown exactly once"
+ );
+}