Bug 1566367 - [4.0] Add test for onTelemetryReceived. r=chutten,Dexter,snorp
authorAgi Sferro <agi@sferro.dev>
Fri, 09 Aug 2019 04:51:05 +0000
changeset 487159 3f5971e7dc98001ab565319dd74321eb059c8d8f
parent 487158 45d2398750a203b61bad00233031bc98db63c4f4
child 487160 36c3240e5cafd7b57146bab3b177bfa47f42bcfa
push id92126
push userasferro@mozilla.com
push dateFri, 09 Aug 2019 04:52:11 +0000
treeherderautoland@3f5971e7dc98 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerschutten, Dexter, snorp
bugs1566367
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1566367 - [4.0] Add test for onTelemetryReceived. r=chutten,Dexter,snorp Differential Revision: https://phabricator.services.mozilla.com/D40774
mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/background.js
mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-api.js
mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-schema.json
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TelemetryTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java
toolkit/components/telemetry/Histograms.json
--- a/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/background.js
+++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/background.js
@@ -47,16 +47,27 @@ port.onMessage.addListener(async message
       break;
 
     case "GetRequestedLocales":
       {
         const { id } = message;
         sendResponse(id, browser.test.getRequestedLocales());
       }
       break;
+
+    case "AddHistogram":
+      {
+        const {
+          id,
+          args: { id: histogramId, value },
+        } = message;
+        browser.test.addHistogram(histogramId, value);
+        sendResponse(id, null);
+      }
+      break;
   }
 });
 
 function sendResponse(id, response, exception) {
   Promise.resolve(response).then(
     value => sendSyncResponse(id, value),
     reason => sendSyncResponse(id, null, reason)
   );
--- a/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-api.js
+++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-api.js
@@ -99,12 +99,16 @@ this.test = class extends ExtensionAPI {
               { uri, selector }
             );
           });
         },
 
         async getRequestedLocales() {
           return Services.locale.requestedLocales;
         },
+
+        async addHistogram(id, value) {
+          return Services.telemetry.getHistogramById(id).add(value);
+        },
       },
     };
   }
 };
--- a/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-schema.json
+++ b/mobile/android/geckoview/src/androidTest/assets/web_extensions/test-support/test-schema.json
@@ -67,12 +67,28 @@
         ]
       },
       {
         "name": "getRequestedLocales",
         "type": "function",
         "async": true,
         "description": "Gets the requested locales.",
         "parameters": []
+      },
+      {
+        "name": "addHistogram",
+        "type": "function",
+        "async": true,
+        "description": "Add a sample with the given value to the histogram with the given id.",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "id"
+          },
+          {
+            "type": "any",
+            "name": "value"
+          }
+        ]
       }
     ]
   }
 ]
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -22,16 +22,17 @@ import org.mozilla.geckoview.test.rule.G
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 import org.hamcrest.Matchers.*
 import org.json.JSONObject
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule
 import org.mozilla.geckoview.test.util.HttpBin
 import org.mozilla.geckoview.test.util.UiThreadUtils
 import java.net.URI
 
 @RunWith(AndroidJUnit4::class)
@@ -578,16 +579,17 @@ class NavigationDelegateTest : BaseSessi
         sessionRule.session.reload()
         sessionRule.session.waitForPageStop()
 
         innerWidth = sessionRule.session.evaluateJS(innerWidthJs) as Double
         assertThat("innerWidth should be equal to $mobileInnerWidth again",
                 innerWidth, closeTo(mobileInnerWidth, 0.1))
     }
 
+    @Ignore // This test needs to set RuntimeSettings, TODO: Bug 1572245
     @Test fun telemetrySnapshots() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         val telemetry = sessionRule.runtime.telemetry
         val result = sessionRule.waitForResult(telemetry.getSnapshots(false))
 
         assertThat("Snapshots should not be null",
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TelemetryTest.kt
@@ -0,0 +1,56 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.geckoview.test
+
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import android.util.Log
+import org.hamcrest.CoreMatchers.equalTo
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
+import org.mozilla.geckoview.RuntimeTelemetry
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class TelemetryTest : BaseSessionTest() {
+    @Test
+    fun testOnTelemetryReceived() {
+        sessionRule.addExternalDelegateUntilTestEnd(
+            RuntimeTelemetry.Delegate::class,
+                sessionRule::setTelemetryDelegate,
+                { sessionRule.setTelemetryDelegate(null) },
+                object : RuntimeTelemetry.Delegate {}
+        )
+
+        // Let's make sure we batch the two telemetry calls
+        sessionRule.setPrefsUntilTestEnd(
+                mapOf("toolkit.telemetry.geckoview.batchDurationMS" to 100000))
+
+        sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", 401)
+        sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", 12)
+        sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", 1)
+        sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", 109)
+
+        // Forces flushing telemetry data at next histogram
+        sessionRule.setPrefsUntilTestEnd(mapOf("toolkit.telemetry.geckoview.batchDurationMS" to 0))
+
+        val telemetryReceived = GeckoResult<Void>()
+        sessionRule.delegateDuringNextWait(object : RuntimeTelemetry.Delegate {
+            @AssertCalled
+            override fun onTelemetryReceived(metric: RuntimeTelemetry.Metric) {
+                assertThat("Metric name should be correct", metric.name,
+                        equalTo("TELEMETRY_TEST_STREAMING"))
+                assertThat("Metric name should be correct", metric.values,
+                        equalTo(longArrayOf(401, 12, 1, 109, 2000)))
+                telemetryReceived.complete(null)
+            }
+        })
+
+        sessionRule.addHistogram("TELEMETRY_TEST_STREAMING", 2000)
+        sessionRule.waitForResult(telemetryReceived)
+    }
+}
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -11,16 +11,17 @@ import org.json.JSONTokener;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.geckoview.ContentBlocking;
 import org.mozilla.geckoview.GeckoDisplay;
 import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
+import org.mozilla.geckoview.RuntimeTelemetry;
 import org.mozilla.geckoview.SessionTextInput;
 import org.mozilla.geckoview.WebExtension;
 import org.mozilla.geckoview.test.util.HttpBin;
 import org.mozilla.geckoview.test.util.RuntimeCreator;
 import org.mozilla.geckoview.test.util.Environment;
 import org.mozilla.geckoview.test.util.UiThreadUtils;
 import org.mozilla.geckoview.test.util.Callbacks;
 
@@ -546,17 +547,17 @@ public class GeckoSessionTestRule implem
     protected class CallbackDelegates {
         private final Map<Pair<GeckoSession, Method>, MethodCall> mDelegates = new HashMap<>();
         private final List<ExternalDelegate<?>> mExternalDelegates = new ArrayList<>();
         private int mOrder;
         private JSONObject mOldPrefs;
 
         public void delegate(final @Nullable GeckoSession session,
                              final @NonNull Object callback) {
-            for (final Class<?> ifce : DEFAULT_DELEGATES) {
+            for (final Class<?> ifce : mAllDelegates) {
                 if (!ifce.isInstance(callback)) {
                     continue;
                 }
                 assertThat("Cannot delegate null-delegate callbacks",
                            ifce, not(isIn(mNullDelegates)));
                 addDelegatesForInterface(session, callback, ifce);
             }
         }
@@ -854,16 +855,20 @@ public class GeckoSessionTestRule implem
      * Get the runtime set up for the current test.
      *
      * @return GeckoRuntime object.
      */
     public @NonNull GeckoRuntime getRuntime() {
         return RuntimeCreator.getRuntime();
     }
 
+    public void setTelemetryDelegate(RuntimeTelemetry.Delegate delegate) {
+        RuntimeCreator.setTelemetryDelegate(delegate);
+    }
+
     public @Nullable GeckoDisplay getDisplay() {
         return mDisplay;
     }
 
     protected static Object setDelegate(final @NonNull Class<?> cls,
                                         final @NonNull GeckoSession session,
                                         final @Nullable Object delegate)
             throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
@@ -1216,16 +1221,17 @@ public class GeckoSessionTestRule implem
         mAllDelegates = null;
         mNullDelegates = null;
         mCallRecords = null;
         mWaitScopeDelegates = null;
         mTestScopeDelegates = null;
         mLastWaitStart = 0;
         mLastWaitEnd = 0;
         mTimeoutMillis = 0;
+        RuntimeCreator.setTelemetryDelegate(null);
     }
 
     @Override
     public Statement apply(final Statement base, final Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
                 final AtomicReference<Throwable> exceptionRef = new AtomicReference<>();
@@ -2031,16 +2037,34 @@ public class GeckoSessionTestRule implem
             }
 
             return result;
         } catch (JSONException ex) {
             throw new RuntimeException(ex);
         }
     }
 
+    /**
+     * Adds value to the given histogram.
+     *
+     * @param id the histogram id to increment.
+     * @param value to add to the histogram.
+     */
+    public void addHistogram(final String id, final long value) {
+        try {
+            final JSONObject args = new JSONObject();
+            args.put("id", id);
+            args.put("value", value);
+
+            webExtensionApiCall("AddHistogram", args);
+        } catch (JSONException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
     private Object webExtensionApiCall(final String apiName, JSONObject args) throws JSONException {
         // Ensure background script is connected
         UiThreadUtils.waitForCondition(() -> RuntimeCreator.backgroundPort() != null,
                 env.getDefaultTimeoutMillis());
 
         final String id = UUID.randomUUID().toString();
 
         final JSONObject message = new JSONObject();
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/RuntimeCreator.java
@@ -1,13 +1,14 @@
 package org.mozilla.geckoview.test.util;
 
 import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoRuntimeSettings;
+import org.mozilla.geckoview.RuntimeTelemetry;
 import org.mozilla.geckoview.WebExtension;
 import org.mozilla.geckoview.test.TestCrashHandler;
 
 import android.os.Process;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 import android.support.test.InstrumentationRegistry;
@@ -23,16 +24,32 @@ public class RuntimeCreator {
 
     private static GeckoRuntime sRuntime;
     public static AtomicInteger sTestSupport = new AtomicInteger(0);
     public static final WebExtension TEST_SUPPORT_WEB_EXTENSION =
             new WebExtension("resource://android/assets/web_extensions/test-support/",
                     "test-support@mozilla.com",
                     WebExtension.Flags.ALLOW_CONTENT_MESSAGING);
 
+    // The RuntimeTelemetry.Delegate can only be set when creating the RuntimeCreator, to
+    // let tests set their own Delegate we need to create a proxy here.
+    public static class RuntimeTelemetryDelegate implements RuntimeTelemetry.Delegate {
+        public RuntimeTelemetry.Delegate delegate = null;
+
+        @Override
+        public void onTelemetryReceived(@NonNull RuntimeTelemetry.Metric metric) {
+            if (delegate != null) {
+                delegate.onTelemetryReceived(metric);
+            }
+        }
+    }
+
+    public static final RuntimeTelemetryDelegate sRuntimeTelemetryProxy =
+            new RuntimeTelemetryDelegate();
+
     private static WebExtension.Port sBackgroundPort;
 
     private static WebExtension.PortDelegate sPortDelegate;
 
     private static WebExtension.MessageDelegate sMessageDelegate
             = new WebExtension.MessageDelegate() {
         @Nullable
         @Override
@@ -61,39 +78,51 @@ public class RuntimeCreator {
                 .accept(value -> {
                     sTestSupport.set(TEST_SUPPORT_OK);
                 }, exception -> {
                     Log.e(LOGTAG, "Could not register TestSupport", exception);
                     sTestSupport.set(TEST_SUPPORT_ERROR);
                 });
     }
 
+    /**
+     * Set the {@link RuntimeTelemetry.Delegate} instance for this test. Application code can only
+     * register this delegate when the {@link GeckoRuntime} is created, so we need to proxy it
+     * for test code.
+     *
+     * @param delegate the {@link RuntimeTelemetry.Delegate} for this test run.
+     */
+    public static void setTelemetryDelegate(RuntimeTelemetry.Delegate delegate) {
+        sRuntimeTelemetryProxy.delegate = delegate;
+    }
+
     public static void setPortDelegate(WebExtension.PortDelegate portDelegate) {
         sPortDelegate = portDelegate;
     }
 
     @UiThread
     public static GeckoRuntime getRuntime() {
         if (sRuntime != null) {
             return sRuntime;
         }
 
-        final GeckoRuntimeSettings.Builder runtimeSettingsBuilder =
-                new GeckoRuntimeSettings.Builder();
-        runtimeSettingsBuilder.arguments(new String[]{"-purgecaches"})
+        TEST_SUPPORT_WEB_EXTENSION.setMessageDelegate(sMessageDelegate, "browser");
+
+        final GeckoRuntimeSettings runtimeSettings = new GeckoRuntimeSettings.Builder()
+                .arguments(new String[]{"-purgecaches"})
                 .extras(InstrumentationRegistry.getArguments())
                 .remoteDebuggingEnabled(true)
                 .consoleOutput(true)
-                .crashHandler(TestCrashHandler.class);
-
-        TEST_SUPPORT_WEB_EXTENSION.setMessageDelegate(sMessageDelegate, "browser");
+                .crashHandler(TestCrashHandler.class)
+                .telemetryDelegate(sRuntimeTelemetryProxy)
+                .build();
 
         sRuntime = GeckoRuntime.create(
                 InstrumentationRegistry.getTargetContext(),
-                runtimeSettingsBuilder.build());
+                runtimeSettings);
 
         registerTestSupport();
 
         sRuntime.setDelegate(() -> Process.killProcess(Process.myPid()));
 
         return sRuntime;
     }
 }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8920,16 +8920,17 @@
     "record_in_processes": ["main"],
     "products": ["geckoview_streaming"],
     "alert_emails": ["telemetry-client-dev@mozilla.com"],
     "expires_in_version": "never",
     "kind": "linear",
     "low": 1,
     "high": 2147483646,
     "n_buckets": 10,
+    "releaseChannelCollection": "opt-out",
     "bug_numbers": [1566366],
     "description": "a testing histogram; not meant to be touched"
   },
   "STARTUP_CRASH_DETECTED": {
     "record_in_processes": ["main", "content"],
     "products": ["firefox", "fennec", "geckoview"],
     "expires_in_version": "never",
     "kind": "flag",