diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/glean/tests | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
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.rs | 51 | ||||
-rw-r--r-- | third_party/rust/glean/tests/init_fails.rs | 77 | ||||
-rw-r--r-- | third_party/rust/glean/tests/never_init.rs | 66 | ||||
-rw-r--r-- | third_party/rust/glean/tests/no_time_to_init.rs | 74 | ||||
-rw-r--r-- | third_party/rust/glean/tests/overflowing_preinit.rs | 88 | ||||
-rw-r--r-- | third_party/rust/glean/tests/persist_ping_lifetime.rs | 89 | ||||
-rw-r--r-- | third_party/rust/glean/tests/persist_ping_lifetime_nopanic.rs | 37 | ||||
-rw-r--r-- | third_party/rust/glean/tests/schema.rs | 211 | ||||
-rw-r--r-- | third_party/rust/glean/tests/simple.rs | 77 | ||||
-rwxr-xr-x | third_party/rust/glean/tests/test-shutdown-blocking.sh | 29 | ||||
-rw-r--r-- | third_party/rust/glean/tests/upload_timing.rs | 225 |
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" + ); +} |