summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/gtest
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/tests/gtest')
-rw-r--r--toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp28
-rw-r--r--toolkit/components/telemetry/tests/gtest/TelemetryFixture.h39
-rw-r--r--toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp376
-rw-r--r--toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.h68
-rw-r--r--toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp158
-rw-r--r--toolkit/components/telemetry/tests/gtest/TestCounters.cpp173
-rw-r--r--toolkit/components/telemetry/tests/gtest/TestEvents.cpp125
-rw-r--r--toolkit/components/telemetry/tests/gtest/TestHistograms.cpp891
-rw-r--r--toolkit/components/telemetry/tests/gtest/TestOrigins.cpp290
-rw-r--r--toolkit/components/telemetry/tests/gtest/TestScalars.cpp491
-rw-r--r--toolkit/components/telemetry/tests/gtest/moz.build30
11 files changed, 2669 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp
new file mode 100644
index 0000000000..35742654f6
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "TelemetryFixture.h"
+#include "mozilla/dom/SimpleGlobalObject.h"
+
+using namespace mozilla;
+
+void TelemetryTestFixture::SetUp() {
+ mTelemetry = do_GetService("@mozilla.org/base/telemetry;1");
+
+ mCleanGlobal = dom::SimpleGlobalObject::Create(
+ dom::SimpleGlobalObject::GlobalType::BindingDetail);
+
+ // The test must fail if we failed getting the global.
+ ASSERT_NE(mCleanGlobal, nullptr)
+ << "SimpleGlobalObject must return a valid global object.";
+}
+
+AutoJSContextWithGlobal::AutoJSContextWithGlobal(JSObject* aGlobalObject)
+ : mCx(nullptr) {
+ // The JS API must initialize correctly.
+ JS::Rooted<JSObject*> globalObject(dom::RootingCx(), aGlobalObject);
+ MOZ_ALWAYS_TRUE(mJsAPI.Init(globalObject));
+}
+
+JSContext* AutoJSContextWithGlobal::GetJSContext() const { return mJsAPI.cx(); }
diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h
new file mode 100644
index 0000000000..f89aecf6a8
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#ifndef TelemetryFixture_h_
+#define TelemetryFixture_h_
+
+#include "gtest/gtest.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsITelemetry.h"
+
+class TelemetryTestFixture : public ::testing::Test {
+ protected:
+ TelemetryTestFixture() : mCleanGlobal(nullptr) {}
+ virtual void SetUp();
+
+ JSObject* mCleanGlobal;
+
+ nsCOMPtr<nsITelemetry> mTelemetry;
+};
+
+// AutoJSAPI is annotated with MOZ_STACK_CLASS and thus cannot be
+// used as a member of TelemetryTestFixture, since gtest instantiates
+// that on the heap. To work around the problem, use the following class
+// at the beginning of each Telemetry test.
+// Note: this is very similar to AutoJSContext, but it allows to pass a
+// global JS object in.
+class MOZ_RAII AutoJSContextWithGlobal {
+ public:
+ explicit AutoJSContextWithGlobal(JSObject* aGlobalObject);
+ JSContext* GetJSContext() const;
+
+ protected:
+ mozilla::dom::AutoJSAPI mJsAPI;
+ JSContext* mCx;
+};
+
+#endif // TelemetryFixture_h_
diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp
new file mode 100644
index 0000000000..ff219ef386
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.cpp
@@ -0,0 +1,376 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "TelemetryTestHelpers.h"
+
+#include "core/TelemetryCommon.h"
+#include "core/TelemetryOrigin.h"
+#include "gtest/gtest.h"
+#include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/Unused.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla;
+
+// Helper methods provided to simplify writing tests and meant to be used in C++
+// Gtests.
+namespace TelemetryTestHelpers {
+
+void CheckUintScalar(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot, uint32_t expectedValue) {
+ // Validate the value of the test scalar.
+ JS::RootedValue value(aCx);
+ JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
+ ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value))
+ << "The test scalar must be reported.";
+ JS_GetProperty(aCx, scalarObj, aName, &value);
+
+ ASSERT_TRUE(value.isInt32())
+ << "The scalar value must be of the correct type.";
+ ASSERT_TRUE(value.toInt32() >= 0)
+ << "The uint scalar type must contain a value >= 0.";
+ ASSERT_EQ(static_cast<uint32_t>(value.toInt32()), expectedValue)
+ << "The scalar value must match the expected value.";
+}
+
+void CheckBoolScalar(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot, bool expectedValue) {
+ // Validate the value of the test scalar.
+ JS::RootedValue value(aCx);
+ JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
+ ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value))
+ << "The test scalar must be reported.";
+ ASSERT_TRUE(value.isBoolean())
+ << "The scalar value must be of the correct type.";
+ ASSERT_EQ(static_cast<bool>(value.toBoolean()), expectedValue)
+ << "The scalar value must match the expected value.";
+}
+
+void CheckStringScalar(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot, const char* expectedValue) {
+ // Validate the value of the test scalar.
+ JS::RootedValue value(aCx);
+ JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
+ ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &value))
+ << "The test scalar must be reported.";
+ ASSERT_TRUE(value.isString())
+ << "The scalar value must be of the correct type.";
+
+ bool sameString;
+ ASSERT_TRUE(
+ JS_StringEqualsAscii(aCx, value.toString(), expectedValue, &sameString))
+ << "JS String comparison failed";
+ ASSERT_TRUE(sameString)
+ << "The scalar value must match the expected string";
+}
+
+void CheckKeyedUintScalar(const char* aName, const char* aKey, JSContext* aCx,
+ JS::HandleValue aSnapshot, uint32_t expectedValue) {
+ JS::RootedValue keyedScalar(aCx);
+ JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
+ // Get the aName keyed scalar object from the scalars snapshot.
+ ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar))
+ << "The keyed scalar must be reported.";
+
+ CheckUintScalar(aKey, aCx, keyedScalar, expectedValue);
+}
+
+void CheckKeyedBoolScalar(const char* aName, const char* aKey, JSContext* aCx,
+ JS::HandleValue aSnapshot, bool expectedValue) {
+ JS::RootedValue keyedScalar(aCx);
+ JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
+ // Get the aName keyed scalar object from the scalars snapshot.
+ ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar))
+ << "The keyed scalar must be reported.";
+
+ CheckBoolScalar(aKey, aCx, keyedScalar, expectedValue);
+}
+
+void CheckNumberOfProperties(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot,
+ uint32_t expectedNumProperties) {
+ JS::RootedValue keyedScalar(aCx);
+ JS::RootedObject scalarObj(aCx, &aSnapshot.toObject());
+ // Get the aName keyed scalar object from the scalars snapshot.
+ ASSERT_TRUE(JS_GetProperty(aCx, scalarObj, aName, &keyedScalar))
+ << "The keyed scalar must be reported.";
+
+ JS::RootedObject keyedScalarObj(aCx, &keyedScalar.toObject());
+ JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
+ ASSERT_TRUE(JS_Enumerate(aCx, keyedScalarObj, &ids))
+ << "We must be able to get keyed scalar members.";
+
+ ASSERT_EQ(expectedNumProperties, ids.length())
+ << "The scalar must report the expected number of properties.";
+}
+
+bool EventPresent(JSContext* aCx, const JS::RootedValue& aSnapshot,
+ const nsACString& aCategory, const nsACString& aMethod,
+ const nsACString& aObject) {
+ EXPECT_FALSE(aSnapshot.isNullOrUndefined())
+ << "Event snapshot must not be null/undefined.";
+ bool isArray = false;
+ EXPECT_TRUE(JS::IsArrayObject(aCx, aSnapshot, &isArray) && isArray)
+ << "The snapshot must be an array.";
+ JS::RootedObject arrayObj(aCx, &aSnapshot.toObject());
+ uint32_t arrayLength = 0;
+ EXPECT_TRUE(JS::GetArrayLength(aCx, arrayObj, &arrayLength))
+ << "Array must have a length.";
+ EXPECT_TRUE(arrayLength > 0) << "Array must have at least one element.";
+
+ for (uint32_t arrayIdx = 0; arrayIdx < arrayLength; ++arrayIdx) {
+ JS::Rooted<JS::Value> element(aCx);
+ EXPECT_TRUE(JS_GetElement(aCx, arrayObj, arrayIdx, &element))
+ << "Must be able to get element.";
+ EXPECT_TRUE(JS::IsArrayObject(aCx, element, &isArray) && isArray)
+ << "Element must be an array.";
+ JS::RootedObject eventArray(aCx, &element.toObject());
+ uint32_t eventLength;
+ EXPECT_TRUE(JS::GetArrayLength(aCx, eventArray, &eventLength))
+ << "Event array must have a length.";
+ EXPECT_TRUE(eventLength >= 4)
+ << "Event array must have at least 4 elements (timestamp, category, "
+ "method, object).";
+
+ JS::Rooted<JS::Value> str(aCx);
+ nsAutoJSString jsStr;
+ EXPECT_TRUE(JS_GetElement(aCx, eventArray, 1, &str))
+ << "Must be able to get category.";
+ EXPECT_TRUE(str.isString()) << "Category must be a string.";
+ EXPECT_TRUE(jsStr.init(aCx, str))
+ << "Category must be able to be init'd to a jsstring.";
+ if (NS_ConvertUTF16toUTF8(jsStr) != aCategory) {
+ continue;
+ }
+
+ EXPECT_TRUE(JS_GetElement(aCx, eventArray, 2, &str))
+ << "Must be able to get method.";
+ EXPECT_TRUE(str.isString()) << "Method must be a string.";
+ EXPECT_TRUE(jsStr.init(aCx, str))
+ << "Method must be able to be init'd to a jsstring.";
+ if (NS_ConvertUTF16toUTF8(jsStr) != aMethod) {
+ continue;
+ }
+
+ EXPECT_TRUE(JS_GetElement(aCx, eventArray, 3, &str))
+ << "Must be able to get object.";
+ EXPECT_TRUE(str.isString()) << "Object must be a string.";
+ EXPECT_TRUE(jsStr.init(aCx, str))
+ << "Object must be able to be init'd to a jsstring.";
+ if (NS_ConvertUTF16toUTF8(jsStr) != aObject) {
+ continue;
+ }
+
+ // We found it!
+ return true;
+ }
+
+ // We didn't find it!
+ return false;
+}
+
+void GetOriginSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ bool aClear) {
+ nsCOMPtr<nsITelemetry> telemetry =
+ do_GetService("@mozilla.org/base/telemetry;1");
+
+ JS::RootedValue originSnapshot(aCx);
+ nsresult rv;
+ rv = telemetry->GetOriginSnapshot(aClear, aCx, &originSnapshot);
+ ASSERT_EQ(rv, NS_OK) << "Snapshotting origin data must not fail.";
+ ASSERT_TRUE(originSnapshot.isObject())
+ << "The snapshot must be an object.";
+
+ aResult.set(originSnapshot);
+}
+
+/*
+ * Extracts the `a` and `b` strings from the prioData snapshot object
+ * of any length. Which looks like:
+ *
+ * [{
+ * encoding: encodingName,
+ * prio: {
+ * a: <string>,
+ * b: <string>,
+ * },
+ * }, ...]
+ */
+void GetEncodedOriginStrings(
+ JSContext* aCx, const nsCString& aEncoding,
+ nsTArray<Tuple<nsCString, nsCString>>& aPrioStrings) {
+ JS::RootedValue snapshot(aCx);
+ nsresult rv;
+ rv = TelemetryOrigin::GetEncodedOriginSnapshot(false /* clear */, aCx,
+ &snapshot);
+
+ ASSERT_FALSE(NS_FAILED(rv));
+ ASSERT_FALSE(snapshot.isNullOrUndefined())
+ << "Encoded snapshot must not be null/undefined.";
+
+ JS::RootedObject prioDataObj(aCx, &snapshot.toObject());
+ bool isArray = false;
+ ASSERT_TRUE(JS::IsArrayObject(aCx, prioDataObj, &isArray) && isArray)
+ << "The metric's origins must be in an array.";
+
+ uint32_t length = 0;
+ ASSERT_TRUE(JS::GetArrayLength(aCx, prioDataObj, &length));
+ ASSERT_TRUE(length > 0)
+ << "Length of returned array must greater than 0";
+
+ for (auto i = 0u; i < length; ++i) {
+ JS::RootedValue arrayItem(aCx);
+ ASSERT_TRUE(JS_GetElement(aCx, prioDataObj, i, &arrayItem));
+ ASSERT_TRUE(arrayItem.isObject());
+ ASSERT_FALSE(arrayItem.isNullOrUndefined());
+
+ JS::RootedObject arrayItemObj(aCx, &arrayItem.toObject());
+
+ JS::RootedValue encodingVal(aCx);
+ ASSERT_TRUE(JS_GetProperty(aCx, arrayItemObj, "encoding", &encodingVal));
+ ASSERT_TRUE(encodingVal.isString());
+ nsAutoJSString jsStr;
+ ASSERT_TRUE(jsStr.init(aCx, encodingVal));
+
+ nsPrintfCString encoding(aEncoding.get(),
+ i % TelemetryOrigin::SizeOfPrioDatasPerMetric());
+ ASSERT_TRUE(NS_ConvertUTF16toUTF8(jsStr) == encoding)
+ << "Actual 'encoding' (" << NS_ConvertUTF16toUTF8(jsStr).get()
+ << ") must match expected (" << encoding << ")";
+
+ JS::RootedValue prioVal(aCx);
+ ASSERT_TRUE(JS_GetProperty(aCx, arrayItemObj, "prio", &prioVal));
+ ASSERT_TRUE(prioVal.isObject());
+ ASSERT_FALSE(prioVal.isNullOrUndefined());
+
+ JS::RootedObject prioObj(aCx, &prioVal.toObject());
+
+ JS::RootedValue aVal(aCx);
+ nsAutoJSString aStr;
+ ASSERT_TRUE(JS_GetProperty(aCx, prioObj, "a", &aVal));
+ ASSERT_TRUE(aVal.isString());
+ ASSERT_TRUE(aStr.init(aCx, aVal));
+
+ JS::RootedValue bVal(aCx);
+ nsAutoJSString bStr;
+ ASSERT_TRUE(JS_GetProperty(aCx, prioObj, "b", &bVal));
+ ASSERT_TRUE(bVal.isString());
+ ASSERT_TRUE(bStr.init(aCx, bVal));
+
+ aPrioStrings.AppendElement(Tuple<nsCString, nsCString>(
+ NS_ConvertUTF16toUTF8(aStr), NS_ConvertUTF16toUTF8(bStr)));
+ }
+}
+
+void GetEventSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ ProcessID aProcessType) {
+ nsCOMPtr<nsITelemetry> telemetry =
+ do_GetService("@mozilla.org/base/telemetry;1");
+
+ JS::RootedValue eventSnapshot(aCx);
+ nsresult rv;
+ rv = telemetry->SnapshotEvents(1 /* PRERELEASE_CHANNELS */, false /* clear */,
+ 0 /* eventLimit */, aCx, 1 /* argc */,
+ &eventSnapshot);
+ ASSERT_EQ(rv, NS_OK) << "Snapshotting events must not fail.";
+ ASSERT_TRUE(eventSnapshot.isObject())
+ << "The snapshot must be an object.";
+
+ JS::RootedValue processEvents(aCx);
+ JS::RootedObject eventObj(aCx, &eventSnapshot.toObject());
+ Unused << JS_GetProperty(aCx, eventObj,
+ Telemetry::Common::GetNameForProcessID(aProcessType),
+ &processEvents);
+
+ aResult.set(processEvents);
+}
+
+void GetScalarsSnapshot(bool aKeyed, JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult,
+ ProcessID aProcessType) {
+ nsCOMPtr<nsITelemetry> telemetry =
+ do_GetService("@mozilla.org/base/telemetry;1");
+
+ // Get a snapshot of the scalars.
+ JS::RootedValue scalarsSnapshot(aCx);
+ nsresult rv;
+
+ if (aKeyed) {
+ rv = telemetry->GetSnapshotForKeyedScalars(
+ "main"_ns, false, false /* filter */, aCx, &scalarsSnapshot);
+ } else {
+ rv = telemetry->GetSnapshotForScalars("main"_ns, false, false /* filter */,
+ aCx, &scalarsSnapshot);
+ }
+
+ // Validate the snapshot.
+ ASSERT_EQ(rv, NS_OK) << "Creating a snapshot of the data must not fail.";
+ ASSERT_TRUE(scalarsSnapshot.isObject())
+ << "The snapshot must be an object.";
+
+ JS::RootedValue processScalars(aCx);
+ JS::RootedObject scalarObj(aCx, &scalarsSnapshot.toObject());
+ // Don't complain if no scalars for the process can be found. Just
+ // return an empty object.
+ Unused << JS_GetProperty(aCx, scalarObj,
+ Telemetry::Common::GetNameForProcessID(aProcessType),
+ &processScalars);
+
+ aResult.set(processScalars);
+}
+
+void GetAndClearHistogram(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry,
+ const nsACString& name, bool is_keyed) {
+ JS::RootedValue testHistogram(cx);
+ nsresult rv =
+ is_keyed ? mTelemetry->GetKeyedHistogramById(name, cx, &testHistogram)
+ : mTelemetry->GetHistogramById(name, cx, &testHistogram);
+
+ ASSERT_EQ(rv, NS_OK) << "Cannot fetch histogram";
+
+ // Clear the stored value
+ JS::RootedObject testHistogramObj(cx, &testHistogram.toObject());
+ JS::RootedValue rval(cx);
+ ASSERT_TRUE(JS_CallFunctionName(cx, testHistogramObj, "clear",
+ JS::HandleValueArray::empty(), &rval))
+ << "Cannot clear histogram";
+}
+
+void GetProperty(JSContext* cx, const char* name, JS::HandleValue valueIn,
+ JS::MutableHandleValue valueOut) {
+ JS::RootedValue property(cx);
+ JS::RootedObject valueInObj(cx, &valueIn.toObject());
+ ASSERT_TRUE(JS_GetProperty(cx, valueInObj, name, &property))
+ << "Cannot get property '" << name << "'";
+ valueOut.set(property);
+}
+
+void GetElement(JSContext* cx, uint32_t index, JS::HandleValue valueIn,
+ JS::MutableHandleValue valueOut) {
+ JS::RootedValue element(cx);
+ JS::RootedObject valueInObj(cx, &valueIn.toObject());
+ ASSERT_TRUE(JS_GetElement(cx, valueInObj, index, &element))
+ << "Cannot get element at index '" << index << "'";
+ valueOut.set(element);
+}
+
+void GetSnapshots(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry,
+ const char* name, JS::MutableHandleValue valueOut,
+ bool is_keyed) {
+ JS::RootedValue snapshots(cx);
+ nsresult rv = is_keyed
+ ? mTelemetry->GetSnapshotForKeyedHistograms(
+ "main"_ns, false, false /* filter */, cx, &snapshots)
+ : mTelemetry->GetSnapshotForHistograms(
+ "main"_ns, false, false /* filter */, cx, &snapshots);
+
+ JS::RootedValue snapshot(cx);
+ GetProperty(cx, "parent", snapshots, &snapshot);
+
+ ASSERT_EQ(rv, NS_OK) << "Cannot call histogram snapshots";
+ valueOut.set(snapshot);
+}
+
+} // namespace TelemetryTestHelpers
diff --git a/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.h b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.h
new file mode 100644
index 0000000000..b5fdf98e1e
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TelemetryTestHelpers.h
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#ifndef TelemetryTestHelpers_h_
+#define TelemetryTestHelpers_h_
+
+#include "js/TypeDecls.h"
+#include "mozilla/TelemetryProcessEnums.h"
+#include "nsITelemetry.h"
+
+using mozilla::Telemetry::ProcessID;
+
+namespace TelemetryTestHelpers {
+
+void CheckUintScalar(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot, uint32_t expectedValue);
+
+void CheckBoolScalar(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot, bool expectedValue);
+
+void CheckStringScalar(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot, const char* expectedValue);
+
+void CheckKeyedUintScalar(const char* aName, const char* aKey, JSContext* aCx,
+ JS::HandleValue aSnapshot, uint32_t expectedValue);
+
+void CheckKeyedBoolScalar(const char* aName, const char* aKey, JSContext* aCx,
+ JS::HandleValue aSnapshot, bool expectedValue);
+
+void CheckNumberOfProperties(const char* aName, JSContext* aCx,
+ JS::HandleValue aSnapshot,
+ uint32_t expectedNumProperties);
+
+bool EventPresent(JSContext* aCx, const JS::RootedValue& aSnapshot,
+ const nsACString& aCategory, const nsACString& aMethod,
+ const nsACString& aObject);
+
+void GetEventSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ ProcessID aProcessType = ProcessID::Parent);
+
+void GetScalarsSnapshot(bool aKeyed, JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult,
+ ProcessID aProcessType = ProcessID::Parent);
+
+void GetAndClearHistogram(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry,
+ const nsACString& name, bool is_keyed);
+
+void GetProperty(JSContext* cx, const char* name, JS::HandleValue valueIn,
+ JS::MutableHandleValue valueOut);
+
+void GetElement(JSContext* cx, uint32_t index, JS::HandleValue valueIn,
+ JS::MutableHandleValue valueOut);
+
+void GetSnapshots(JSContext* cx, nsCOMPtr<nsITelemetry> mTelemetry,
+ const char* name, JS::MutableHandleValue valueOut,
+ bool is_keyed);
+
+void GetOriginSnapshot(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
+ bool aClear = false);
+
+void GetEncodedOriginStrings(
+ JSContext* aCx, const nsCString& aEncoding,
+ nsTArray<mozilla::Tuple<nsCString, nsCString>>& aPrioStrings);
+
+} // namespace TelemetryTestHelpers
+
+#endif
diff --git a/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp
new file mode 100644
index 0000000000..3e21c7378c
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TestCombinedStacks.cpp
@@ -0,0 +1,158 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+#include "other/CombinedStacks.h"
+#include "other/ProcessedStack.h"
+#include "nsPrintfCString.h"
+
+using namespace mozilla::Telemetry;
+using namespace TelemetryTestHelpers;
+
+TEST_F(TelemetryTestFixture, CombinedStacks) {
+ const size_t kMaxStacksKept = 10;
+ CombinedStacks stacks(kMaxStacksKept);
+
+ size_t iterations = kMaxStacksKept * 2;
+ for (size_t i = 0; i < iterations; ++i) {
+ ProcessedStack stack;
+ ProcessedStack::Frame frame = {static_cast<uint16_t>(i)};
+ const nsAutoString& name =
+ NS_ConvertUTF8toUTF16(nsPrintfCString("test%zu", i));
+ ProcessedStack::Module module = {name};
+
+ stack.AddFrame(frame);
+ stack.AddModule(module);
+ stacks.AddStack(stack);
+ }
+
+ ASSERT_EQ(stacks.GetStackCount(), kMaxStacksKept) << "Wrong number of stacks";
+ ASSERT_EQ(stacks.GetModuleCount(), kMaxStacksKept * 2)
+ << "Wrong number of modules";
+
+ for (size_t i = 0; i < kMaxStacksKept; ++i) {
+ ProcessedStack::Frame frame = stacks.GetStack(i)[0];
+ ASSERT_EQ(frame.mOffset, kMaxStacksKept + i)
+ << "Frame is not returning expected value";
+
+ ProcessedStack::Module module = stacks.GetModule(frame.mModIndex);
+ nsPrintfCString moduleName("test%hu", frame.mModIndex);
+ ASSERT_TRUE(module.mName.Equals(NS_ConvertUTF8toUTF16(moduleName)))
+ << "Module should have expected name";
+ }
+
+ for (size_t i = 0; i < kMaxStacksKept; ++i) {
+ stacks.RemoveStack(kMaxStacksKept - i - 1);
+ ASSERT_EQ(stacks.GetStackCount(), kMaxStacksKept - i - 1)
+ << "Stack should be removed";
+ }
+}
+
+template <int N>
+ProcessedStack MakeStack(const nsLiteralString (&aModules)[N],
+ const uintptr_t (&aOffsets)[N]) {
+ ProcessedStack stack;
+ for (int i = 0; i < N; ++i) {
+ ProcessedStack::Frame frame = {aOffsets[i]};
+ if (aModules[i].IsEmpty()) {
+ frame.mModIndex = std::numeric_limits<uint16_t>::max();
+ } else {
+ frame.mModIndex = stack.GetNumModules();
+ stack.AddModule(ProcessedStack::Module{aModules[i]});
+ }
+ stack.AddFrame(frame);
+ }
+ return stack;
+}
+
+TEST(CombinedStacks, Combine)
+{
+ const nsLiteralString moduleSet1[] = {u"mod1"_ns, u"mod2"_ns, u"base"_ns};
+ const nsLiteralString moduleSet2[] = {u"modX"_ns, u""_ns, u"modZ"_ns,
+ u"base"_ns};
+ // [0] 00 mod1+100
+ // 01 mod2+200
+ // 02 base+300
+ // [1] 00 mod1+1000
+ // 01 mod2+2000
+ // 02 base+3000
+ // [2] 00 modX+100
+ // 01 <no module>+200
+ // 02 modZ+300
+ // 03 base+400
+ // [3] 00 modX+1000
+ // 01 <no module>+3000
+ // 02 modZ+2000
+ // 03 base+4000
+ const ProcessedStack testStacks[] = {
+ MakeStack(moduleSet1, {100ul, 200ul, 300ul}),
+ MakeStack(moduleSet1, {1000ul, 2000ul, 3000ul}),
+ MakeStack(moduleSet2, {100ul, 200ul, 300ul, 400ul}),
+ MakeStack(moduleSet2, {1000ul, 2000ul, 3000ul, 4000ul}),
+ };
+
+ // combined1 <-- testStacks[0] + testStacks[1]
+ // combined2 <-- testStacks[2] + testStacks[3]
+ CombinedStacks combined1, combined2;
+ combined1.AddStack(testStacks[0]);
+ combined1.AddStack(testStacks[1]);
+ combined2.AddStack(testStacks[2]);
+ combined2.AddStack(testStacks[3]);
+
+ EXPECT_EQ(combined1.GetModuleCount(), mozilla::ArrayLength(moduleSet1));
+ EXPECT_EQ(combined1.GetStackCount(), 2u);
+ EXPECT_EQ(combined2.GetModuleCount(), mozilla::ArrayLength(moduleSet2) - 1);
+ EXPECT_EQ(combined2.GetStackCount(), 2u);
+
+ // combined1 <-- combined1 + combined2
+ combined1.AddStacks(combined2);
+
+ EXPECT_EQ(combined1.GetModuleCount(), 5u); // {mod1, mod2, modX, modZ, base}
+ EXPECT_EQ(combined1.GetStackCount(), mozilla::ArrayLength(testStacks));
+
+ for (size_t i = 0; i < combined1.GetStackCount(); ++i) {
+ const auto& expectedStack = testStacks[i];
+ const auto& actualStack = combined1.GetStack(i);
+ EXPECT_EQ(actualStack.size(), expectedStack.GetStackSize());
+ if (actualStack.size() != expectedStack.GetStackSize()) {
+ continue;
+ }
+
+ for (size_t j = 0; j < actualStack.size(); ++j) {
+ const auto& expectedFrame = expectedStack.GetFrame(j);
+ const auto& actualFrame = actualStack[j];
+
+ EXPECT_EQ(actualFrame.mOffset, expectedFrame.mOffset);
+
+ if (expectedFrame.mModIndex == std::numeric_limits<uint16_t>::max()) {
+ EXPECT_EQ(actualFrame.mModIndex, std::numeric_limits<uint16_t>::max());
+ } else {
+ EXPECT_EQ(combined1.GetModule(actualFrame.mModIndex),
+ expectedStack.GetModule(expectedFrame.mModIndex));
+ }
+ }
+ }
+
+ // Only testStacks[3] will be stored into oneStack
+ CombinedStacks oneStack(1);
+ oneStack.AddStacks(combined1);
+
+ EXPECT_EQ(oneStack.GetStackCount(), 1u);
+ EXPECT_EQ(oneStack.GetStack(0).size(), testStacks[3].GetStackSize());
+
+ for (size_t i = 0; i < oneStack.GetStack(0).size(); ++i) {
+ const auto& expectedFrame = testStacks[3].GetFrame(i);
+ const auto& actualFrame = oneStack.GetStack(0)[i];
+
+ EXPECT_EQ(actualFrame.mOffset, expectedFrame.mOffset);
+
+ if (expectedFrame.mModIndex == std::numeric_limits<uint16_t>::max()) {
+ EXPECT_EQ(actualFrame.mModIndex, std::numeric_limits<uint16_t>::max());
+ } else {
+ EXPECT_EQ(oneStack.GetModule(actualFrame.mModIndex),
+ testStacks[3].GetModule(expectedFrame.mModIndex));
+ }
+ }
+}
diff --git a/toolkit/components/telemetry/tests/gtest/TestCounters.cpp b/toolkit/components/telemetry/tests/gtest/TestCounters.cpp
new file mode 100644
index 0000000000..2950500c3f
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TestCounters.cpp
@@ -0,0 +1,173 @@
+/* vim:set ts=2 sw=2 sts=0 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+#include "js/Conversions.h"
+#include "mozilla/Telemetry.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+using namespace TelemetryTestHelpers;
+
+TEST_F(TelemetryTestFixture, AutoCounter) {
+ const uint32_t kExpectedValue = 100;
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ const char* telemetryTestCountName =
+ Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns,
+ false);
+
+ // Accumulate in the histogram
+ {
+ Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter;
+ autoCounter += kExpectedValue / 2;
+ }
+ // This counter should not accumulate since it does not go out of scope
+ Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter;
+ autoCounter += kExpectedValue;
+ // Accumulate a second time in the histogram
+ {
+ Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter;
+ autoCounter += kExpectedValue / 2;
+ }
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot,
+ false);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram);
+
+ // Get "sum" property from histogram
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
+
+ // Check that the "sum" stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture, AutoCounterUnderflow) {
+ const uint32_t kExpectedValue = 0;
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ const char* telemetryTestCountName =
+ Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns,
+ false);
+
+ // Accumulate in the histogram
+ {
+ Telemetry::AutoCounter<Telemetry::TELEMETRY_TEST_COUNT> autoCounter;
+ autoCounter += -1;
+ }
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot,
+ false);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram);
+
+ // Get "sum" property from histogram
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
+
+ // Check that the "sum" stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 42;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is supposed to return 0 when an underflow occurs.";
+}
+
+TEST_F(TelemetryTestFixture, RuntimeAutoCounter) {
+ const uint32_t kExpectedValue = 100;
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ const char* telemetryTestCountName =
+ Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns,
+ false);
+
+ // Accumulate in the histogram
+ {
+ Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT);
+ autoCounter += kExpectedValue / 2;
+ }
+ // This counter should not accumulate since it does not go out of scope
+ Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT);
+ autoCounter += kExpectedValue;
+ // Accumulate a second time in the histogram
+ {
+ Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT);
+ autoCounter += kExpectedValue / 2;
+ }
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot,
+ false);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram);
+
+ // Get "sum" property from histogram
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
+
+ // Check that the "sum" stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture, RuntimeAutoCounterUnderflow) {
+ const uint32_t kExpectedValue = 0;
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ const char* telemetryTestCountName =
+ Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns,
+ false);
+
+ // Accumulate in the histogram
+ {
+ Telemetry::RuntimeAutoCounter autoCounter(Telemetry::TELEMETRY_TEST_COUNT,
+ kExpectedValue);
+ autoCounter += -1;
+ }
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot,
+ false);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram);
+
+ // Get "sum" property from histogram
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
+
+ // Check that the "sum" stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 42;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is supposed to return 0 when an underflow occurs.";
+}
diff --git a/toolkit/components/telemetry/tests/gtest/TestEvents.cpp b/toolkit/components/telemetry/tests/gtest/TestEvents.cpp
new file mode 100644
index 0000000000..1298586c59
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TestEvents.cpp
@@ -0,0 +1,125 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "core/TelemetryEvent.h"
+#include "gtest/gtest.h"
+#include "js/Array.h" // JS::GetArrayLength
+#include "mozilla/Maybe.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+using namespace TelemetryTestHelpers;
+
+namespace mozilla {
+namespace Telemetry {
+
+struct EventExtraEntry {
+ nsCString key;
+ nsCString value;
+};
+
+} // namespace Telemetry
+} // namespace mozilla
+
+// Test that we can properly record events using the C++ API.
+TEST_F(TelemetryTestFixture, RecordEventNative) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get events from other tests.
+ Unused << mTelemetry->ClearEvents();
+
+ const nsLiteralCString category("telemetry.test");
+ const nsLiteralCString method("test1");
+ const nsLiteralCString method2("test2");
+ const nsLiteralCString object("object1");
+ const nsLiteralCString object2("object2");
+ const nsLiteralCString value("value");
+ const nsLiteralCString valueLong(
+ "this value is much too long and must be truncated to fit in the limit "
+ "which at time of writing was 80 bytes.");
+ const nsLiteralCString extraKey("key1");
+ const nsLiteralCString extraValue("extra value");
+ const nsLiteralCString extraValueLong(
+ "this extra value is much too long and must be truncated to fit in the "
+ "limit which at time of writing was 80 bytes.");
+
+ // Try recording before category's enabled.
+ Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test1_Object1,
+ Nothing(), Nothing());
+
+ // Ensure "telemetry.test" is enabled
+ Telemetry::SetEventRecordingEnabled(category, true);
+
+ // Try recording after it's enabled.
+ Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test2_Object1,
+ Nothing(), Nothing());
+
+ // Try recording with normal value, extra
+ CopyableTArray<EventExtraEntry> extra(
+ {EventExtraEntry{extraKey, extraValue}});
+ Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test1_Object2,
+ mozilla::Some(value), mozilla::Some(extra));
+
+ // Try recording with too-long value, extra
+ CopyableTArray<EventExtraEntry> longish(
+ {EventExtraEntry{extraKey, extraValueLong}});
+ Telemetry::RecordEvent(Telemetry::EventID::TelemetryTest_Test2_Object2,
+ mozilla::Some(valueLong), mozilla::Some(longish));
+
+ JS::RootedValue eventsSnapshot(cx.GetJSContext());
+ GetEventSnapshot(cx.GetJSContext(), &eventsSnapshot);
+
+ ASSERT_TRUE(!EventPresent(cx.GetJSContext(), eventsSnapshot, category, method,
+ object))
+ << "Test event must not be present when recorded before enabled.";
+ ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, category, method2,
+ object))
+ << "Test event must be present.";
+ ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, category, method,
+ object2))
+ << "Test event with value and extra must be present.";
+ ASSERT_TRUE(EventPresent(cx.GetJSContext(), eventsSnapshot, category, method2,
+ object2))
+ << "Test event with truncated value and extra must be present.";
+
+ // Ensure that the truncations happened appropriately.
+ JSContext* aCx = cx.GetJSContext();
+ JS::RootedObject arrayObj(aCx, &eventsSnapshot.toObject());
+ JS::Rooted<JS::Value> eventRecord(aCx);
+ ASSERT_TRUE(JS_GetElement(aCx, arrayObj, 2, &eventRecord))
+ << "Must be able to get record.";
+ JS::RootedObject recordArray(aCx, &eventRecord.toObject());
+ uint32_t recordLength;
+ ASSERT_TRUE(JS::GetArrayLength(aCx, recordArray, &recordLength))
+ << "Event record array must have length.";
+ ASSERT_TRUE(recordLength == 6)
+ << "Event record must have 6 elements.";
+
+ JS::Rooted<JS::Value> str(aCx);
+ nsAutoJSString jsStr;
+ // The value string is at index 4
+ ASSERT_TRUE(JS_GetElement(aCx, recordArray, 4, &str))
+ << "Must be able to get value.";
+ ASSERT_TRUE(jsStr.init(aCx, str))
+ << "Value must be able to be init'd to a jsstring.";
+ ASSERT_EQ(NS_ConvertUTF16toUTF8(jsStr).Length(), (uint32_t)80)
+ << "Value must have been truncated to 80 bytes.";
+
+ // Extra is at index 5
+ JS::Rooted<JS::Value> obj(aCx);
+ ASSERT_TRUE(JS_GetElement(aCx, recordArray, 5, &obj))
+ << "Must be able to get extra.";
+ JS::RootedObject extraObj(aCx, &obj.toObject());
+ JS::Rooted<JS::Value> extraVal(aCx);
+ ASSERT_TRUE(JS_GetProperty(aCx, extraObj, extraKey.get(), &extraVal))
+ << "Must be able to get the extra key's value.";
+ ASSERT_TRUE(jsStr.init(aCx, extraVal))
+ << "Extra must be able to be init'd to a jsstring.";
+ ASSERT_EQ(NS_ConvertUTF16toUTF8(jsStr).Length(), (uint32_t)80)
+ << "Extra must have been truncated to 80 bytes.";
+}
diff --git a/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp b/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp
new file mode 100644
index 0000000000..5bb22dc3b5
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TestHistograms.cpp
@@ -0,0 +1,891 @@
+/* vim:set ts=2 sw=2 sts=0 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+#include "js/Conversions.h"
+#include "mozilla/Telemetry.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+using namespace TelemetryTestHelpers;
+
+TEST_F(TelemetryTestFixture, AccumulateCountHistogram) {
+ const uint32_t kExpectedValue = 200;
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ const char* telemetryTestCountName =
+ Telemetry::GetHistogramName(Telemetry::TELEMETRY_TEST_COUNT);
+ ASSERT_STREQ(telemetryTestCountName, "TELEMETRY_TEST_COUNT")
+ << "The histogram name is wrong";
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns,
+ false);
+
+ // Accumulate in the histogram
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_COUNT, kExpectedValue / 2);
+ Telemetry::Accumulate("TELEMETRY_TEST_COUNT", kExpectedValue / 2);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, telemetryTestCountName, &snapshot,
+ false);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), telemetryTestCountName, snapshot, &histogram);
+
+ // Get "sum" property from histogram
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
+
+ // Check that the "sum" stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture, AccumulateKeyedCountHistogram) {
+ const uint32_t kExpectedValue = 100;
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_COUNT"_ns, true);
+
+ // Accumulate data in the provided key within the histogram
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_COUNT, "sample"_ns,
+ kExpectedValue);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT",
+ &snapshot, true);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", snapshot,
+ &histogram);
+
+ // Get "sample" property from histogram
+ JS::RootedValue expectedKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sample", histogram, &expectedKeyData);
+
+ // Get "sum" property from keyed data
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum);
+
+ // Check that the sum stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is not returning expected sum";
+}
+
+TEST_F(TelemetryTestFixture, TestKeyedKeysHistogram) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ JS::RootedValue testHistogram(cx.GetJSContext());
+ JS::RootedValue rval(cx.GetJSContext());
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_KEYS"_ns, true);
+
+ // Test the accumulation on both the allowed and unallowed keys, using
+ // the API that accepts histogram IDs.
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "not-allowed"_ns,
+ 1);
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "testkey"_ns, 0);
+ // Do the same, using the API that accepts the histogram name as a string.
+ Telemetry::Accumulate("TELEMETRY_TEST_KEYED_KEYS", "not-allowed"_ns, 1);
+ Telemetry::Accumulate("TELEMETRY_TEST_KEYED_KEYS", "CommonKey"_ns, 1);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_KEYS",
+ &snapshot, true);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_KEYS", snapshot,
+ &histogram);
+
+ // Get "testkey" property from histogram and check that it stores the correct
+ // data.
+ JS::RootedValue expectedKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "testkey", histogram, &expectedKeyData);
+ ASSERT_TRUE(!expectedKeyData.isUndefined())
+ << "Cannot find the expected key in the histogram data";
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum);
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, 0U)
+ << "The histogram is not returning expected sum for 'testkey'";
+
+ // Do the same for the "CommonKey" property.
+ GetProperty(cx.GetJSContext(), "CommonKey", histogram, &expectedKeyData);
+ ASSERT_TRUE(!expectedKeyData.isUndefined())
+ << "Cannot find the expected key in the histogram data";
+ GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum);
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, 1U)
+ << "The histogram is not returning expected sum for 'CommonKey'";
+
+ GetProperty(cx.GetJSContext(), "not-allowed", histogram, &expectedKeyData);
+ ASSERT_TRUE(expectedKeyData.isUndefined())
+ << "Unallowed keys must not be recorded in the histogram data";
+
+ // The 'not-allowed' key accumulation for 'TELEMETRY_TESTED_KEYED_KEYS' was
+ // attemtped twice, so we expect the count of
+ // 'telemetry.accumulate_unknown_histogram_keys' to be 2
+ const uint32_t expectedAccumulateUnknownCount = 2;
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckKeyedUintScalar("telemetry.accumulate_unknown_histogram_keys",
+ "TELEMETRY_TEST_KEYED_KEYS", cx.GetJSContext(),
+ scalarsSnapshot, expectedAccumulateUnknownCount);
+}
+
+TEST_F(TelemetryTestFixture, AccumulateCategoricalHistogram) {
+ const uint32_t kExpectedValue = 2;
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_CATEGORICAL"_ns, false);
+
+ // Accumulate one unit into the categorical histogram with label
+ // Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel);
+
+ // Accumulate another unit into the same categorical histogram using a string
+ // label
+ Telemetry::AccumulateCategorical(Telemetry::TELEMETRY_TEST_CATEGORICAL,
+ "CommonLabel"_ns);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL",
+ &snapshot, false);
+
+ // Get our histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot,
+ &histogram);
+
+ // Get values object from histogram. Each entry in the object maps to a label
+ // in the histogram.
+ JS::RootedValue values(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", histogram, &values);
+
+ // Get the value for the label we care about
+ JS::RootedValue value(cx.GetJSContext());
+ GetElement(cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
+ values, &value);
+
+ // Check that the value stored in the histogram matches with |kExpectedValue|
+ uint32_t uValue = 0;
+ JS::ToUint32(cx.GetJSContext(), value, &uValue);
+ ASSERT_EQ(uValue, kExpectedValue)
+ << "The histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture, AccumulateKeyedCategoricalHistogram) {
+ const uint32_t kSampleExpectedValue = 2;
+ const uint32_t kOtherSampleExpectedValue = 1;
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_CATEGORICAL"_ns, true);
+
+ // Accumulate one unit into the categorical histogram with label
+ // Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel
+ Telemetry::AccumulateCategoricalKeyed(
+ "sample"_ns,
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel);
+ // Accumulate another unit into the same categorical histogram
+ Telemetry::AccumulateCategoricalKeyed(
+ "sample"_ns,
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel);
+ // Accumulate another unit into a different categorical histogram
+ Telemetry::AccumulateCategoricalKeyed(
+ "other-sample"_ns,
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_CATEGORICAL", &snapshot, true);
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_CATEGORICAL", snapshot,
+ &histogram);
+
+ // Check that the sample histogram contains the values we expect
+ JS::RootedValue sample(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sample", histogram, &sample);
+ // Get values object from sample. Each entry in the object maps to a label in
+ // the histogram.
+ JS::RootedValue sampleValues(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", sample, &sampleValues);
+ // Get the value for the label we care about
+ JS::RootedValue sampleValue(cx.GetJSContext());
+ GetElement(
+ cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel),
+ sampleValues, &sampleValue);
+ // Check that the value stored in the histogram matches with
+ // |kSampleExpectedValue|
+ uint32_t uSampleValue = 0;
+ JS::ToUint32(cx.GetJSContext(), sampleValue, &uSampleValue);
+ ASSERT_EQ(uSampleValue, kSampleExpectedValue)
+ << "The sample histogram is not returning expected value";
+
+ // Check that the other-sample histogram contains the values we expect
+ JS::RootedValue otherSample(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "other-sample", histogram, &otherSample);
+ // Get values object from the other-sample. Each entry in the object maps to a
+ // label in the histogram.
+ JS::RootedValue otherValues(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", otherSample, &otherValues);
+ // Get the value for the label we care about
+ JS::RootedValue otherValue(cx.GetJSContext());
+ GetElement(
+ cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel),
+ otherValues, &otherValue);
+ // Check that the value stored in the histogram matches with
+ // |kOtherSampleExpectedValue|
+ uint32_t uOtherValue = 0;
+ JS::ToUint32(cx.GetJSContext(), otherValue, &uOtherValue);
+ ASSERT_EQ(uOtherValue, kOtherSampleExpectedValue)
+ << "The other-sample histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture, AccumulateCountHistogram_MultipleSamples) {
+ nsTArray<uint32_t> samples({4, 4, 4});
+ const uint32_t kExpectedSum = 12;
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns,
+ false);
+
+ // Accumulate in histogram
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_COUNT, samples);
+
+ // Get a snapshot of all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT", &snapshot,
+ false);
+
+ // Get histogram from snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_COUNT", snapshot, &histogram);
+
+ // Get "sum" from histogram
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
+
+ // Check that sum matches with aValue
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedSum)
+ << "This histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture, AccumulateLinearHistogram_MultipleSamples) {
+ nsTArray<uint32_t> samples({4, 4, 4});
+ const uint32_t kExpectedCount = 3;
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_LINEAR"_ns, false);
+
+ // Accumulate in the histogram
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_LINEAR, samples);
+
+ // Get a snapshot of all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_LINEAR",
+ &snapshot, false);
+
+ // Get histogram from snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_LINEAR", snapshot, &histogram);
+
+ // Get "values" object from histogram
+ JS::RootedValue values(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", histogram, &values);
+
+ // Index 0 is only for values less than 'low'. Values within range start at
+ // index 1
+ JS::RootedValue count(cx.GetJSContext());
+ const uint32_t index = 1;
+ GetElement(cx.GetJSContext(), index, values, &count);
+
+ // Check that this count matches with nSamples
+ uint32_t uCount = 0;
+ JS::ToUint32(cx.GetJSContext(), count, &uCount);
+ ASSERT_EQ(uCount, kExpectedCount)
+ << "The histogram did not accumulate the correct number of values";
+}
+
+TEST_F(TelemetryTestFixture, AccumulateLinearHistogram_DifferentSamples) {
+ nsTArray<uint32_t> samples(
+ {4, 8, 2147483646, uint32_t(INT_MAX) + 1, UINT32_MAX});
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ mTelemetry->ClearScalars();
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_LINEAR"_ns, false);
+
+ // Accumulate in histogram
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_LINEAR, samples);
+
+ // Get a snapshot of all histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_LINEAR",
+ &snapshot, false);
+
+ // Get histogram from snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_LINEAR", snapshot, &histogram);
+
+ // Get values object from histogram
+ JS::RootedValue values(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", histogram, &values);
+
+ // Get values in first and last buckets
+ JS::RootedValue countFirst(cx.GetJSContext());
+ JS::RootedValue countLast(cx.GetJSContext());
+ const uint32_t firstIndex = 1;
+ // Buckets are indexed by their start value
+ const uint32_t lastIndex = INT32_MAX - 1;
+ GetElement(cx.GetJSContext(), firstIndex, values, &countFirst);
+ GetElement(cx.GetJSContext(), lastIndex, values, &countLast);
+
+ // Check that the values match
+ uint32_t uCountFirst = 0;
+ uint32_t uCountLast = 0;
+ JS::ToUint32(cx.GetJSContext(), countFirst, &uCountFirst);
+ JS::ToUint32(cx.GetJSContext(), countLast, &uCountLast);
+
+ const uint32_t kExpectedCountFirst = 2;
+ // We expect 2147483646 to be in the last bucket, as well the two samples
+ // above 2^31 (prior to bug 1438335, values between INT_MAX and UINT32_MAX
+ // would end up as 0s)
+ const uint32_t kExpectedCountLast = 3;
+ ASSERT_EQ(uCountFirst, kExpectedCountFirst)
+ << "The first bucket did not accumulate the correct number of values";
+ ASSERT_EQ(uCountLast, kExpectedCountLast)
+ << "The last bucket did not accumulate the correct number of values";
+
+ // We accumulated two values that had to be clamped. We expect the count in
+ // 'telemetry.accumulate_clamped_values' to be 2 (only one storage).
+ const uint32_t expectedAccumulateClampedCount = 2;
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckKeyedUintScalar("telemetry.accumulate_clamped_values",
+ "TELEMETRY_TEST_LINEAR", cx.GetJSContext(),
+ scalarsSnapshot, expectedAccumulateClampedCount);
+}
+
+TEST_F(TelemetryTestFixture, AccumulateKeyedCountHistogram_MultipleSamples) {
+ const nsTArray<uint32_t> samples({5, 10, 15});
+ const uint32_t kExpectedSum = 5 + 10 + 15;
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_COUNT"_ns, true);
+
+ // Accumulate data in the provided key within the histogram
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_COUNT, "sample"_ns,
+ samples);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT",
+ &snapshot, true);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", snapshot,
+ &histogram);
+
+ // Get "sample" property from histogram
+ JS::RootedValue expectedKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sample", histogram, &expectedKeyData);
+
+ // Get "sum" property from keyed data
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum);
+
+ // Check that the sum stored in the histogram matches with |kExpectedSum|
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedSum)
+ << "The histogram is not returning expected sum";
+}
+
+TEST_F(TelemetryTestFixture, TestKeyedLinearHistogram_MultipleSamples) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ mTelemetry->ClearScalars();
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_LINEAR"_ns, true);
+
+ const nsTArray<uint32_t> samples({1, 5, 250000, UINT_MAX});
+ // Test the accumulation on the key 'testkey', using
+ // the API that accepts histogram IDs.
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_LINEAR, "testkey"_ns,
+ samples);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_LINEAR",
+ &snapshot, true);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_LINEAR", snapshot,
+ &histogram);
+
+ // Get "testkey" property from histogram.
+ JS::RootedValue expectedKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "testkey", histogram, &expectedKeyData);
+ ASSERT_TRUE(!expectedKeyData.isUndefined())
+ << "Cannot find the expected key in the histogram data";
+
+ // Get values object from 'testkey' histogram.
+ JS::RootedValue values(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", expectedKeyData, &values);
+
+ // Get values in first and last buckets.
+ JS::RootedValue countFirst(cx.GetJSContext());
+ JS::RootedValue countLast(cx.GetJSContext());
+ const uint32_t firstIndex = 1;
+ // Buckets are indexed by their start value
+ const uint32_t lastIndex = 250000;
+ GetElement(cx.GetJSContext(), firstIndex, values, &countFirst);
+ GetElement(cx.GetJSContext(), lastIndex, values, &countLast);
+
+ // Check that the values match.
+ uint32_t uCountFirst = 0;
+ uint32_t uCountLast = 0;
+ JS::ToUint32(cx.GetJSContext(), countFirst, &uCountFirst);
+ JS::ToUint32(cx.GetJSContext(), countLast, &uCountLast);
+
+ const uint32_t kExpectedCountFirst = 2;
+ const uint32_t kExpectedCountLast = 2;
+ ASSERT_EQ(uCountFirst, kExpectedCountFirst)
+ << "The first bucket did not accumulate the correct number of values for "
+ "key 'testkey'";
+ ASSERT_EQ(uCountLast, kExpectedCountLast)
+ << "The last bucket did not accumulate the correct number of values for "
+ "key 'testkey'";
+
+ // We accumulated one keyed values that had to be clamped. We expect the
+ // count in 'telemetry.accumulate_clamped_values' to be 1
+ const uint32_t expectedAccumulateClampedCount = 1;
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckKeyedUintScalar("telemetry.accumulate_clamped_values",
+ "TELEMETRY_TEST_KEYED_LINEAR", cx.GetJSContext(),
+ scalarsSnapshot, expectedAccumulateClampedCount);
+}
+
+TEST_F(TelemetryTestFixture, TestKeyedKeysHistogram_MultipleSamples) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ mTelemetry->ClearScalars();
+ const nsTArray<uint32_t> samples({false, false, true, 32, true});
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_KEYS"_ns, true);
+
+ // Test the accumulation on both the allowed and unallowed keys, using
+ // the API that accepts histogram IDs.
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "not-allowed"_ns,
+ samples);
+ Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_KEYS, "testkey"_ns,
+ samples);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_KEYS",
+ &snapshot, true);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_KEYS", snapshot,
+ &histogram);
+
+ // Get "testkey" property from histogram and check that it stores the correct
+ // data.
+ JS::RootedValue testKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "testkey", histogram, &testKeyData);
+ ASSERT_TRUE(!testKeyData.isUndefined())
+ << "Cannot find the key 'testkey' in the histogram data";
+
+ JS::RootedValue values(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", testKeyData, &values);
+
+ // Get values in buckets 0,1,2
+ const uint32_t falseIndex = 0;
+ const uint32_t trueIndex = 1;
+ const uint32_t otherIndex = 2;
+
+ JS::RootedValue countFalse(cx.GetJSContext());
+ JS::RootedValue countTrue(cx.GetJSContext());
+ JS::RootedValue countOther(cx.GetJSContext());
+
+ GetElement(cx.GetJSContext(), falseIndex, values, &countFalse);
+ GetElement(cx.GetJSContext(), trueIndex, values, &countTrue);
+ GetElement(cx.GetJSContext(), otherIndex, values, &countOther);
+
+ uint32_t uCountFalse = 0;
+ uint32_t uCountTrue = 0;
+ uint32_t uCountOther = 0;
+ JS::ToUint32(cx.GetJSContext(), countFalse, &uCountFalse);
+ JS::ToUint32(cx.GetJSContext(), countTrue, &uCountTrue);
+ JS::ToUint32(cx.GetJSContext(), countOther, &uCountOther);
+
+ const uint32_t kExpectedCountFalse = 2;
+ const uint32_t kExpectedCountTrue = 3;
+ const uint32_t kExpectedCountOther = 0;
+
+ ASSERT_EQ(uCountFalse, kExpectedCountFalse)
+ << "The histogram did not accumulate the correct number of 'false' "
+ "booleans for key 'testkey'";
+ ASSERT_EQ(uCountTrue, kExpectedCountTrue)
+ << "The histogram did not accumulate the correct number of 'true' "
+ "booleans for key 'testkey'";
+ ASSERT_EQ(uCountOther, kExpectedCountOther)
+ << "The histogram did not accumulate the correct number of undefined "
+ "values for key 'testkey'";
+
+ // Here we check that we are not accumulating to a different (but still
+ // 'allowed') key. Get "CommonKey" property from histogram and check that it
+ // has no data. Since we accumulated no data to it, commonKeyData should be
+ // undefined.
+ JS::RootedValue commonKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "CommonKey", histogram, &commonKeyData);
+ ASSERT_TRUE(commonKeyData.isUndefined())
+ << "Found data in key 'CommonKey' even though we accumulated no data to "
+ "it";
+
+ // Here we check that our function does not allow accumulation into unallowed
+ // keys. Get 'not-allowed' property from histogram and check that this also
+ // has no data. This should contain no data because this key is not allowed.
+ JS::RootedValue notAllowedKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "not-allowed", histogram, &notAllowedKeyData);
+ ASSERT_TRUE(notAllowedKeyData.isUndefined())
+ << "Found data in key 'not-allowed' even though accumuling data to it is "
+ "not allowed";
+
+ // The 'not-allowed' key accumulation for 'TELEMETRY_TESTED_KEYED_KEYS' was
+ // attemtped once, so we expect the count of
+ // 'telemetry.accumulate_unknown_histogram_keys' to be 1
+ const uint32_t expectedAccumulateUnknownCount = 1;
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckKeyedUintScalar("telemetry.accumulate_unknown_histogram_keys",
+ "TELEMETRY_TEST_KEYED_KEYS", cx.GetJSContext(),
+ scalarsSnapshot, expectedAccumulateUnknownCount);
+}
+
+TEST_F(TelemetryTestFixture,
+ AccumulateCategoricalHistogram_MultipleStringLabels) {
+ const uint32_t kExpectedValue = 2;
+ const nsTArray<nsCString> labels({"CommonLabel"_ns, "CommonLabel"_ns});
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_CATEGORICAL"_ns, false);
+
+ // Accumulate the units into a categorical histogram using a string label
+ Telemetry::AccumulateCategorical(Telemetry::TELEMETRY_TEST_CATEGORICAL,
+ labels);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL",
+ &snapshot, false);
+
+ // Get our histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot,
+ &histogram);
+
+ // Get values object from histogram. Each entry in the object maps to a label
+ // in the histogram.
+ JS::RootedValue values(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", histogram, &values);
+
+ // Get the value for the label we care about
+ JS::RootedValue value(cx.GetJSContext());
+ GetElement(cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
+ values, &value);
+
+ // Check that the value stored in the histogram matches with |kExpectedValue|
+ uint32_t uValue = 0;
+ JS::ToUint32(cx.GetJSContext(), value, &uValue);
+ ASSERT_EQ(uValue, kExpectedValue)
+ << "The histogram is not returning expected value";
+
+ // Now we check for no accumulation when a bad label is present in the array.
+ //
+ // The 'values' property is not initialized unless data is accumulated so
+ // keeping another test to check for this case alone is wasteful as we will
+ // have to accumulate some data anyway.
+
+ const nsTArray<nsCString> badLabelArray({"CommonLabel"_ns, "BadLabel"_ns});
+
+ // Try to accumulate the array into the histogram.
+ Telemetry::AccumulateCategorical(Telemetry::TELEMETRY_TEST_CATEGORICAL,
+ badLabelArray);
+
+ // Get snapshot of all the histograms
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL",
+ &snapshot, false);
+
+ // Get our histogram from the snapshot
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot,
+ &histogram);
+
+ // Get values array from histogram
+ GetProperty(cx.GetJSContext(), "values", histogram, &values);
+
+ // Get the value for the label we care about
+ GetElement(cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
+ values, &value);
+
+ // Check that the value stored in the histogram matches with |kExpectedValue|
+ uValue = 0;
+ JS::ToUint32(cx.GetJSContext(), value, &uValue);
+ ASSERT_EQ(uValue, kExpectedValue)
+ << "The histogram accumulated data when it should not have";
+}
+
+TEST_F(TelemetryTestFixture,
+ AccumulateCategoricalHistogram_MultipleEnumValues) {
+ const uint32_t kExpectedValue = 2;
+ const nsTArray<Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL> enumLabels(
+ {Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel,
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel});
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_CATEGORICAL"_ns, false);
+
+ // Accumulate the units into a categorical histogram using the enumLabels
+ // array
+ Telemetry::AccumulateCategorical<
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL>(enumLabels);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_CATEGORICAL",
+ &snapshot, false);
+
+ // Get our histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_CATEGORICAL", snapshot,
+ &histogram);
+
+ // Get values object from histogram. Each entry in the object maps to a label
+ // in the histogram.
+ JS::RootedValue values(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", histogram, &values);
+
+ // Get the value for the label we care about
+ JS::RootedValue value(cx.GetJSContext());
+ GetElement(cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_CATEGORICAL::CommonLabel),
+ values, &value);
+
+ // Check that the value stored in the histogram matches with |kExpectedValue|
+ uint32_t uValue = 0;
+ JS::ToUint32(cx.GetJSContext(), value, &uValue);
+ ASSERT_EQ(uValue, kExpectedValue)
+ << "The histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture,
+ AccumulateKeyedCategoricalHistogram_MultipleEnumValues) {
+ const uint32_t kExpectedCommonLabel = 2;
+ const uint32_t kExpectedLabel2 = 1;
+ const nsTArray<Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL> enumLabels(
+ {Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel,
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel,
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::Label2});
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_CATEGORICAL"_ns, true);
+
+ // Accumulate the array into the categorical keyed histogram
+ Telemetry::AccumulateCategoricalKeyed("sampleKey"_ns, enumLabels);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_CATEGORICAL", &snapshot, true);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_CATEGORICAL", snapshot,
+ &histogram);
+
+ // Check that the sampleKey histogram contains correct number of CommonLabel
+ // samples
+ JS::RootedValue sample(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sampleKey", histogram, &sample);
+
+ // Get values object from the sample. Each entry in the object maps to a label
+ // in the histogram.
+ JS::RootedValue sampleKeyValues(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "values", sample, &sampleKeyValues);
+
+ // Get the count of CommonLabel
+ JS::RootedValue commonLabelValue(cx.GetJSContext());
+ GetElement(
+ cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::CommonLabel),
+ sampleKeyValues, &commonLabelValue);
+
+ // Check that the value stored in the histogram matches with
+ // |kExpectedCommonLabel|
+ uint32_t uCommonLabelValue = 0;
+ JS::ToUint32(cx.GetJSContext(), commonLabelValue, &uCommonLabelValue);
+ ASSERT_EQ(uCommonLabelValue, kExpectedCommonLabel)
+ << "The sampleKey histogram did not accumulate the correct number of "
+ "CommonLabel samples";
+
+ // Check that the sampleKey histogram contains the correct number of Label2
+ // values Get the count of Label2
+ JS::RootedValue label2Value(cx.GetJSContext());
+ GetElement(cx.GetJSContext(),
+ static_cast<uint32_t>(
+ Telemetry::LABELS_TELEMETRY_TEST_KEYED_CATEGORICAL::Label2),
+ sampleKeyValues, &label2Value);
+
+ // Check that the value stored in the histogram matches with |kExpectedLabel2|
+ uint32_t uLabel2Value = 0;
+ JS::ToUint32(cx.GetJSContext(), label2Value, &uLabel2Value);
+ ASSERT_EQ(uLabel2Value, kExpectedLabel2)
+ << "The sampleKey histogram did not accumulate the correct number of "
+ "Label2 samples";
+}
+
+TEST_F(TelemetryTestFixture, AccumulateTimeDelta) {
+ const uint32_t kExpectedValue = 100;
+ const TimeStamp start = TimeStamp::Now();
+ const TimeDuration delta = TimeDuration::FromMilliseconds(50);
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT"_ns,
+ false);
+
+ // Accumulate in the histogram
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start - delta,
+ start);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start - delta,
+ start);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start, start);
+
+ // end > start timestamp gives zero contribution
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_COUNT, start + delta,
+ start);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_COUNT", &snapshot,
+ false);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_COUNT", snapshot, &histogram);
+
+ // Get "sum" property from histogram
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", histogram, &sum);
+
+ // Check that the "sum" stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is not returning expected value";
+}
+
+TEST_F(TelemetryTestFixture, AccumulateKeyedTimeDelta) {
+ const uint32_t kExpectedValue = 100;
+ const TimeStamp start = TimeStamp::Now();
+ const TimeDuration delta = TimeDuration::FromMilliseconds(50);
+
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+ "TELEMETRY_TEST_KEYED_COUNT"_ns, true);
+
+ // Accumulate time delta in the provided key within the histogram
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT,
+ "sample"_ns, start - delta, start);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT,
+ "sample"_ns, start - delta, start);
+
+ // end > start timestamp gives zero contribution
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT,
+ "sample"_ns, start + delta, start);
+
+ Telemetry::AccumulateTimeDelta(Telemetry::TELEMETRY_TEST_KEYED_COUNT,
+ "sample"_ns, start, start);
+
+ // Get a snapshot for all the histograms
+ JS::RootedValue snapshot(cx.GetJSContext());
+ GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT",
+ &snapshot, true);
+
+ // Get the histogram from the snapshot
+ JS::RootedValue histogram(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", snapshot,
+ &histogram);
+
+ // Get "sample" property from histogram
+ JS::RootedValue expectedKeyData(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sample", histogram, &expectedKeyData);
+
+ // Get "sum" property from keyed data
+ JS::RootedValue sum(cx.GetJSContext());
+ GetProperty(cx.GetJSContext(), "sum", expectedKeyData, &sum);
+
+ // Check that the sum stored in the histogram matches with |kExpectedValue|
+ uint32_t uSum = 0;
+ JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+ ASSERT_EQ(uSum, kExpectedValue)
+ << "The histogram is not returning expected sum";
+}
diff --git a/toolkit/components/telemetry/tests/gtest/TestOrigins.cpp b/toolkit/components/telemetry/tests/gtest/TestOrigins.cpp
new file mode 100644
index 0000000000..b52391e869
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TestOrigins.cpp
@@ -0,0 +1,290 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "core/TelemetryOrigin.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "mozilla/ContentBlockingLog.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "nsIObserverService.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+using namespace TelemetryTestHelpers;
+using mozilla::Telemetry::OriginMetricID;
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::StrEq;
+
+constexpr auto kTelemetryTest1Metric = "telemetry.test_test1"_ns;
+
+constexpr auto kDoubleclickOrigin = "doubleclick.net"_ns;
+constexpr auto kDoubleclickOriginHash =
+ "uXNT1PzjAVau8b402OMAIGDejKbiXfQX5iXvPASfO/s="_ns;
+constexpr auto kFacebookOrigin = "fb.com"_ns;
+constexpr auto kUnknownOrigin1 =
+ "this origin isn't known to Origin Telemetry"_ns;
+constexpr auto kUnknownOrigin2 = "neither is this one"_ns;
+
+// Properly prepare the prio prefs
+// (Sourced from PrioEncoder.cpp from when it was being prototyped)
+constexpr auto prioKeyA =
+ "35AC1C7576C7C6EDD7FED6BCFC337B34D48CB4EE45C86BEEFB40BD8875707733"_ns;
+constexpr auto prioKeyB =
+ "26E6674E65425B823F1F1D5F96E3BB3EF9E406EC7FBA7DEF8B08A35DD135AF50"_ns;
+
+// Test that we can properly record origin stuff using the C++ API.
+TEST_F(TelemetryTestFixture, RecordOrigin) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ JSContext* aCx = cx.GetJSContext();
+
+ Unused << mTelemetry->ClearOrigins();
+
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ mozilla::ContentBlockingLog::kDummyOriginHash);
+
+ JS::RootedValue originSnapshot(aCx);
+ GetOriginSnapshot(aCx, &originSnapshot);
+
+ ASSERT_FALSE(originSnapshot.isNullOrUndefined())
+ << "Origin snapshot must not be null/undefined.";
+
+ JS::RootedValue origins(aCx);
+ JS::RootedObject snapshotObj(aCx, &originSnapshot.toObject());
+ ASSERT_TRUE(
+ JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
+ << "telemetry.test_test1 must be in the snapshot.";
+
+ JS::RootedObject originsObj(aCx, &origins.toObject());
+ JS::RootedValue count(aCx);
+ ASSERT_TRUE(JS_GetProperty(
+ aCx, originsObj, mozilla::ContentBlockingLog::kDummyOriginHash.get(),
+ &count));
+ ASSERT_TRUE(count.isInt32() && count.toInt32() == 1)
+ << "Must have recorded the origin exactly once.";
+
+ // Now test that the snapshot didn't clear things out.
+ GetOriginSnapshot(aCx, &originSnapshot);
+ ASSERT_FALSE(originSnapshot.isNullOrUndefined());
+ JS::RootedObject unemptySnapshotObj(aCx, &originSnapshot.toObject());
+ JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
+ ASSERT_TRUE(JS_Enumerate(aCx, unemptySnapshotObj, &ids));
+ ASSERT_GE(ids.length(), (unsigned)0) << "Returned object must not be empty.";
+}
+
+TEST_F(TelemetryTestFixture, RecordOriginTwiceAndClear) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ JSContext* aCx = cx.GetJSContext();
+
+ Unused << mTelemetry->ClearOrigins();
+
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ kDoubleclickOrigin);
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ kDoubleclickOrigin);
+
+ JS::RootedValue originSnapshot(aCx);
+ GetOriginSnapshot(aCx, &originSnapshot, true /* aClear */);
+
+ ASSERT_FALSE(originSnapshot.isNullOrUndefined())
+ << "Origin snapshot must not be null/undefined.";
+
+ JS::RootedValue origins(aCx);
+ JS::RootedObject snapshotObj(aCx, &originSnapshot.toObject());
+ ASSERT_TRUE(
+ JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
+ << "telemetry.test_test1 must be in the snapshot.";
+
+ JS::RootedObject originsObj(aCx, &origins.toObject());
+ JS::RootedValue count(aCx);
+ ASSERT_TRUE(
+ JS_GetProperty(aCx, originsObj, kDoubleclickOrigin.get(), &count));
+ ASSERT_TRUE(count.isInt32() && count.toInt32() == 2)
+ << "Must have recorded the origin exactly twice.";
+
+ // Now check that snapshotting with clear actually cleared it.
+ GetOriginSnapshot(aCx, &originSnapshot);
+ ASSERT_FALSE(originSnapshot.isNullOrUndefined());
+ JS::RootedObject emptySnapshotObj(aCx, &originSnapshot.toObject());
+ JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
+ ASSERT_TRUE(JS_Enumerate(aCx, emptySnapshotObj, &ids));
+ ASSERT_EQ(ids.length(), (unsigned)0) << "Returned object must be empty.";
+}
+
+TEST_F(TelemetryTestFixture, RecordOriginTwiceMixed) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ JSContext* aCx = cx.GetJSContext();
+
+ Unused << mTelemetry->ClearOrigins();
+
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ kDoubleclickOrigin);
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ kDoubleclickOriginHash);
+
+ Preferences::SetCString("prio.publicKeyA", prioKeyA);
+ Preferences::SetCString("prio.publicKeyB", prioKeyB);
+
+ nsTArray<Tuple<nsCString, nsCString>> encodedStrings;
+ GetEncodedOriginStrings(aCx, kTelemetryTest1Metric + "-%u"_ns,
+ encodedStrings);
+ ASSERT_EQ(2 * TelemetryOrigin::SizeOfPrioDatasPerMetric(),
+ encodedStrings.Length());
+
+ JS::RootedValue originSnapshot(aCx);
+ GetOriginSnapshot(aCx, &originSnapshot, true /* aClear */);
+
+ ASSERT_FALSE(originSnapshot.isNullOrUndefined())
+ << "Origin snapshot must not be null/undefined.";
+
+ JS::RootedValue origins(aCx);
+ JS::RootedObject snapshotObj(aCx, &originSnapshot.toObject());
+ ASSERT_TRUE(
+ JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
+ << "telemetry.test_test1 must be in the snapshot.";
+
+ JS::RootedObject originsObj(aCx, &origins.toObject());
+ JS::RootedValue count(aCx);
+ ASSERT_TRUE(
+ JS_GetProperty(aCx, originsObj, kDoubleclickOrigin.get(), &count));
+ ASSERT_TRUE(count.isInt32() && count.toInt32() == 2)
+ << "Must have recorded the origin exactly twice.";
+}
+
+TEST_F(TelemetryTestFixture, RecordUnknownOrigin) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ JSContext* aCx = cx.GetJSContext();
+
+ Unused << mTelemetry->ClearOrigins();
+
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1, kUnknownOrigin1);
+
+ JS::RootedValue originSnapshot(aCx);
+ GetOriginSnapshot(aCx, &originSnapshot);
+
+ ASSERT_FALSE(originSnapshot.isNullOrUndefined())
+ << "Origin snapshot must not be null/undefined.";
+
+ JS::RootedValue origins(aCx);
+ JS::RootedObject snapshotObj(aCx, &originSnapshot.toObject());
+ ASSERT_TRUE(
+ JS_GetProperty(aCx, snapshotObj, kTelemetryTest1Metric.get(), &origins))
+ << "telemetry.test_test1 must be in the snapshot.";
+
+ JS::RootedObject originsObj(aCx, &origins.toObject());
+ JS::RootedValue count(aCx);
+ ASSERT_TRUE(JS_GetProperty(aCx, originsObj, "__UNKNOWN__", &count));
+ ASSERT_TRUE(count.isInt32() && count.toInt32() == 1)
+ << "Must have recorded the unknown origin exactly once.";
+
+ // Record a second, different unknown origin and ensure only one is stored.
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1, kUnknownOrigin2);
+
+ GetOriginSnapshot(aCx, &originSnapshot);
+
+ ASSERT_FALSE(originSnapshot.isNullOrUndefined())
+ << "Origin snapshot must not be null/undefined.";
+
+ JS::RootedObject snapshotObj2(aCx, &originSnapshot.toObject());
+ ASSERT_TRUE(
+ JS_GetProperty(aCx, snapshotObj2, kTelemetryTest1Metric.get(), &origins))
+ << "telemetry.test_test1 must be in the snapshot.";
+
+ JS::RootedObject originsObj2(aCx, &origins.toObject());
+ JS::RootedValue count2(aCx);
+ ASSERT_TRUE(JS_GetProperty(aCx, originsObj2, "__UNKNOWN__", &count2));
+ ASSERT_TRUE(count2.isInt32() && count2.toInt32() == 1)
+ << "Must have recorded the unknown origin exactly once.";
+}
+
+TEST_F(TelemetryTestFixture, EncodedSnapshot) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ JSContext* aCx = cx.GetJSContext();
+
+ Unused << mTelemetry->ClearOrigins();
+
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ kDoubleclickOrigin);
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1, kUnknownOrigin1);
+
+ Preferences::SetCString("prio.publicKeyA", prioKeyA);
+ Preferences::SetCString("prio.publicKeyB", prioKeyB);
+
+ nsTArray<Tuple<nsCString, nsCString>> firstStrings;
+ GetEncodedOriginStrings(aCx, kTelemetryTest1Metric + "-%u"_ns, firstStrings);
+
+ // Now snapshot a second time and ensure the encoded payloads change.
+ nsTArray<Tuple<nsCString, nsCString>> secondStrings;
+ GetEncodedOriginStrings(aCx, kTelemetryTest1Metric + "-%u"_ns, secondStrings);
+
+ const auto sizeOfPrioDatasPerMetric =
+ TelemetryOrigin::SizeOfPrioDatasPerMetric();
+ ASSERT_EQ(sizeOfPrioDatasPerMetric, firstStrings.Length());
+ ASSERT_EQ(sizeOfPrioDatasPerMetric, secondStrings.Length());
+
+ for (size_t i = 0; i < sizeOfPrioDatasPerMetric; ++i) {
+ auto& aStr = Get<0>(firstStrings[i]);
+ auto& bStr = Get<1>(firstStrings[i]);
+ auto& secondAStr = Get<0>(secondStrings[i]);
+ auto& secondBStr = Get<1>(secondStrings[i]);
+
+ ASSERT_TRUE(aStr != secondAStr)
+ << "aStr (" << aStr.get() << ") must not equal secondAStr ("
+ << secondAStr.get() << ")";
+ ASSERT_TRUE(bStr != secondBStr)
+ << "bStr (" << bStr.get() << ") must not equal secondBStr ("
+ << secondBStr.get() << ")";
+ }
+}
+
+class MockObserver final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ MOCK_METHOD1(Mobserve, void(const char* aTopic));
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ Mobserve(aTopic);
+ return NS_OK;
+ };
+
+ MockObserver() = default;
+
+ private:
+ ~MockObserver() = default;
+};
+
+NS_IMPL_ISUPPORTS(MockObserver, nsIObserver);
+
+TEST_F(TelemetryTestFixture, OriginTelemetryNotifiesTopic) {
+ Unused << mTelemetry->ClearOrigins();
+
+ const char* kTopic = "origin-telemetry-storage-limit-reached";
+
+ MockObserver* mo = new MockObserver();
+ nsCOMPtr<nsIObserver> nsMo(mo);
+ EXPECT_CALL(*mo, Mobserve(StrEq(kTopic))).Times(1);
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ ASSERT_TRUE(os);
+ os->AddObserver(nsMo, kTopic, false);
+
+ const size_t size = ceil(10.0 / TelemetryOrigin::SizeOfPrioDatasPerMetric());
+ for (size_t i = 0; i < size; ++i) {
+ if (i < size - 1) {
+ // Let's ensure we only notify the once.
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ kFacebookOrigin);
+ }
+ Telemetry::RecordOrigin(OriginMetricID::TelemetryTest_Test1,
+ kDoubleclickOrigin);
+ }
+
+ os->RemoveObserver(nsMo, kTopic);
+}
diff --git a/toolkit/components/telemetry/tests/gtest/TestScalars.cpp b/toolkit/components/telemetry/tests/gtest/TestScalars.cpp
new file mode 100644
index 0000000000..c860369eac
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/TestScalars.cpp
@@ -0,0 +1,491 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "core/TelemetryScalar.h"
+#include "gtest/gtest.h"
+#include "js/Conversions.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryProcessEnums.h"
+#include "mozilla/Unused.h"
+#include "nsJSUtils.h" // nsAutoJSString
+#include "nsThreadUtils.h"
+#include "TelemetryFixture.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+using namespace TelemetryTestHelpers;
+using mozilla::Telemetry::ProcessID;
+
+#define EXPECTED_STRING "Nice, expected and creative string."
+
+// Test that we can properly write unsigned scalars using the C++ API.
+TEST_F(TelemetryTestFixture, ScalarUnsigned) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+
+ // Set the test scalar to a known value.
+ const uint32_t kInitialValue = 1172015;
+ const uint32_t kExpectedUint = 1172017;
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ kInitialValue);
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ kExpectedUint - kInitialValue);
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+ CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(),
+ scalarsSnapshot, kExpectedUint);
+
+ // Try to use SetMaximum.
+ const uint32_t kExpectedUintMaximum = kExpectedUint * 2;
+ Telemetry::ScalarSetMaximum(
+ Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ kExpectedUintMaximum);
+
+// Make sure that calls of the unsupported type don't corrupt the stored value.
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ false);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ u"test"_ns);
+#endif
+
+ // Check the recorded value.
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+ CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(),
+ scalarsSnapshot, kExpectedUintMaximum);
+}
+
+// Test that the AutoScalarTimer records a proper uint32_t value to a
+// scalar once it goes out of scope.
+TEST_F(TelemetryTestFixture, AutoScalarTimer) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+ {
+ Telemetry::AutoScalarTimer<
+ Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND>
+ timer;
+ }
+
+ const char* kScalarName = "telemetry.test.unsigned_int_kind";
+
+ // Check that there's a recorded value that is greater than 0. Since
+ // this is a timer, we'll not check the non-deterministic value - just
+ // that it exists.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+
+ // Validate the value of the test scalar.
+ JS::RootedValue value(cx.GetJSContext());
+ JS::RootedObject scalarObj(cx.GetJSContext(), &scalarsSnapshot.toObject());
+ ASSERT_TRUE(JS_GetProperty(cx.GetJSContext(), scalarObj, kScalarName, &value))
+ << "The test scalar must be reported.";
+
+ JS_GetProperty(cx.GetJSContext(), scalarObj, kScalarName, &value);
+ ASSERT_TRUE(value.isInt32())
+ << "The scalar value must be of the correct type.";
+ ASSERT_TRUE(value.toInt32() >= 0)
+ << "The uint scalar type must contain a value >= 0.";
+}
+
+// Test that we can properly write boolean scalars using the C++ API.
+TEST_F(TelemetryTestFixture, ScalarBoolean) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ Unused << mTelemetry->ClearScalars();
+
+ // Set the test scalar to a known value.
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, true);
+
+// Make sure that calls of the unsupported type don't corrupt the stored value.
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND,
+ static_cast<uint32_t>(12));
+ Telemetry::ScalarSetMaximum(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND,
+ 20);
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, 2);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND,
+ u"test"_ns);
+#endif
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+ CheckBoolScalar("telemetry.test.boolean_kind", cx.GetJSContext(),
+ scalarsSnapshot, true);
+}
+
+// Test that we can properly write string scalars using the C++ API.
+TEST_F(TelemetryTestFixture, ScalarString) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ Unused << mTelemetry->ClearScalars();
+
+ // Set the test scalar to a known value.
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND,
+ NS_LITERAL_STRING_FROM_CSTRING(EXPECTED_STRING));
+
+// Make sure that calls of the unsupported type don't corrupt the stored value.
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND,
+ static_cast<uint32_t>(12));
+ Telemetry::ScalarSetMaximum(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND,
+ 20);
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, 2);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, true);
+#endif
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+ CheckStringScalar("telemetry.test.string_kind", cx.GetJSContext(),
+ scalarsSnapshot, EXPECTED_STRING);
+}
+
+// Test that we can properly write keyed unsigned scalars using the C++ API.
+TEST_F(TelemetryTestFixture, KeyedScalarUnsigned) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ Unused << mTelemetry->ClearScalars();
+
+ // Set the test scalar to a known value.
+ const char* kScalarName = "telemetry.test.keyed_unsigned_int";
+ const uint32_t kKey1Value = 1172015;
+ const uint32_t kKey2Value = 1172017;
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+ u"key1"_ns, kKey1Value);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+ u"key2"_ns, kKey1Value);
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+ u"key2"_ns, 2);
+
+// Make sure that calls of the unsupported type don't corrupt the stored value.
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+ u"key1"_ns, false);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+ u"test"_ns);
+#endif
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+
+ // Check the keyed scalar we're interested in.
+ CheckKeyedUintScalar(kScalarName, "key1", cx.GetJSContext(), scalarsSnapshot,
+ kKey1Value);
+ CheckKeyedUintScalar(kScalarName, "key2", cx.GetJSContext(), scalarsSnapshot,
+ kKey2Value);
+ CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 2);
+
+ // Try to use SetMaximum.
+ const uint32_t kExpectedUintMaximum = kKey1Value * 2;
+ Telemetry::ScalarSetMaximum(
+ Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT, u"key1"_ns,
+ kExpectedUintMaximum);
+
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ // The first key should be different and te second is expected to be the same.
+ CheckKeyedUintScalar(kScalarName, "key1", cx.GetJSContext(), scalarsSnapshot,
+ kExpectedUintMaximum);
+ CheckKeyedUintScalar(kScalarName, "key2", cx.GetJSContext(), scalarsSnapshot,
+ kKey2Value);
+ CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 2);
+}
+
+TEST_F(TelemetryTestFixture, KeyedScalarBoolean) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ Unused << mTelemetry->ClearScalars();
+
+ // Set the test scalar to a known value.
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
+ u"key1"_ns, false);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
+ u"key2"_ns, true);
+
+// Make sure that calls of the unsupported type don't corrupt the stored value.
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
+ u"key1"_ns, static_cast<uint32_t>(12));
+ Telemetry::ScalarSetMaximum(
+ Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND, u"key1"_ns, 20);
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
+ u"key1"_ns, 2);
+#endif
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+
+ // Make sure that the keys contain the expected values.
+ const char* kScalarName = "telemetry.test.keyed_boolean_kind";
+ CheckKeyedBoolScalar(kScalarName, "key1", cx.GetJSContext(), scalarsSnapshot,
+ false);
+ CheckKeyedBoolScalar(kScalarName, "key2", cx.GetJSContext(), scalarsSnapshot,
+ true);
+ CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 2);
+}
+
+TEST_F(TelemetryTestFixture, NonMainThreadAdd) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ Unused << mTelemetry->ClearScalars();
+
+ // Define the function that will be called on the testing thread.
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "TelemetryTestFixture_NonMainThreadAdd_Test::TestBody", []() -> void {
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND, 37);
+ });
+
+ // Spawn the testing thread and run the function.
+ nsCOMPtr<nsIThread> testingThread;
+ nsresult rv =
+ NS_NewNamedThread("Test thread", getter_AddRefs(testingThread), runnable);
+ ASSERT_EQ(rv, NS_OK);
+
+ // Shutdown the thread. This also waits for the runnable to complete.
+ testingThread->Shutdown();
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+ CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(),
+ scalarsSnapshot, 37);
+}
+
+TEST_F(TelemetryTestFixture, ScalarUnknownID) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ const uint32_t kTestFakeIds[] = {
+ static_cast<uint32_t>(Telemetry::ScalarID::ScalarCount),
+ static_cast<uint32_t>(Telemetry::ScalarID::ScalarCount) + 378537,
+ std::numeric_limits<uint32_t>::max()};
+
+ for (auto id : kTestFakeIds) {
+ Telemetry::ScalarID scalarId = static_cast<Telemetry::ScalarID>(id);
+ Telemetry::ScalarSet(scalarId, static_cast<uint32_t>(1));
+ Telemetry::ScalarSet(scalarId, true);
+ Telemetry::ScalarSet(scalarId, u"test"_ns);
+ Telemetry::ScalarAdd(scalarId, 1);
+ Telemetry::ScalarSetMaximum(scalarId, 1);
+
+ // Make sure that nothing was recorded in the plain scalars.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+ ASSERT_TRUE(scalarsSnapshot.isUndefined())
+ << "No scalar must be recorded";
+
+ // Same for the keyed scalars.
+ Telemetry::ScalarSet(scalarId, u"key1"_ns, static_cast<uint32_t>(1));
+ Telemetry::ScalarSet(scalarId, u"key1"_ns, true);
+ Telemetry::ScalarAdd(scalarId, u"key1"_ns, 1);
+ Telemetry::ScalarSetMaximum(scalarId, u"key1"_ns, 1);
+
+ // Make sure that nothing was recorded in the keyed scalars.
+ JS::RootedValue keyedSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &keyedSnapshot);
+ ASSERT_TRUE(keyedSnapshot.isUndefined())
+ << "No keyed scalar must be recorded";
+ }
+#endif
+}
+
+TEST_F(TelemetryTestFixture, ScalarEventSummary) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+
+ const char* kScalarName = "telemetry.event_counts";
+
+ const char* kLongestEvent =
+ "oohwowlookthiscategoryissolong#thismethodislongtooo#"
+ "thisobjectisnoslouch";
+ TelemetryScalar::SummarizeEvent(nsCString(kLongestEvent), ProcessID::Parent,
+ false /* aDynamic */);
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+
+ CheckKeyedUintScalar(kScalarName, kLongestEvent, cx.GetJSContext(),
+ scalarsSnapshot, 1);
+
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ const char* kTooLongEvent =
+ "oohwowlookthiscategoryissolong#thismethodislongtooo#"
+ "thisobjectisnoslouch2";
+ TelemetryScalar::SummarizeEvent(nsCString(kTooLongEvent), ProcessID::Parent,
+ false /* aDynamic */);
+
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 1);
+#endif // #ifndef DEBUG
+
+ // Test we can fill the next 499 keys up to our 500 maximum
+ for (int i = 1; i < 500; i++) {
+ std::ostringstream eventName;
+ eventName << "category#method#object" << i;
+ TelemetryScalar::SummarizeEvent(nsCString(eventName.str().c_str()),
+ ProcessID::Parent, false /* aDynamic */);
+ }
+
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 500);
+
+// Don't run this part in debug builds as that intentionally asserts.
+#ifndef DEBUG
+ TelemetryScalar::SummarizeEvent(nsCString("whoops#too#many"),
+ ProcessID::Parent, false /* aDynamic */);
+
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckNumberOfProperties(kScalarName, cx.GetJSContext(), scalarsSnapshot, 500);
+#endif // #ifndef DEBUG
+}
+
+TEST_F(TelemetryTestFixture, ScalarEventSummary_Dynamic) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+
+ const char* kScalarName = "telemetry.dynamic_event_counts";
+ const char* kLongestEvent =
+ "oohwowlookthiscategoryissolong#thismethodislongtooo#"
+ "thisobjectisnoslouch";
+ TelemetryScalar::SummarizeEvent(nsCString(kLongestEvent), ProcessID::Parent,
+ true /* aDynamic */);
+ TelemetryScalar::SummarizeEvent(nsCString(kLongestEvent), ProcessID::Content,
+ true /* aDynamic */);
+
+ // Check the recorded value.
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot,
+ ProcessID::Dynamic);
+
+ // Recording in parent or content doesn't matter for dynamic scalars
+ // which all end up in the same place.
+ CheckKeyedUintScalar(kScalarName, kLongestEvent, cx.GetJSContext(),
+ scalarsSnapshot, 2);
+}
+
+TEST_F(TelemetryTestFixture, WrongScalarOperator) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+
+ const uint32_t expectedValue = 1172015;
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ expectedValue);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND,
+ NS_LITERAL_STRING_FROM_CSTRING(EXPECTED_STRING));
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, true);
+
+ TelemetryScalar::DeserializationStarted();
+
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_STRING_KIND, 1447);
+ Telemetry::ScalarAdd(Telemetry::ScalarID::TELEMETRY_TEST_BOOLEAN_KIND, 1447);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_UNSIGNED_INT_KIND,
+ true);
+ TelemetryScalar::ApplyPendingOperations();
+
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+ CheckStringScalar("telemetry.test.string_kind", cx.GetJSContext(),
+ scalarsSnapshot, EXPECTED_STRING);
+ CheckBoolScalar("telemetry.test.boolean_kind", cx.GetJSContext(),
+ scalarsSnapshot, true);
+ CheckUintScalar("telemetry.test.unsigned_int_kind", cx.GetJSContext(),
+ scalarsSnapshot, expectedValue);
+}
+
+TEST_F(TelemetryTestFixture, WrongKeyedScalarOperator) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+
+ const uint32_t kExpectedUint = 1172017;
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+ u"key1"_ns, kExpectedUint);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
+ u"key2"_ns, true);
+
+ TelemetryScalar::DeserializationStarted();
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+ u"key1"_ns, false);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_BOOLEAN_KIND,
+ u"key2"_ns, static_cast<uint32_t>(13));
+
+ TelemetryScalar::ApplyPendingOperations();
+
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckKeyedUintScalar("telemetry.test.keyed_unsigned_int", "key1",
+ cx.GetJSContext(), scalarsSnapshot, kExpectedUint);
+ CheckKeyedBoolScalar("telemetry.test.keyed_boolean_kind", "key2",
+ cx.GetJSContext(), scalarsSnapshot, true);
+}
+
+TEST_F(TelemetryTestFixture, TestKeyedScalarAllowedKeys) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ // Make sure we don't get scalars from other tests.
+ Unused << mTelemetry->ClearScalars();
+
+ const uint32_t kExpectedUint = 1172017;
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS,
+ u"only"_ns, kExpectedUint);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS,
+ u"meant"_ns, kExpectedUint);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS,
+ u"for"_ns, kExpectedUint);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS,
+ u"testing"_ns, kExpectedUint);
+
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS,
+ u"invalid"_ns, kExpectedUint);
+ Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_WITH_KEYS,
+ u"not-valid"_ns, kExpectedUint);
+
+ JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+ GetScalarsSnapshot(true, cx.GetJSContext(), &scalarsSnapshot);
+ CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "only",
+ cx.GetJSContext(), scalarsSnapshot, kExpectedUint);
+ CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "meant",
+ cx.GetJSContext(), scalarsSnapshot, kExpectedUint);
+ CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "for",
+ cx.GetJSContext(), scalarsSnapshot, kExpectedUint);
+ CheckKeyedUintScalar("telemetry.test.keyed_with_keys", "testing",
+ cx.GetJSContext(), scalarsSnapshot, kExpectedUint);
+ CheckNumberOfProperties("telemetry.test.keyed_with_keys", cx.GetJSContext(),
+ scalarsSnapshot, 4);
+
+ CheckKeyedUintScalar("telemetry.keyed_scalars_unknown_keys",
+ "telemetry.test.keyed_with_keys", cx.GetJSContext(),
+ scalarsSnapshot, 2);
+}
diff --git a/toolkit/components/telemetry/tests/gtest/moz.build b/toolkit/components/telemetry/tests/gtest/moz.build
new file mode 100644
index 0000000000..1a20a75d99
--- /dev/null
+++ b/toolkit/components/telemetry/tests/gtest/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Library("telemetrytest")
+
+LOCAL_INCLUDES += [
+ "../..",
+]
+
+UNIFIED_SOURCES = [
+ "TelemetryFixture.cpp",
+ "TelemetryTestHelpers.cpp",
+]
+
+if CONFIG["OS_TARGET"] != "Android":
+ UNIFIED_SOURCES += [
+ "TestCombinedStacks.cpp",
+ "TestCounters.cpp",
+ "TestEvents.cpp",
+ "TestHistograms.cpp",
+ "TestOrigins.cpp",
+ "TestScalars.cpp",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"
+
+include("/ipc/chromium/chromium-config.mozbuild")