Bug 1453591 - Add gtest coverage for the persistence logic. r?chutten,janerik,froydnj draft
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Mon, 23 Apr 2018 19:14:23 +0200
changeset 795176 65683ae851faa4532bf313d75c88053fe13876c5
parent 795175 eda708bacfd34f10d166b79cc702bb515d3feeb4
push id109885
push userbmo:alessio.placitelli@gmail.com
push dateTue, 15 May 2018 07:22:25 +0000
reviewerschutten, janerik, froydnj
bugs1453591
milestone62.0a1
Bug 1453591 - Add gtest coverage for the persistence logic. r?chutten,janerik,froydnj This changes the build system to add a new define when on Android or when tests are enabled, MOZ_TELEMETRY_GECKOVIEW. MozReview-Commit-ID: 5n2A8G2ZzRK
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/TelemetryScalar.cpp
toolkit/components/telemetry/TelemetryScalar.h
toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
toolkit/components/telemetry/geckoview/gtest/TestGeckoView.cpp
toolkit/components/telemetry/geckoview/gtest/moz.build
toolkit/components/telemetry/moz.build
toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp
toolkit/components/telemetry/tests/gtest/TelemetryFixture.h
toolkit/components/telemetry/tests/gtest/moz.build
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -90,17 +90,17 @@
 #include "nsProxyRelease.h"
 #include "HangReports.h"
 
 #if defined(MOZ_GECKO_PROFILER)
 #include "shared-libraries.h"
 #include "KeyedStackCapturer.h"
 #endif // MOZ_GECKO_PROFILER
 
-#if defined(MOZ_WIDGET_ANDROID)
+#if defined(MOZ_TELEMETRY_GECKOVIEW)
 #include "geckoview/TelemetryGeckoViewPersistence.h"
 #endif
 
 namespace {
 
 using namespace mozilla;
 using namespace mozilla::HangMonitor;
 using Telemetry::Common::AutoHashtable;
@@ -1277,17 +1277,17 @@ TelemetryImpl::CreateTelemetryInstance()
   nsCOMPtr<nsITelemetry> ret = sTelemetry;
 
   sTelemetry->mCanRecordBase = useTelemetry;
   sTelemetry->mCanRecordExtended = useTelemetry;
 
   sTelemetry->InitMemoryReporter();
   InitHistogramRecordingEnabled(); // requires sTelemetry to exist
 
-#if defined MOZ_WIDGET_ANDROID
+#if defined(MOZ_TELEMETRY_GECKOVIEW)
   // We only want to add persistence for GeckoView, but both
   // GV and Fennec are on Android. So just init persistence if this
   // is Android but not Fennec.
   if (GetCurrentProduct() == SupportedProduct::Geckoview) {
     TelemetryGeckoViewPersistence::InitPersistence();
   }
 #endif
 
@@ -1303,17 +1303,17 @@ TelemetryImpl::ShutdownTelemetry()
 
   // Lastly, de-initialise the TelemetryHistogram and TelemetryScalar global states,
   // so as to release any heap storage that would otherwise be kept alive by it.
   TelemetryHistogram::DeInitializeGlobalState();
   TelemetryScalar::DeInitializeGlobalState();
   TelemetryEvent::DeInitializeGlobalState();
   TelemetryIPCAccumulator::DeInitializeGlobalState();
 
-#if defined(MOZ_WIDGET_ANDROID)
+#if defined(MOZ_TELEMETRY_GECKOVIEW)
   if (GetCurrentProduct() == SupportedProduct::Geckoview) {
     TelemetryGeckoViewPersistence::DeInitPersistence();
   }
 #endif
 }
 
 void
 TelemetryImpl::StoreSlowSQL(const nsACString &sql, uint32_t delay,
@@ -1855,17 +1855,17 @@ TelemetryImpl::ResetCurrentProduct()
 #else
   return NS_ERROR_FAILURE;
 #endif
 }
 
 NS_IMETHODIMP
 TelemetryImpl::ClearProbes()
 {
-#if defined(MOZ_WIDGET_ANDROID)
+#if defined(MOZ_TELEMETRY_GECKOVIEW)
   // We only support this in GeckoView.
   if (GetCurrentProduct() != SupportedProduct::Geckoview) {
     MOZ_ASSERT(false, "ClearProbes is only supported on GeckoView");
     return NS_ERROR_FAILURE;
   }
 
   // TODO: supporting clear for histograms will come from bug 1457127.
   TelemetryScalar::ClearScalars();
--- a/toolkit/components/telemetry/TelemetryScalar.cpp
+++ b/toolkit/components/telemetry/TelemetryScalar.cpp
@@ -13,17 +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_WIDGET_ANDROID)
+#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"
 
@@ -255,17 +255,17 @@ GetVariantFromIVariant(nsIVariant* aInpu
       }
     default:
       MOZ_ASSERT(false, "Unknown scalar kind.");
       return ScalarResult::UnknownScalar;
   }
   return ScalarResult::Ok;
 }
 
-#if defined(MOZ_WIDGET_ANDROID)
+#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 +297,17 @@ WriteVariantToJSONWriter(uint32_t aScala
       }
     default:
       MOZ_ASSERT(false, "Unknown scalar kind.");
       return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
-#endif // MOZ_WIDGET_ANDROID
+#endif // MOZ_TELEMETRY_GECKOVIEW
 
 // Implements the methods for ScalarInfo.
 const char *
 ScalarInfo::name() const
 {
   return &gScalarsStringTable[this->name_offset];
 }
 
@@ -3079,17 +3079,17 @@ TelemetryScalar::AddDynamicScalarDefinit
   }
 
   {
     StaticMutexAutoLock locker(gTelemetryScalarsMutex);
     internal_RegisterScalars(locker, dynamicStubs);
   }
 }
 
-#if defined(MOZ_WIDGET_ANDROID)
+#if defined(MOZ_TELEMETRY_GECKOVIEW)
 /**
  * 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 +3500,9 @@ TelemetryScalar::DeserializePersistedKey
                                                       mozilla::Get<2>(processScalars[i]),
                                                       ProcessID(iter.Key()));
       }
     }
   }
 
   return NS_OK;
 }
-#endif // MOZ_WIDGET_ANDROID
+#endif // MOZ_TELEMETRY_GECKOVIEW
--- a/toolkit/components/telemetry/TelemetryScalar.h
+++ b/toolkit/components/telemetry/TelemetryScalar.h
@@ -9,19 +9,18 @@
 #include "mozilla/TelemetryScalarEnums.h"
 #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_WIDGET_ANDROID)
+#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;
@@ -88,21 +87,19 @@ void UpdateChildKeyedData(mozilla::Telem
                           const nsTArray<mozilla::Telemetry::KeyedScalarAction>& aScalarActions);
 
 void RecordDiscardedData(mozilla::Telemetry::ProcessID aProcessType,
                          const mozilla::Telemetry::DiscardedData& aDiscardedData);
 
 void GetDynamicScalarDefinitions(nsTArray<mozilla::Telemetry::DynamicScalarDefinition>&);
 void AddDynamicScalarDefinitions(const nsTArray<mozilla::Telemetry::DynamicScalarDefinition>&);
 
-// 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_WIDGET_ANDROID)
+#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_WIDGET_ANDROID
-
+#endif // MOZ_TELEMETRY_GECKOVIEW
 } // namespace TelemetryScalar
 
 #endif // TelemetryScalar_h__
--- a/toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
+++ b/toolkit/components/telemetry/geckoview/TelemetryGeckoViewPersistence.cpp
@@ -406,16 +406,29 @@ PersistenceThreadLoadData()
     ANDROID_LOG("PersistenceThreadLoadData - Failed to load cache file at %s",
                 persistenceFile->HumanReadablePath().get());
     return;
   }
 }
 
 } // anonymous namespace
 
+// This namespace exposes testing only helpers to simplify writing
+// gtest cases.
+namespace TelemetryGeckoViewTesting {
+
+void
+TestDispatchPersist()
+{
+  gPersistenceThread->Dispatch(NS_NewRunnableFunction("Persist",
+    []() -> void { ::PersistenceThreadPersist(); }));
+}
+
+} // GeckoViewTesting
+
 void
 TelemetryGeckoViewPersistence::InitPersistence()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (gPersistenceThread) {
     ANDROID_LOG("Init must only be called once.");
     return;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/gtest/TestGeckoView.cpp
@@ -0,0 +1,346 @@
+/* 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 "gtest/gtest.h"
+#include "mozilla/JSONWriter.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIOutputStream.h"
+#include "nsITelemetry.h"
+#include "nsJSUtils.h"
+#include "nsNetUtil.h"
+#include "prenv.h"
+#include "Telemetry.h"
+#include "TelemetryFixture.h"
+#include "TelemetryGeckoViewPersistence.h"
+#include "TelemetryScalar.h"
+#include "TelemetryTestHelpers.h"
+
+using namespace mozilla;
+using namespace TelemetryTestHelpers;
+
+const char kSampleData[] = R"({
+  "scalars": {
+    "content": {
+      "telemetry.test.all_processes_uint": 37
+    }
+  },
+  "keyedScalars": {
+    "parent": {
+      "telemetry.test.keyed_unsigned_int": {
+        "testKey": 73
+      }
+    }
+  }
+})";
+
+const char16_t kPersistedFilename[] = u"gv_measurements.json";
+
+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.
+ */
+void
+GetMockedDataDir(nsAString& aMockedDir)
+{
+  // Get the OS temporary directory.
+  nsCOMPtr<nsIFile> tmpDir;
+  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+                                       getter_AddRefs(tmpDir));
+  ASSERT_EQ(NS_SUCCEEDED(rv), true);
+  // Return the mocked dir.
+  rv = tmpDir->GetPath(aMockedDir);
+  ASSERT_EQ(NS_SUCCEEDED(rv), true);
+}
+
+void
+MockAndroidDataDir()
+{
+  // Get the OS temporary directory.
+  nsAutoString mockedPath;
+  GetMockedDataDir(mockedPath);
+
+  // Set the environment variable to mock.
+  // Note: we intentionally leak it with |ToNewCString| as PR_SetEnv forces
+  // us to!
+  nsAutoCString mockedEnv(
+    nsPrintfCString("MOZ_ANDROID_DATA_DIR=%s", NS_ConvertUTF16toUTF8(mockedPath).get()));
+  ASSERT_EQ(PR_SetEnv(ToNewCString(mockedEnv)), PR_SUCCESS);
+}
+
+void
+WritePersistenceFile(const nsACString& aData)
+{
+  // Write the file to the temporary directory.
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+                                       getter_AddRefs(file));
+  ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+  // Append the filename and the extension.
+  nsAutoString fileName;
+  fileName.Append(kPersistedFilename);
+  file->Append(fileName);
+
+  nsCOMPtr<nsIOutputStream> stream;
+  rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), file);
+  ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+  uint32_t count;
+  rv = stream->Write(aData.Data(), aData.Length(), &count);
+  // Make sure we wrote correctly.
+  ASSERT_EQ(NS_SUCCEEDED(rv), true);
+  ASSERT_EQ(count, aData.Length());
+
+  stream->Close();
+}
+
+void
+RemovePersistenceFile()
+{
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+                                       getter_AddRefs(file));
+  ASSERT_EQ(NS_SUCCEEDED(rv), true);
+
+  // Append the filename and the extension.
+  nsAutoString fileName;
+  fileName.Append(kPersistedFilename);
+  file->Append(fileName);
+
+  bool exists = true;
+  rv = file->Exists(&exists);
+  ASSERT_EQ(NS_OK, rv) << "nsIFile::Exists cannot fail";
+
+  if (exists) {
+    rv = file->Remove(false);
+    ASSERT_EQ(NS_OK, rv) << "nsIFile::Remove cannot delete the requested file";
+  }
+}
+
+void
+CheckPersistenceFileExists(bool& aFileExists)
+{
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR,
+                                       getter_AddRefs(file));
+  ASSERT_EQ(NS_OK, rv) << "NS_GetSpecialDirectory must return a valid directory";
+
+  // Append the filename and the extension.
+  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)
+{
+  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();
+}
+
+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();
+}
+
+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";
+
+  // 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);
+}
+
+} // 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 TelemetryGeckoViewTesting {
+
+void TestDispatchPersist();
+
+} // TelemetryGeckoViewTesting
+
+/**
+ * Test that corrupted JSON files don't crash the Telemetry core.
+ */
+TEST_F(TelemetryGeckoViewFixture, CorruptedPersistenceFiles) {
+  AutoJSContextWithGlobal cx(mCleanGlobal);
+
+  // Try to load a corrupted file.
+  WritePersistenceFile(NS_LITERAL_CSTRING("{"));
+  TelemetryGeckoViewPersistence::InitPersistence();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  // Cleanup/remove the files.
+  RemovePersistenceFile();
+}
+
+/**
+ * Test that valid and empty JSON files don't crash the Telemetry core.
+ */
+TEST_F(TelemetryGeckoViewFixture, EmptyPersistenceFiles) {
+  AutoJSContextWithGlobal cx(mCleanGlobal);
+
+  // Try to load an empty file/corrupted file.
+  WritePersistenceFile(EmptyCString());
+  TelemetryGeckoViewPersistence::InitPersistence();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  // Cleanup/remove the files.
+  RemovePersistenceFile();
+}
+
+/**
+ * Test that we're able to clear the persistence storage.
+ */
+TEST_F(TelemetryGeckoViewFixture, ClearPersistenceFiles) {
+  AutoJSContextWithGlobal cx(mCleanGlobal);
+
+  bool fileExists = false;
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk";
+
+  WritePersistenceFile(nsDependentCString(kSampleData));
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_TRUE(fileExists) << "We should have written the test persistence file to disk";
+
+  // Init the persistence: this will trigger the measurements to be written
+  // to disk off-the-main thread.
+  TelemetryGeckoViewPersistence::InitPersistence();
+  TelemetryGeckoViewPersistence::ClearPersistenceData();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_FALSE(fileExists) << "ClearPersistenceData must remove the persistence file";
+}
+
+/**
+ * Test that we can correctly persist the data.
+ */
+TEST_F(TelemetryGeckoViewFixture, PersistData) {
+  AutoJSContextWithGlobal cx(mCleanGlobal);
+
+  bool fileExists = false;
+  CheckPersistenceFileExists(fileExists);
+  ASSERT_FALSE(fileExists) << "No persisted measurements must exist on the disk";
+
+  // Init the persistence: this will trigger the measurements to be written
+  // to disk off-the-main thread.
+  TelemetryGeckoViewPersistence::InitPersistence();
+
+  // 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.
+  TelemetryGeckoViewPersistence::InitPersistence();
+  TelemetryGeckoViewPersistence::DeInitPersistence();
+
+  // Cleanup/remove the files.
+  RemovePersistenceFile();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/geckoview/gtest/moz.build
@@ -0,0 +1,25 @@
+# -*- 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('telemetrygeckoviewtest')
+
+LOCAL_INCLUDES += [
+    '../',
+    '../..',
+    '../../..',
+    '/toolkit/components/jsoncpp/include',
+    '/toolkit/components/telemetry/tests/gtest',
+    '/xpcom/io',
+]
+
+DEFINES['MOZ_TELEMETRY_GECKOVIEW'] = True
+
+UNIFIED_SOURCES = [
+    '../TelemetryGeckoViewPersistence.cpp',
+    'TestGeckoView.cpp',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -26,17 +26,22 @@ SPHINX_TREES['telemetry'] = 'docs'
 
 with Files('docs/**'):
     SCHEDULES.exclusive = ['docs']
 
 if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
     CXXFLAGS += ['-Wno-error=shadow']
 
 if CONFIG['ENABLE_TESTS']:
-    DIRS += ['tests/gtest']
+    # We need to use a separate directory for GeckoView gtests. See
+    # the comment below near MOZ_TELEMETRY_GECKOVIEW.
+    DIRS += [
+        'geckoview/gtest',
+        'tests/gtest'
+    ]
 
 TEST_DIRS += ['tests']
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 
 XPIDL_SOURCES += [
     'nsITelemetry.idl',
@@ -193,16 +198,22 @@ processes_data = GENERATED_FILES['Teleme
 processes_data.script = 'gen_process_data.py'
 processes_data.inputs = processes_files
 
 # Add support for GeckoView: please note that building GeckoView
 # implies having an Android build. The packaging step decides
 # which files to include. As a consequence, we can simply only
 # include the GeckoView files on all Android builds.
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+    # Introduce this define to conditionally enable Telemetry GV code in the various
+    # C++ modules. We need this trick in order to run gtest coverage on Treeherder
+    # on platforms other than Android, since gtests on Android are not supported
+    # yet (see bug 1318091).
+    DEFINES['MOZ_TELEMETRY_GECKOVIEW'] = True
+
     SOURCES += [
         'geckoview/TelemetryGeckoViewPersistence.cpp'
     ]
 
     EXTRA_JS_MODULES += [
         'geckoview/GeckoViewTelemetryController.jsm',
     ]
 
copy from toolkit/components/telemetry/tests/gtest/TelemetryFixture.h
copy to toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp
--- a/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h
+++ b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.cpp
@@ -1,65 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-#ifndef TelemetryFixture_h_
-#define TelemetryFixture_h_
-
-#include "mozilla/CycleCollectedJSContext.h"
-#include "mozilla/dom/ScriptSettings.h"
+#include "TelemetryFixture.h"
 #include "mozilla/dom/SimpleGlobalObject.h"
 
 using namespace mozilla;
 
-class TelemetryTestFixture: public ::testing::Test {
-protected:
-  TelemetryTestFixture() : mCleanGlobal(nullptr) {}
-  virtual void SetUp();
-
-  JSObject* mCleanGlobal;
-
-  nsCOMPtr<nsITelemetry> mTelemetry;
-};
-
 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.";
 }
 
-
-// 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:
-  dom::AutoJSAPI mJsAPI;
-  JSContext* mCx;
-};
-
 AutoJSContextWithGlobal::AutoJSContextWithGlobal(JSObject* aGlobalObject)
   : mCx(nullptr)
 {
   // The JS API must initialize correctly.
   MOZ_ALWAYS_TRUE(mJsAPI.Init(aGlobalObject));
 }
 
 JSContext* AutoJSContextWithGlobal::GetJSContext() const
 {
   return mJsAPI.cx();
 }
-
-#endif //TelemetryFixture_h_
--- a/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h
+++ b/toolkit/components/telemetry/tests/gtest/TelemetryFixture.h
@@ -1,65 +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 "nsITelemetry.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/dom/ScriptSettings.h"
-#include "mozilla/dom/SimpleGlobalObject.h"
-
-using namespace mozilla;
 
 class TelemetryTestFixture: public ::testing::Test {
 protected:
   TelemetryTestFixture() : mCleanGlobal(nullptr) {}
   virtual void SetUp();
 
   JSObject* mCleanGlobal;
 
   nsCOMPtr<nsITelemetry> mTelemetry;
 };
 
-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.";
-}
-
-
 // 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:
-  dom::AutoJSAPI mJsAPI;
+  mozilla::dom::AutoJSAPI mJsAPI;
   JSContext* mCx;
 };
 
-AutoJSContextWithGlobal::AutoJSContextWithGlobal(JSObject* aGlobalObject)
-  : mCx(nullptr)
-{
-  // The JS API must initialize correctly.
-  MOZ_ALWAYS_TRUE(mJsAPI.Init(aGlobalObject));
-}
-
-JSContext* AutoJSContextWithGlobal::GetJSContext() const
-{
-  return mJsAPI.cx();
-}
-
 #endif //TelemetryFixture_h_
--- a/toolkit/components/telemetry/tests/gtest/moz.build
+++ b/toolkit/components/telemetry/tests/gtest/moz.build
@@ -6,15 +6,16 @@
 
 Library('telemetrytest')
 
 LOCAL_INCLUDES += [
     '../..',
 ]
 
 UNIFIED_SOURCES = [
+    'TelemetryFixture.cpp',
     'TelemetryTestHelpers.cpp',
     'TestCounters.cpp',
     'TestHistograms.cpp',
     'TestScalars.cpp'
 ]
 
 FINAL_LIBRARY = 'xul-gtest'