Bug 1459144 - Add gtest coverage for GeckoView measurements' persistence. r?chutten,janerik draft
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Tue, 22 May 2018 17:15:33 +0200
changeset 799212 b97f17f7f25841ba7ea40b87caf642f44eb370ba
parent 799211 56a8b262c6348fad021e693912135d0b6c12775d
push id110969
push userbmo:alessio.placitelli@gmail.com
push dateThu, 24 May 2018 07:46:56 +0000
reviewerschutten, janerik
bugs1459144
milestone62.0a1
Bug 1459144 - Add gtest coverage for GeckoView measurements' persistence. r?chutten,janerik This patch changes GeckoView persistence code so that it will always get compiled and linked to the final executable, even outside of Android. By doing so, we are able to easily add gtest coverage for this code on all platforms other than Android on which gtest is not yet supported. In addition to that, this patch adds proper test cases for measurements' serialization and deserialization for both scalars and histograms. MozReview-Commit-ID: J0Snhl3Y8jk
toolkit/components/telemetry/TelemetryHistogram.cpp
toolkit/components/telemetry/TelemetryHistogram.h
toolkit/components/telemetry/TelemetryScalar.cpp
toolkit/components/telemetry/TelemetryScalar.h
toolkit/components/telemetry/geckoview/gtest/TestGeckoView.cpp
--- a/toolkit/components/telemetry/TelemetryHistogram.cpp
+++ b/toolkit/components/telemetry/TelemetryHistogram.cpp
@@ -13,20 +13,17 @@
 #include "nsBaseHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsITelemetry.h"
 #include "nsPrintfCString.h"
 
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/Atomics.h"
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
-// This is only used on GeckoView.
 #include "mozilla/JSONWriter.h"
-#endif
 #include "mozilla/StartupTimeline.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/Unused.h"
 
 #include "TelemetryCommon.h"
 #include "TelemetryHistogram.h"
 #include "TelemetryScalar.h"
 #include "ipc/TelemetryIPCAccumulator.h"
@@ -2516,17 +2513,21 @@ size_t
 TelemetryHistogram::GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf
                                                      aMallocSizeOf)
 {
   StaticMutexAutoLock locker(gTelemetryHistogramMutex);
   // TODO
   return 0;
 }
 
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PRIVATE: GeckoView specific helpers
+
 namespace base {
 class PersistedSampleSet : public Histogram::SampleSet
 {
 public:
   explicit PersistedSampleSet(const nsTArray<Histogram::Count>& aCounts,
                               int64_t aSampleSum);
 };
 
@@ -2670,16 +2671,21 @@ internal_ParseHistogramData(JSContext* a
     aOutCountArray.AppendElement(countAsInt);
   }
 
   return NS_OK;
 }
 
 } // Anonymous namespace
 
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PUBLIC: GeckoView serialization/deserialization functions.
+
 nsresult
 TelemetryHistogram::SerializeHistograms(mozilla::JSONWriter& aWriter)
 {
   MOZ_ASSERT(XRE_IsParentProcess(), "Only save histograms in the parent process");
   if (!XRE_IsParentProcess()) {
     return NS_ERROR_FAILURE;
   }
 
@@ -3123,9 +3129,8 @@ TelemetryHistogram::DeserializeKeyedHist
         h->AddSampleSet(base::PersistedSampleSet(mozilla::Move(mozilla::Get<2>(histogramData)),
                                                  mozilla::Get<3>(histogramData)));
       }
     }
   }
 
   return NS_OK;
 }
-#endif // MOZ_TELEMETRY_GECKOVIEW
--- a/toolkit/components/telemetry/TelemetryHistogram.h
+++ b/toolkit/components/telemetry/TelemetryHistogram.h
@@ -7,22 +7,20 @@
 #define TelemetryHistogram_h__
 
 #include "mozilla/TelemetryHistogramEnums.h"
 #include "mozilla/TelemetryProcessEnums.h"
 
 #include "mozilla/TelemetryComms.h"
 #include "nsXULAppAPI.h"
 
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
 namespace mozilla{
 // This is only used for the GeckoView persistence.
 class JSONWriter;
 }
-#endif
 
 // This module is internal to Telemetry.  It encapsulates Telemetry's
 // histogram accumulation and storage logic.  It should only be used by
 // Telemetry.cpp.  These functions should not be used anywhere else.
 // For the public interface to Telemetry functionality, see Telemetry.h.
 
 namespace TelemetryHistogram {
 
@@ -82,18 +80,16 @@ size_t
 GetMapShallowSizesOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 size_t
 GetHistogramSizesofIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
 // These functions are only meant to be used for GeckoView persistence.
 // They are responsible for updating in-memory probes with the data persisted
 // on the disk and vice-versa.
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
 nsresult SerializeHistograms(mozilla::JSONWriter &aWriter);
 nsresult SerializeKeyedHistograms(mozilla::JSONWriter &aWriter);
 nsresult DeserializeHistograms(JSContext* aCx, JS::HandleValue aData);
 nsresult DeserializeKeyedHistograms(JSContext* aCx, JS::HandleValue aData);
-#endif // MOZ_TELEMETRY_GECKOVIEW
 
 } // namespace TelemetryHistogram
 
 #endif // TelemetryHistogram_h__
--- a/toolkit/components/telemetry/TelemetryScalar.cpp
+++ b/toolkit/components/telemetry/TelemetryScalar.cpp
@@ -13,20 +13,17 @@
 #include "nsDataHashtable.h"
 #include "nsIXPConnect.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 #include "nsJSUtils.h"
 #include "nsPrintfCString.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/PContent.h"
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
-// This is only used on GeckoView.
 #include "mozilla/JSONWriter.h"
-#endif
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Unused.h"
 
 #include "TelemetryCommon.h"
 #include "TelemetryScalar.h"
 #include "TelemetryScalarData.h"
@@ -255,17 +252,16 @@ GetVariantFromIVariant(nsIVariant* aInpu
       }
     default:
       MOZ_ASSERT(false, "Unknown scalar kind.");
       return ScalarResult::UnknownScalar;
   }
   return ScalarResult::Ok;
 }
 
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
 /**
  * Write a nsIVariant with a JSONWriter, used for GeckoView persistence.
  */
 nsresult
 WriteVariantToJSONWriter(uint32_t aScalarType, nsIVariant* aInputValue,
                          const char* aPropertyName, mozilla::JSONWriter& aWriter)
 {
   MOZ_ASSERT(aInputValue);
@@ -297,17 +293,16 @@ WriteVariantToJSONWriter(uint32_t aScala
       }
     default:
       MOZ_ASSERT(false, "Unknown scalar kind.");
       return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
-#endif // MOZ_TELEMETRY_GECKOVIEW
 
 // Implements the methods for ScalarInfo.
 const char *
 ScalarInfo::name() const
 {
   return &gScalarsStringTable[this->name_offset];
 }
 
@@ -3079,17 +3074,21 @@ TelemetryScalar::AddDynamicScalarDefinit
   }
 
   {
     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
     internal_RegisterScalars(locker, dynamicStubs);
   }
 }
 
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+//
+// PUBLIC: GeckoView serialization/deserialization functions.
+
 /**
  * Write the scalar data to the provided Json object, for
  * GeckoView measurement persistence. The output format is the same one used
  * for snapshotting the scalars.
  *
  * @param {aWriter} The JSON object to write to.
  * @returns NS_OK or a failure value explaining why persistence failed.
  */
@@ -3500,9 +3499,8 @@ TelemetryScalar::DeserializePersistedKey
                                                       mozilla::Get<2>(processScalars[i]),
                                                       ProcessID(iter.Key()));
       }
     }
   }
 
   return NS_OK;
 }
-#endif // MOZ_TELEMETRY_GECKOVIEW
--- a/toolkit/components/telemetry/TelemetryScalar.h
+++ b/toolkit/components/telemetry/TelemetryScalar.h
@@ -10,20 +10,18 @@
 #include "mozilla/TelemetryProcessEnums.h"
 
 // This module is internal to Telemetry. It encapsulates Telemetry's
 // scalar accumulation and storage logic. It should only be used by
 // Telemetry.cpp. These functions should not be used anywhere else.
 // For the public interface to Telemetry functionality, see Telemetry.h.
 
 namespace mozilla {
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
 // This is only used for the GeckoView persistence.
 class JSONWriter;
-#endif
 namespace Telemetry {
   struct ScalarAction;
   struct KeyedScalarAction;
   struct DiscardedData;
   struct DynamicScalarDefinition;
 } // namespace Telemetry
 } // namespace mozilla
 
@@ -89,17 +87,15 @@ void UpdateChildKeyedData(mozilla::Telem
 void RecordDiscardedData(mozilla::Telemetry::ProcessID aProcessType,
                          const mozilla::Telemetry::DiscardedData& aDiscardedData);
 
 void GetDynamicScalarDefinitions(nsTArray<mozilla::Telemetry::DynamicScalarDefinition>&);
 void AddDynamicScalarDefinitions(const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>&);
 
 // They are responsible for updating in-memory probes with the data persisted
 // on the disk and vice-versa.
-#if defined(MOZ_TELEMETRY_GECKOVIEW)
 nsresult SerializeScalars(mozilla::JSONWriter &aWriter);
 nsresult SerializeKeyedScalars(mozilla::JSONWriter &aWriter);
 nsresult DeserializePersistedScalars(JSContext* aCx, JS::HandleValue aData);
 nsresult DeserializePersistedKeyedScalars(JSContext* aCx, JS::HandleValue aData);
-#endif // MOZ_TELEMETRY_GECKOVIEW
 } // namespace TelemetryScalar
 
 #endif // TelemetryScalar_h__
--- a/toolkit/components/telemetry/geckoview/gtest/TestGeckoView.cpp
+++ b/toolkit/components/telemetry/geckoview/gtest/TestGeckoView.cpp
@@ -27,42 +27,21 @@ const char kSampleData[] = R"({
     }
   },
   "keyedScalars": {
     "parent": {
       "telemetry.test.keyed_unsigned_int": {
         "testKey": 73
       }
     }
-  },
-  "histograms": {
-    "parent": {
-      "TELEMETRY_TEST_MULTIPRODUCT": {
-        "sum": 6,
-        "counts": [
-          3, 5, 7
-        ]
-      }
-    }
-  },
-  "keyedHistograms": {
-    "content": {
-      "TELEMETRY_TEST_MULTIPRODUCT_KEYED": {
-        "niceKey": {
-          "sum": 10,
-          "counts": [
-            1, 2, 3
-          ]
-        }
-      }
-    }
   }
 })";
 
 const char16_t kPersistedFilename[] = u"gv_measurements.json";
+const char kDataLoadedTopic[] = "internal-telemetry-geckoview-load-complete";
 
 namespace {
 
 /**
  * Using gtest assertion macros requires the containing function to return
  * a void type. For this reason, all the functions below are using that return
  * type.
  */
@@ -156,212 +135,79 @@ CheckPersistenceFileExists(bool& aFileEx
   nsAutoString fileName;
   fileName.Append(kPersistedFilename);
   file->Append(fileName);
 
   rv = file->Exists(&aFileExists);
   ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists must not fail";
 }
 
-void
-CheckJSONEqual(JSContext* aCx, JS::HandleValue aData, JS::HandleValue aDataOther)
+/**
+ * A helper class to wait for the internal "data loaded"
+ * topic.
+ */
+class DataLoadedObserver final : public nsIObserver
 {
-  auto JSONCreator = [](const char16_t* aBuf, uint32_t aLen, void* aData) -> bool
-  {
-    nsAString* result = static_cast<nsAString*>(aData);
-    result->Append(static_cast<const char16_t*>(aBuf),
-                   static_cast<uint32_t>(aLen));
-    return true;
-  };
-
-  // Unfortunately, we dont
-  nsAutoString dataAsString;
-  JS::RootedObject dataObj(aCx, &aData.toObject());
-  ASSERT_TRUE(JS::ToJSONMaybeSafely(aCx, dataObj, JSONCreator, &dataAsString))
-    << "The JS object must be correctly converted to a JSON string";
-
-  nsAutoString otherAsString;
-  JS::RootedObject otherObj(aCx, &aDataOther.toObject());
-  ASSERT_TRUE(JS::ToJSONMaybeSafely(aCx, otherObj, JSONCreator, &otherAsString))
-    << "The JS object must be correctly converted to a JSON string";
-
-  ASSERT_TRUE(dataAsString.Equals(otherAsString))
-    << "The JSON strings must match";
-}
-
-void
-TestSerializeScalars(JSONWriter& aWriter)
-{
-  // Report the same data that's in kSampleData for scalars.
-  // We only want to make sure that I/O and parsing works, as telemetry
-  // measurement updates is taken care of by xpcshell tests.
-  aWriter.StartObjectProperty("content");
-  aWriter.IntProperty("telemetry.test.all_processes_uint", 37);
-  aWriter.EndObject();
-}
+  ~DataLoadedObserver() = default;
 
-void
-TestSerializeKeyedScalars(JSONWriter& aWriter)
-{
-  // Report the same data that's in kSampleData for keyed scalars.
-  // We only want to make sure that I/O and parsing works, as telemetry
-  // measurement updates is taken care of by xpcshell tests.
-  aWriter.StartObjectProperty("parent");
-  aWriter.StartObjectProperty("telemetry.test.keyed_unsigned_int");
-  aWriter.IntProperty("testKey", 73);
-  aWriter.EndObject();
-  aWriter.EndObject();
-}
+public:
+  NS_DECL_ISUPPORTS
 
-void
-TestDeserializePersistedScalars(JSContext* aCx, JS::HandleValue aData)
-{
-  // Get a JS object out of the JSON sample.
-  JS::RootedValue sampleData(aCx);
-  NS_ConvertUTF8toUTF16 utf16Content(kSampleData);
-  ASSERT_TRUE(JS_ParseJSON(aCx, utf16Content.BeginReading(), utf16Content.Length(), &sampleData))
-    << "Failed to create a JS object from the JSON sample";
-
-  // Get sampleData["scalars"].
-  JS::RootedObject sampleObj(aCx, &sampleData.toObject());
-  JS::RootedValue scalarData(aCx);
-  ASSERT_TRUE(JS_GetProperty(aCx, sampleObj, "scalars", &scalarData) && scalarData.isObject())
-    << "Failed to get sampleData['scalars']";
-
-  CheckJSONEqual(aCx, aData, scalarData);
-}
-
-void
-TestDeserializePersistedKeyedScalars(JSContext* aCx, JS::HandleValue aData)
-{
-  // Get a JS object out of the JSON sample.
-  JS::RootedValue sampleData(aCx);
-  NS_ConvertUTF8toUTF16 utf16Content(kSampleData);
-  ASSERT_TRUE(JS_ParseJSON(aCx, utf16Content.BeginReading(), utf16Content.Length(), &sampleData))
-    << "Failed to create a JS object from the JSON sample";
+  explicit DataLoadedObserver() :
+    mDataLoaded(false)
+  {
+    // The following line can fail to fetch the observer service. However,
+    // since we're test code, we're fine with crashing due to that.
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->AddObserver(this, kDataLoadedTopic, false);
+  }
 
-  // Get sampleData["keyedScalars"].
-  JS::RootedObject sampleObj(aCx, &sampleData.toObject());
-  JS::RootedValue keyedScalarData(aCx);
-  ASSERT_TRUE(JS_GetProperty(aCx, sampleObj, "keyedScalars", &keyedScalarData)
-              && keyedScalarData.isObject()) << "Failed to get sampleData['keyedScalars']";
-
-  CheckJSONEqual(aCx, aData, keyedScalarData);
-}
-
-void
-TestSerializeHistograms(JSONWriter& aWriter)
-{
-  // Report the same data that's in kSampleData for histograms.
-  // We only want to make sure that I/O and parsing works, as telemetry
-  // measurement updates is taken care of by xpcshell tests.
-  aWriter.StartObjectProperty("parent");
-  aWriter.StartObjectProperty("TELEMETRY_TEST_MULTIPRODUCT");
-  aWriter.IntProperty("sum", 6);
-  aWriter.StartArrayProperty("counts");
-  aWriter.IntElement(3);
-  aWriter.IntElement(5);
-  aWriter.IntElement(7);
-  aWriter.EndArray();
-  aWriter.EndObject();
-  aWriter.EndObject();
-}
+  void WaitForNotification()
+  {
+    mozilla::SpinEventLoopUntil([&]() { return mDataLoaded; });
+  }
 
-void
-TestSerializeKeyedHistograms(JSONWriter& aWriter)
-{
-  // Report the same data that's in kSampleData for keyed histograms.
-  // We only want to make sure that I/O and parsing works, as telemetry
-  // measurement updates is taken care of by xpcshell tests.
-  aWriter.StartObjectProperty("content");
-  aWriter.StartObjectProperty("TELEMETRY_TEST_MULTIPRODUCT_KEYED");
-  aWriter.StartObjectProperty("niceKey");
-  aWriter.IntProperty("sum", 10);
-  aWriter.StartArrayProperty("counts");
-  aWriter.IntElement(1);
-  aWriter.IntElement(2);
-  aWriter.IntElement(3);
-  aWriter.EndArray();
-  aWriter.EndObject();
-  aWriter.EndObject();
-  aWriter.EndObject();
-}
+  NS_IMETHOD Observe(nsISupports* aSubject,
+                     const char* aTopic,
+                     const char16_t* aData) override
+  {
+    if (!strcmp(aTopic, kDataLoadedTopic)) {
+      nsCOMPtr<nsIObserverService> observerService =
+        mozilla::services::GetObserverService();
+      observerService->RemoveObserver(this, kDataLoadedTopic);
+      mDataLoaded = true;
+    }
 
-void
-TestDeserializeHistograms(JSContext* aCx, JS::HandleValue aData)
-{
-  // Get a JS object out of the JSON sample.
-  JS::RootedValue sampleData(aCx);
-  NS_ConvertUTF8toUTF16 utf16Content(kSampleData);
-  ASSERT_TRUE(JS_ParseJSON(aCx, utf16Content.BeginReading(), utf16Content.Length(), &sampleData))
-    << "Failed to create a JS object from the JSON sample";
-
-  // Get sampleData["histograms"].
-  JS::RootedObject sampleObj(aCx, &sampleData.toObject());
-  JS::RootedValue histogramData(aCx);
-  ASSERT_TRUE(JS_GetProperty(aCx, sampleObj, "histograms", &histogramData) && histogramData.isObject())
-    << "Failed to get sampleData['histograms']";
-
-  CheckJSONEqual(aCx, aData, histogramData);
-}
+    return NS_OK;
+  }
 
-void
-TestDeserializeKeyedHistograms(JSContext* aCx, JS::HandleValue aData)
-{
-  // Get a JS object out of the JSON sample.
-  JS::RootedValue sampleData(aCx);
-  NS_ConvertUTF8toUTF16 utf16Content(kSampleData);
-  ASSERT_TRUE(JS_ParseJSON(aCx, utf16Content.BeginReading(), utf16Content.Length(), &sampleData))
-    << "Failed to create a JS object from the JSON sample";
+private:
+  bool mDataLoaded;
+};
 
-  // Get sampleData["keyedHistograms"].
-  JS::RootedObject sampleObj(aCx, &sampleData.toObject());
-  JS::RootedValue keyedHistogramData(aCx);
-  ASSERT_TRUE(JS_GetProperty(aCx, sampleObj, "keyedHistograms", &keyedHistogramData)
-              && keyedHistogramData.isObject()) << "Failed to get sampleData['keyedHistograms']";
-
-  CheckJSONEqual(aCx, aData, keyedHistogramData);
-}
+NS_IMPL_ISUPPORTS(
+  DataLoadedObserver,
+  nsIObserver
+)
 
 } // Anonymous
 
 /**
  * A GeckoView specific test fixture. Please note that this
  * can't live in the above anonymous namespace.
  */
 class TelemetryGeckoViewFixture : public TelemetryTestFixture {
 protected:
   virtual void SetUp() {
     TelemetryTestFixture::SetUp();
     MockAndroidDataDir();
   }
 };
 
-/**
- * We can't link TelemetryScalar.cpp to these test files, so mock up
- * the required functions to make the linker not complain.
- */
-namespace TelemetryScalar {
-
-nsresult SerializeScalars(JSONWriter& aWriter) { TestSerializeScalars(aWriter); return NS_OK; }
-nsresult SerializeKeyedScalars(JSONWriter& aWriter) { TestSerializeKeyedScalars(aWriter); return NS_OK; }
-nsresult DeserializePersistedScalars(JSContext* aCx, JS::HandleValue aData) { TestDeserializePersistedScalars(aCx, aData); return NS_OK; }
-nsresult DeserializePersistedKeyedScalars(JSContext* aCx, JS::HandleValue aData) { TestDeserializePersistedKeyedScalars(aCx, aData); return NS_OK; }
-
-} // TelemetryScalar
-
-namespace TelemetryHistogram {
-
-nsresult SerializeHistograms(mozilla::JSONWriter &aWriter) { TestSerializeHistograms(aWriter); return NS_OK; }
-nsresult SerializeKeyedHistograms(mozilla::JSONWriter &aWriter) { TestSerializeKeyedHistograms(aWriter); return NS_OK; }
-nsresult DeserializeHistograms(JSContext* aCx, JS::HandleValue aData) { TestDeserializeHistograms(aCx, aData); return NS_OK; }
-nsresult DeserializeKeyedHistograms(JSContext* aCx, JS::HandleValue aData) { TestDeserializeKeyedHistograms(aCx, aData); return NS_OK; }
-
-} // TelemetryHistogram
-
 namespace TelemetryGeckoViewTesting {
 
 void TestDispatchPersist();
 
 } // TelemetryGeckoViewTesting
 
 /**
  * Test that corrupted JSON files don't crash the Telemetry core.
@@ -413,39 +259,190 @@ TEST_F(TelemetryGeckoViewFixture, ClearP
   TelemetryGeckoViewPersistence::ClearPersistenceData();
   TelemetryGeckoViewPersistence::DeInitPersistence();
 
   CheckPersistenceFileExists(fileExists);
   ASSERT_FALSE(fileExists) << "ClearPersistenceData must remove the persistence file";
 }
 
 /**
- * Test that we can correctly persist the data.
+ * Test that the data loaded topic gets notified correctly.
  */
-TEST_F(TelemetryGeckoViewFixture, PersistData) {
+TEST_F(TelemetryGeckoViewFixture, CheckDataLoadedTopic) {
   AutoJSContextWithGlobal cx(mCleanGlobal);
 
   bool fileExists = false;
   CheckPersistenceFileExists(fileExists);
   ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk";
 
+  // Check that the data loaded topic is notified after attempting the load
+  // if no measurement file exists.
+  RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver();
+  TelemetryGeckoViewPersistence::InitPersistence();
+  loadingFinished->WaitForNotification();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  // Check that the topic is triggered when the measuements file exists.
+  WritePersistenceFile(nsDependentCString(kSampleData));
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_TRUE(fileExists) << "The persisted measurements must exist on the disk";
+
+  // Check that the data loaded topic is triggered when the measurement file exists.
+  loadingFinished = new DataLoadedObserver();
+  TelemetryGeckoViewPersistence::InitPersistence();
+  loadingFinished->WaitForNotification();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  // Cleanup/remove the files.
+  RemovePersistenceFile();
+}
+
+/**
+ * Test that we can correctly persist the scalar data.
+ */
+TEST_F(TelemetryGeckoViewFixture, PersistScalars) {
+  AutoJSContextWithGlobal cx(mCleanGlobal);
+
+  Unused << mTelemetry->ClearScalars();
+
+  bool fileExists = false;
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk";
+
+  RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver();
+
   // Init the persistence: this will trigger the measurements to be written
   // to disk off-the-main thread.
   TelemetryGeckoViewPersistence::InitPersistence();
+  loadingFinished->WaitForNotification();
+
+  // Set some scalars: we can only test the parent process as we don't support other
+  // processes in gtests.
+  const uint32_t kExpectedUintValue = 37;
+  const uint32_t kExpectedKeyedUintValue = 73;
+  Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_ALL_PROCESSES_UINT, kExpectedUintValue);
+  Telemetry::ScalarSet(Telemetry::ScalarID::TELEMETRY_TEST_KEYED_UNSIGNED_INT,
+                       NS_LITERAL_STRING("gv_key"), kExpectedKeyedUintValue);
+
+  // Dispatch the persisting task: we don't wait for the timer to expire
+  // as we need a reliable and reproducible way to kick this off. We ensure
+  // that the task runs by shutting down the persistence: this shuts down the
+  // thread which executes the task as the last action.
+  TelemetryGeckoViewTesting::TestDispatchPersist();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_TRUE(fileExists) << "The persisted measurements must exist on the disk";
+
+  // Clear the in-memory scalars again. They will be restored from the disk.
+  Unused << mTelemetry->ClearScalars();
+
+  // Load the persisted file again.
+  TelemetryGeckoViewPersistence::InitPersistence();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  // Get a snapshot of the keyed and plain scalars.
+  JS::RootedValue scalarsSnapshot(cx.GetJSContext());
+  JS::RootedValue keyedScalarsSnapshot(cx.GetJSContext());
+  GetScalarsSnapshot(false, cx.GetJSContext(), &scalarsSnapshot);
+  GetScalarsSnapshot(true, cx.GetJSContext(), &keyedScalarsSnapshot);
+
+  // Verify that the scalars were correctly persisted and restored.
+  CheckUintScalar("telemetry.test.all_processes_uint", cx.GetJSContext(),
+                  scalarsSnapshot, kExpectedUintValue);
+  CheckKeyedUintScalar("telemetry.test.keyed_unsigned_int", "gv_key", cx.GetJSContext(),
+                       keyedScalarsSnapshot, kExpectedKeyedUintValue);
+
+  // Cleanup/remove the files.
+  RemovePersistenceFile();
+}
+
+/**
+ * Test that we can correctly persist the histogram data.
+ */
+TEST_F(TelemetryGeckoViewFixture, PersistHistograms) {
+  AutoJSContextWithGlobal cx(mCleanGlobal);
+
+  // Clear the histogram data.
+  GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+                       NS_LITERAL_CSTRING("TELEMETRY_TEST_MULTIPRODUCT"), false /* is_keyed */);
+  GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+                       NS_LITERAL_CSTRING("TELEMETRY_TEST_KEYED_COUNT"), true /* is_keyed */);
+
+  bool fileExists = false;
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk";
+
+  RefPtr<DataLoadedObserver> loadingFinished = new DataLoadedObserver();
+
+  // Init the persistence: this will trigger the measurements to be written
+  // to disk off-the-main thread.
+  TelemetryGeckoViewPersistence::InitPersistence();
+  loadingFinished->WaitForNotification();
+
+  // Set some histograms: we can only test the parent process as we don't support other
+  // processes in gtests.
+  const uint32_t kExpectedUintValue = 37;
+  const nsTArray<uint32_t> keyedSamples({5, 10, 15});
+  const uint32_t kExpectedKeyedSum = 5 + 10 + 15;
+  Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_MULTIPRODUCT, kExpectedUintValue);
+  Telemetry::Accumulate(Telemetry::TELEMETRY_TEST_KEYED_COUNT, NS_LITERAL_CSTRING("gv_key"),
+                        keyedSamples);
 
   // Dispatch the persisting task: we don't wait for the timer to expire
   // as we need a reliable and reproducible way to kick off this. We ensure
   // that the task runs by shutting down the persistence: this shuts down the
   // thread which executes the task as the last action.
   TelemetryGeckoViewTesting::TestDispatchPersist();
   TelemetryGeckoViewPersistence::DeInitPersistence();
 
   CheckPersistenceFileExists(fileExists);
   ASSERT_TRUE(fileExists) << "The persisted measurements must exist on the disk";
 
-  // Load the persisted file again: this will trigger the TestLoad* functions
-  // that will validate the data.
+  // Clear the in-memory histograms again. They will be restored from the disk.
+  GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+                       NS_LITERAL_CSTRING("TELEMETRY_TEST_MULTIPRODUCT"), false /* is_keyed */);
+  GetAndClearHistogram(cx.GetJSContext(), mTelemetry,
+                       NS_LITERAL_CSTRING("TELEMETRY_TEST_KEYED_COUNT"), true /* is_keyed */);
+
+
+  // Load the persisted file again.
   TelemetryGeckoViewPersistence::InitPersistence();
   TelemetryGeckoViewPersistence::DeInitPersistence();
 
+  // Get a snapshot of the keyed and plain histograms.
+  JS::RootedValue snapshot(cx.GetJSContext());
+  JS::RootedValue keyedSnapshot(cx.GetJSContext());
+  GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_MULTIPRODUCT",
+               &snapshot, false /* is_keyed */);
+  GetSnapshots(cx.GetJSContext(), mTelemetry, "TELEMETRY_TEST_KEYED_COUNT",
+               &keyedSnapshot, true /* is_keyed */);
+
+  // Validate the loaded histogram data.
+  JS::RootedValue histogram(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_MULTIPRODUCT", 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, kExpectedUintValue) << "The histogram is not returning the expected value";
+
+  // Validate the keyed histogram data.
+  GetProperty(cx.GetJSContext(), "TELEMETRY_TEST_KEYED_COUNT", keyedSnapshot, &histogram);
+
+  // Get "testkey" property from histogram and check that it stores the correct
+  // data.
+  JS::RootedValue expectedKeyData(cx.GetJSContext());
+  GetProperty(cx.GetJSContext(), "gv_key", histogram,  &expectedKeyData);
+  ASSERT_FALSE(expectedKeyData.isUndefined())
+    << "Cannot find the expected key in the keyed histogram data";
+  GetProperty(cx.GetJSContext(), "sum", expectedKeyData,  &sum);
+  JS::ToUint32(cx.GetJSContext(), sum, &uSum);
+  ASSERT_EQ(uSum, kExpectedKeyedSum)
+    << "The histogram is not returning the expected sum for 'gv_key'";
+
   // Cleanup/remove the files.
   RemovePersistenceFile();
 }