From 6913fc9ca34fa8846e834b9d9e930cae8bf4c9aa Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Fri, 27 May 2022 15:16:53 +0200 Subject: [PATCH] Implement the text metric for Kotlin and Swift --- docs/user/reference/metrics/text.md | 177 +++++++++++++++-- .../telemetry/glean/private/TextMetricType.kt | 16 ++ .../ios/Glean.xcodeproj/project.pbxproj | 4 + glean-core/ios/Glean/Metrics/TextMetric.swift | 12 ++ glean-core/src/glean.udl | 10 + glean-core/src/lib.rs | 4 +- glean-core/src/lib_unit_tests.rs | 3 + glean-core/src/metrics/mod.rs | 6 + glean-core/src/metrics/text.rs | 180 ++++++++++++++++++ glean-core/src/traits/mod.rs | 2 + glean-core/src/traits/text.rs | 56 ++++++ glean-core/tests/text.rs | 115 +++++++++++ 12 files changed, 564 insertions(+), 21 deletions(-) create mode 100644 glean-core/android/src/main/java/mozilla/telemetry/glean/private/TextMetricType.kt create mode 100644 glean-core/ios/Glean/Metrics/TextMetric.swift create mode 100644 glean-core/src/metrics/text.rs create mode 100644 glean-core/src/traits/text.rs create mode 100644 glean-core/tests/text.rs diff --git a/docs/user/reference/metrics/text.md b/docs/user/reference/metrics/text.md index 9f279130f..6493f49bd 100644 --- a/docs/user/reference/metrics/text.md +++ b/docs/user/reference/metrics/text.md @@ -18,13 +18,54 @@ Sets a text metric to a specific value. {{#include ../../../shared/tab_header.md}} -
+
-
+```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Article -
+Article.content.set(extractedText) +``` -
+
+ +
+ +```Java +import org.mozilla.yourApplication.GleanMetrics.Article; + +Article.INSTANCE.content().set(extractedText); +``` + +
+ +
+ +```Swift +Article.content.set(extractedText) +``` + +
+ +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +metrics.article.content.set(extracted_text) +``` + +
+ +
+ +```Rust +use glean_metrics::article; + +article::content.set(extracted_text); +``` + +
@@ -35,8 +76,6 @@ article.content.set(extractedText); ```
-
-
{{#include ../../../shared/tab_footer.md}} @@ -58,17 +97,60 @@ article.content.set(extractedText); ### `testGetValue` -Gets the recorded value for a given text metric. +Gets the recorded value for a given text metric. +Returns the string if data is stored. +Returns `null` if no data is stored. {{#include ../../../shared/tab_header.md}} -
+
-
+```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Article -
+assertEquals("some content", Article.content.testGetValue()) +``` -
+
+ +
+ +```Java +import org.mozilla.yourApplication.GleanMetrics.Article; + +assertEquals("some content", Article.INSTANCE.content().testGetValue()); +``` + +
+ +
+ +```Swift +XCTAssertEqual("some content", Article.content.testGetValue()) +``` + +
+ +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +assert "some content" == metrics.article.content.test_get_value() +``` + +
+ +
+ +```Rust +use glean_metrics::article; + +assert_eq!("some content", article::content.test_get_value(None).unwrap()); +``` + +
@@ -80,8 +162,6 @@ assert.strictEqual("some content", await article.content.testGetValue());
-
-
{{#include ../../../shared/tab_footer.md}} @@ -92,13 +172,74 @@ Gets the number of errors recorded for a given text metric. {{#include ../../../shared/tab_header.md}} -
+
-
+```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Article -
+// Was the string truncated, and an error reported? +assertEquals( + 0, + Article.content.testGetNumRecordedErrors(ErrorType.INVALID_OVERFLOW) +) +``` -
+
+ +
+ +```Kotlin +import org.mozilla.yourApplication.GleanMetrics.Article; + +// Was the string truncated, and an error reported? +assertEquals( + 0, + Article.content.testGetNumRecordedErrors(ErrorType.INVALID_OVERFLOW) +); +``` + +
+ +
+ +```Swift +// Was the string truncated, and an error reported? +XCTAssertEqual(0, Article.content.testGetNumRecordedErrors(.invalidOverflow)) +``` + +
+ +
+ +```Python +from glean import load_metrics +metrics = load_metrics("metrics.yaml") + +# Was the string truncated, and an error reported? +assert 0 == metrics.article.content.test_get_num_recorded_errors( + ErrorType.INVALID_OVERFLOW +) +``` + +
+ +
+ +```Rust +use glean::ErrorType; +use glean_metrics::article; + +// Was the string truncated, and an error reported? +assert_eq!( + 0, + article::content.test_get_num_recorded_errors( + ErrorType::InvalidOverflow, + None + ) +); +``` + +
@@ -114,8 +255,6 @@ assert.strictEqual(
-
-
{{#include ../../../shared/tab_footer.md}} diff --git a/glean-core/android/src/main/java/mozilla/telemetry/glean/private/TextMetricType.kt b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/TextMetricType.kt new file mode 100644 index 000000000..03f61911d --- /dev/null +++ b/glean-core/android/src/main/java/mozilla/telemetry/glean/private/TextMetricType.kt @@ -0,0 +1,16 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +package mozilla.telemetry.glean.private + +/** + * This implements the developer facing API for recording text metrics. + * + * Instances of this class type are automatically generated by the parsers at build time, + * allowing developers to record values that were previously registered in the metrics.yaml file. + * + * The text API only exposes the [set] method, which takes care of validating the input + * data and making sure that limits are enforced. + */ +typealias TextMetricType = mozilla.telemetry.glean.internal.TextMetric diff --git a/glean-core/ios/Glean.xcodeproj/project.pbxproj b/glean-core/ios/Glean.xcodeproj/project.pbxproj index 0030c95f2..4ebb6b5e4 100644 --- a/glean-core/ios/Glean.xcodeproj/project.pbxproj +++ b/glean-core/ios/Glean.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ BFFE18382350A5F50068D97B /* TimingDistributionMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFE18372350A5F50068D97B /* TimingDistributionMetric.swift */; }; BFFE183A2350A61F0068D97B /* TimingDistributionMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFE18392350A61F0068D97B /* TimingDistributionMetricTests.swift */; }; BFFE33AB232927C3005348FE /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFFE33AA232927C3005348FE /* Utils.swift */; }; + CD062129284110970006370D /* TextMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD062128284110970006370D /* TextMetric.swift */; }; CD0CADA427E216810015A997 /* glean.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDD08C8527E21104007C8400 /* glean.swift */; }; CD0F7CC026F0F27900EDA6A4 /* UrlMetric.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0F7CBF26F0F27900EDA6A4 /* UrlMetric.swift */; }; CD0F7CC226F0F28900EDA6A4 /* UrlMetricTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD0F7CC126F0F28900EDA6A4 /* UrlMetricTests.swift */; }; @@ -132,6 +133,7 @@ BFFE18372350A5F50068D97B /* TimingDistributionMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimingDistributionMetric.swift; sourceTree = ""; }; BFFE18392350A61F0068D97B /* TimingDistributionMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimingDistributionMetricTests.swift; sourceTree = ""; }; BFFE33AA232927C3005348FE /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; + CD062128284110970006370D /* TextMetric.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextMetric.swift; sourceTree = ""; }; CD0F7CBF26F0F27900EDA6A4 /* UrlMetric.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlMetric.swift; sourceTree = ""; }; CD0F7CC126F0F28900EDA6A4 /* UrlMetricTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlMetricTests.swift; sourceTree = ""; }; CD387868271D9CD100C097D8 /* glean.udl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = glean.udl; path = ../../src/glean.udl; sourceTree = ""; }; @@ -314,6 +316,7 @@ BF30FDC3233260B500840607 /* TimespanMetric.swift */, BFAED5072369751100DF293D /* StringListMetric.swift */, BF89055E232BC213003CA2BA /* StringMetric.swift */, + CD062128284110970006370D /* TextMetric.swift */, 1FD4527423395B4500F4C7E8 /* UuidMetric.swift */, CD0F7CBF26F0F27900EDA6A4 /* UrlMetric.swift */, BF2E57042334B77D00364D92 /* EventMetric.swift */, @@ -576,6 +579,7 @@ 1F58920D23C7D615007D2D80 /* MetricsPingScheduler.swift in Sources */, BF7CC0A62473F61C003B166D /* Metrics.swift in Sources */, 1FB70AEF23301C1D00C7CF09 /* Logger.swift in Sources */, + CD062129284110970006370D /* TextMetric.swift in Sources */, 1F6A8FF0233C049D007837D5 /* BooleanMetric.swift in Sources */, 1F6A8FF4233C0A91007837D5 /* DatetimeMetric.swift in Sources */, AC06529C26E032E300D92D5E /* QuantityMetric.swift in Sources */, diff --git a/glean-core/ios/Glean/Metrics/TextMetric.swift b/glean-core/ios/Glean/Metrics/TextMetric.swift new file mode 100644 index 000000000..799f51daa --- /dev/null +++ b/glean-core/ios/Glean/Metrics/TextMetric.swift @@ -0,0 +1,12 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +/// This implements the developer facing API for recording text metrics. +/// +/// Instances of this class type are automatically generated by the parsers at build time, +/// allowing developers to record values that were previously registered in the metrics.yaml file. +/// +/// The text API only exposes the `TextMetricType.set(_:)` method, which takes care of validating the input +/// data and making sure that limits are enforced. +public typealias TextMetricType = TextMetric diff --git a/glean-core/src/glean.udl b/glean-core/src/glean.udl index 03984c158..7a62b1784 100644 --- a/glean-core/src/glean.udl +++ b/glean-core/src/glean.udl @@ -537,3 +537,13 @@ interface NumeratorMetric { i32 test_get_num_recorded_errors(ErrorType error, optional string? ping_name = null); }; + +interface TextMetric { + constructor(CommonMetricData meta); + + void set(string value); + + string? test_get_value(optional string? ping_name = null); + + i32 test_get_num_recorded_errors(ErrorType error, optional string? ping_name = null); +}; diff --git a/glean-core/src/lib.rs b/glean-core/src/lib.rs index fd7dbd1ef..10f7832fa 100644 --- a/glean-core/src/lib.rs +++ b/glean-core/src/lib.rs @@ -59,8 +59,8 @@ pub use crate::metrics::{ BooleanMetric, CounterMetric, CustomDistributionMetric, Datetime, DatetimeMetric, DenominatorMetric, DistributionData, EventMetric, MemoryDistributionMetric, MemoryUnit, NumeratorMetric, PingType, QuantityMetric, Rate, RateMetric, RecordedEvent, RecordedExperiment, - StringListMetric, StringMetric, TimeUnit, TimerId, TimespanMetric, TimingDistributionMetric, - UrlMetric, UuidMetric, + StringListMetric, StringMetric, TextMetric, TimeUnit, TimerId, TimespanMetric, + TimingDistributionMetric, UrlMetric, UuidMetric, }; pub use crate::upload::{PingRequest, PingUploadTask, UploadResult}; diff --git a/glean-core/src/lib_unit_tests.rs b/glean-core/src/lib_unit_tests.rs index 6f0f8127c..0765a9b1f 100644 --- a/glean-core/src/lib_unit_tests.rs +++ b/glean-core/src/lib_unit_tests.rs @@ -402,6 +402,7 @@ fn correct_order() { } // One of every metric type. The values are arbitrary and don't matter. + let long_string = "0123456789".repeat(200); let all_metrics = vec![ Boolean(false), Counter(0), @@ -418,6 +419,7 @@ fn correct_order() { MemoryDistribution(Histogram::functional(2.0, 8.0)), Jwe("eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ".into()), Rate(0, 0), + Text(long_string), ]; for metric in all_metrics { @@ -445,6 +447,7 @@ fn correct_order() { Jwe(..) => assert_eq!(13, disc), Rate(..) => assert_eq!(14, disc), Url(..) => assert_eq!(15, disc), + Text(..) => assert_eq!(16, disc), } } } diff --git a/glean-core/src/metrics/mod.rs b/glean-core/src/metrics/mod.rs index 511610794..fb3534492 100644 --- a/glean-core/src/metrics/mod.rs +++ b/glean-core/src/metrics/mod.rs @@ -27,6 +27,7 @@ mod rate; mod recorded_experiment; mod string; mod string_list; +mod text; mod time_unit; mod timespan; mod timing_distribution; @@ -56,6 +57,7 @@ pub use self::quantity::QuantityMetric; pub use self::rate::{Rate, RateMetric}; pub use self::string::StringMetric; pub use self::string_list::StringListMetric; +pub use self::text::TextMetric; pub use self::time_unit::TimeUnit; pub use self::timespan::TimespanMetric; pub use self::timing_distribution::TimerId; @@ -125,6 +127,8 @@ pub enum Metric { Rate(i32, i32), /// A URL metric. See [`UrlMetric`] for more information. Url(String), + /// A Text metric. See [`TextMetric`] for more information. + Text(String), } /// A [`MetricType`] describes common behavior across all metrics. @@ -181,6 +185,7 @@ impl Metric { Metric::Uuid(_) => "uuid", Metric::MemoryDistribution(_) => "memory_distribution", Metric::Jwe(_) => "jwe", + Metric::Text(_) => "text", } } @@ -209,6 +214,7 @@ impl Metric { Metric::Uuid(s) => json!(s), Metric::MemoryDistribution(hist) => json!(memory_distribution::snapshot(hist)), Metric::Jwe(s) => json!(s), + Metric::Text(s) => json!(s), } } } diff --git a/glean-core/src/metrics/text.rs b/glean-core/src/metrics/text.rs new file mode 100644 index 000000000..ec8c464b6 --- /dev/null +++ b/glean-core/src/metrics/text.rs @@ -0,0 +1,180 @@ +// 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::sync::Arc; + +use crate::error_recording::{test_get_num_recorded_errors, ErrorType}; +use crate::metrics::Metric; +use crate::metrics::MetricType; +use crate::storage::StorageManager; +use crate::util::truncate_string_at_boundary_with_error; +use crate::CommonMetricData; +use crate::Glean; + +// The maximum number of characters for text. +const MAX_LENGTH_VALUE: usize = 200 * 1024; + +/// A text metric. +/// +/// Records a single long Unicode text, +/// used when the limits on `String` are too low. +/// Text is length-limited to `MAX_LENGTH_VALUE` bytes. +#[derive(Clone, Debug)] +pub struct TextMetric { + meta: Arc, +} + +impl MetricType for TextMetric { + fn meta(&self) -> &CommonMetricData { + &self.meta + } + + fn with_name(&self, name: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.name = name; + Self { + meta: Arc::new(meta), + } + } + + fn with_dynamic_label(&self, label: String) -> Self { + let mut meta = (*self.meta).clone(); + meta.dynamic_label = Some(label); + Self { + meta: Arc::new(meta), + } + } +} + +// IMPORTANT: +// +// When changing this implementation, make sure all the operations are +// also declared in the related trait in `../traits/`. +impl TextMetric { + /// Creates a new text metric. + pub fn new(meta: CommonMetricData) -> Self { + Self { + meta: Arc::new(meta), + } + } + + /// Sets to the specified value. + /// + /// # Arguments + /// + /// * `value` - The text to set the metric to. + /// + /// ## Notes + /// + /// Truncates the value (at codepoint boundaries) if it is longer than `MAX_LENGTH_VALUE` bytes + /// and logs an error. + pub fn set(&self, value: String) { + let metric = self.clone(); + crate::launch_with_glean(move |glean| metric.set_sync(glean, &value)) + } + + /// Sets to the specified value synchronously, + /// truncating and recording an error if longer than `MAX_LENGTH_VALUE`. + #[doc(hidden)] + pub fn set_sync>(&self, glean: &Glean, value: S) { + if !self.should_record(glean) { + return; + } + + let s = truncate_string_at_boundary_with_error(glean, &self.meta, value, MAX_LENGTH_VALUE); + + let value = Metric::Text(s); + glean.storage().record(glean, &self.meta, &value) + } + + /// Gets the currently-stored value as a string, or None if there is no value. + #[doc(hidden)] + pub fn get_value<'a, S: Into>>( + &self, + glean: &Glean, + ping_name: S, + ) -> Option { + let queried_ping_name = ping_name + .into() + .unwrap_or_else(|| &self.meta().send_in_pings[0]); + + match StorageManager.snapshot_metric_for_test( + glean.storage(), + queried_ping_name, + &self.meta.identifier(glean), + self.meta.lifetime, + ) { + Some(Metric::Text(s)) => Some(s), + _ => None, + } + } + + /// **Test-only API (exported for FFI purposes).** + /// + /// Gets the currently stored value as a string. + /// + /// This doesn't clear the stored value. + pub fn test_get_value(&self, ping_name: Option) -> Option { + crate::block_on_dispatcher(); + crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref())) + } + + /// **Exported for test purposes.** + /// + /// Gets the number of recorded errors for the given metric and error type. + /// + /// # Arguments + /// + /// * `error` - The type of error + /// * `ping_name` - represents the optional name of the ping to retrieve the + /// metric for. Defaults to the first value in `send_in_pings`. + /// + /// # Returns + /// + /// The number of errors reported. + pub fn test_get_num_recorded_errors(&self, error: ErrorType, ping_name: Option) -> i32 { + crate::block_on_dispatcher(); + + crate::core::with_glean(|glean| { + test_get_num_recorded_errors(glean, self.meta(), error, ping_name.as_deref()) + .unwrap_or(0) + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test_get_num_recorded_errors; + use crate::tests::new_glean; + use crate::util::truncate_string_at_boundary; + use crate::ErrorType; + use crate::Lifetime; + + #[test] + fn setting_a_long_string_records_an_error() { + let (glean, _) = new_glean(None); + + let metric = TextMetric::new(CommonMetricData { + name: "text_metric".into(), + category: "test".into(), + send_in_pings: vec!["store1".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }); + + let sample_string = "0123456789".repeat(200 * 1024); + metric.set_sync(&glean, sample_string.clone()); + + let truncated = truncate_string_at_boundary(sample_string, MAX_LENGTH_VALUE); + assert_eq!(truncated, metric.get_value(&glean, "store1").unwrap()); + + assert_eq!( + 1, + test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidOverflow, None) + .unwrap() + ); + } +} diff --git a/glean-core/src/traits/mod.rs b/glean-core/src/traits/mod.rs index 182c0597b..c4bcf7cdd 100644 --- a/glean-core/src/traits/mod.rs +++ b/glean-core/src/traits/mod.rs @@ -20,6 +20,7 @@ mod quantity; mod rate; mod string; mod string_list; +mod text; mod timespan; mod timing_distribution; mod url; @@ -41,6 +42,7 @@ pub use self::quantity::Quantity; pub use self::rate::Rate; pub use self::string::String; pub use self::string_list::StringList; +pub use self::text::Text; pub use self::timespan::Timespan; pub use self::timing_distribution::TimingDistribution; pub use self::url::Url; diff --git a/glean-core/src/traits/text.rs b/glean-core/src/traits/text.rs new file mode 100644 index 000000000..032eef647 --- /dev/null +++ b/glean-core/src/traits/text.rs @@ -0,0 +1,56 @@ +// 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 crate::ErrorType; + +/// A description for the [`TextMetric`](crate::metrics::TextMetric) type. +/// +/// When changing this trait, make sure all the operations are +/// implemented in the related type in `../metrics/`. +pub trait Text { + /// Sets to the specified value. + /// + /// # Arguments + /// + /// * `value` - The string to set the metric to. + /// + /// ## Notes + /// + /// Truncates the value if it is longer than `MAX_LENGTH_VALUE` bytes and logs an error. + fn set>(&self, value: S); + + /// **Exported for test purposes.** + /// + /// Gets the currently stored value as a string. + /// + /// This doesn't clear the stored value. + /// + /// # Arguments + /// + /// * `ping_name` - represents the optional name of the ping to retrieve the + /// metric for. Defaults to the first value in `send_in_pings`. + fn test_get_value<'a, S: Into>>( + &self, + ping_name: S, + ) -> Option; + + /// **Exported for test purposes.** + /// + /// Gets the number of recorded errors for the given metric and error type. + /// + /// # Arguments + /// + /// * `error` - The type of error + /// * `ping_name` - represents the optional name of the ping to retrieve the + /// metric for. Defaults to the first value in `send_in_pings`. + /// + /// # Returns + /// + /// The number of errors reported. + fn test_get_num_recorded_errors<'a, S: Into>>( + &self, + error: ErrorType, + ping_name: S, + ) -> i32; +} diff --git a/glean-core/tests/text.rs b/glean-core/tests/text.rs new file mode 100644 index 000000000..5f9d84f59 --- /dev/null +++ b/glean-core/tests/text.rs @@ -0,0 +1,115 @@ +// 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/. + +mod common; +use crate::common::*; + +use serde_json::json; + +use glean_core::metrics::*; +use glean_core::storage::StorageManager; +use glean_core::{test_get_num_recorded_errors, ErrorType}; +use glean_core::{CommonMetricData, Lifetime}; + +#[test] +fn text_serializer_should_correctly_serialize_strings() { + let (mut tempdir, _) = tempdir(); + + { + // We give tempdir to the `new_glean` function... + let (glean, dir) = new_glean(Some(tempdir)); + // And then we get it back once that function returns. + tempdir = dir; + + let metric = TextMetric::new(CommonMetricData { + name: "text_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::User, + ..Default::default() + }); + + metric.set_sync(&glean, "test_text_value"); + + let snapshot = StorageManager + .snapshot_as_json(glean.storage(), "store1", true) + .unwrap(); + assert_eq!( + json!({"text": {"telemetry.text_metric": "test_text_value"}}), + snapshot + ); + } + + // Make a new Glean instance here, which should force reloading of the data from disk + // so we can ensure it persisted, because it has User lifetime + { + let (glean, _) = new_glean(Some(tempdir)); + let snapshot = StorageManager + .snapshot_as_json(glean.storage(), "store1", true) + .unwrap(); + assert_eq!( + json!({"text": {"telemetry.text_metric": "test_text_value"}}), + snapshot + ); + } +} + +#[test] +fn set_properly_sets_the_value_in_all_stores() { + let (glean, _t) = new_glean(None); + let store_names: Vec = vec!["store1".into(), "store2".into()]; + + let metric = TextMetric::new(CommonMetricData { + name: "text_metric".into(), + category: "telemetry".into(), + send_in_pings: store_names.clone(), + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }); + + metric.set_sync(&glean, "test_text_value"); + + // Check that the data was correctly set in each store. + for store_name in store_names { + let snapshot = StorageManager + .snapshot_as_json(glean.storage(), &store_name, true) + .unwrap(); + + assert_eq!( + json!({"text": {"telemetry.text_metric": "test_text_value"}}), + snapshot + ); + } +} + +#[test] +fn long_text_values_are_truncated() { + let (glean, _t) = new_glean(None); + + let metric = TextMetric::new(CommonMetricData { + name: "text_metric".into(), + category: "telemetry".into(), + send_in_pings: vec!["store1".into()], + disabled: false, + lifetime: Lifetime::Ping, + ..Default::default() + }); + + let test_sting = "01234567890".repeat(200 * 1024); + metric.set_sync(&glean, test_sting.clone()); + + // Check that data was truncated + assert_eq!( + test_sting[..(200 * 1024)], + metric.get_value(&glean, "store1").unwrap() + ); + + // Make sure that the errors have been recorded + assert_eq!( + Ok(1), + test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidOverflow, None) + ); +}