You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
6.3 KiB
Kotlin
172 lines
6.3 KiB
Kotlin
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.fenix.glean
|
|
|
|
import android.content.Context
|
|
import androidx.test.core.app.ApplicationProvider
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
import androidx.test.platform.app.InstrumentationRegistry
|
|
import androidx.test.rule.ActivityTestRule
|
|
import androidx.test.uiautomator.UiDevice
|
|
import androidx.test.uiautomator.UiSelector
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.GlobalScope
|
|
import kotlinx.coroutines.launch
|
|
import mozilla.components.browser.engine.gecko.fetch.GeckoViewFetchClient
|
|
import mozilla.components.concept.fetch.Client
|
|
import mozilla.components.service.glean.Glean
|
|
import mozilla.components.service.glean.config.Configuration
|
|
import mozilla.components.service.glean.net.ConceptFetchHttpUploader
|
|
import mozilla.components.service.glean.testing.GleanTestLocalServer
|
|
import okhttp3.mockwebserver.RecordedRequest
|
|
import org.json.JSONObject
|
|
import org.junit.Assert.assertTrue
|
|
import org.junit.Assert.assertFalse
|
|
import org.junit.BeforeClass
|
|
import org.junit.Rule
|
|
import org.junit.Test
|
|
import org.junit.runner.RunWith
|
|
import org.mozilla.fenix.HomeActivity
|
|
import org.mozilla.fenix.R
|
|
import org.mozilla.fenix.helpers.HomeActivityTestRule
|
|
import org.mozilla.fenix.helpers.MockWebServerHelper
|
|
import java.util.concurrent.TimeUnit
|
|
import java.io.BufferedReader
|
|
import java.io.ByteArrayInputStream
|
|
import java.util.zip.GZIPInputStream
|
|
|
|
/**
|
|
* Decompress the GZIP returned by the glean-core layer.
|
|
*
|
|
* @param data the gzipped [ByteArray] to decompress
|
|
* @return a [String] containing the uncompressed data.
|
|
*/
|
|
fun decompressGZIP(data: ByteArray): String {
|
|
return GZIPInputStream(ByteArrayInputStream(data)).bufferedReader().use(BufferedReader::readText)
|
|
}
|
|
|
|
/**
|
|
* Convenience method to get the body of a request as a String.
|
|
* The UTF8 representation of the request body will be returned.
|
|
* If the request body is gzipped, it will be decompressed first.
|
|
*
|
|
* @return a [String] containing the body of the request.
|
|
*/
|
|
fun RecordedRequest.getPlainBody(): String {
|
|
return if (this.getHeader("Content-Encoding") == "gzip") {
|
|
val bodyInBytes = this.body.readByteArray()
|
|
decompressGZIP(bodyInBytes)
|
|
} else {
|
|
this.body.readUtf8()
|
|
}
|
|
}
|
|
|
|
@RunWith(AndroidJUnit4::class)
|
|
class BaselinePingTest {
|
|
private val server = MockWebServerHelper.createAlwaysOkMockWebServer()
|
|
|
|
@get:Rule
|
|
val activityRule: ActivityTestRule<HomeActivity> = HomeActivityTestRule()
|
|
|
|
@get:Rule
|
|
val gleanRule = GleanTestLocalServer(ApplicationProvider.getApplicationContext(), server.port)
|
|
|
|
companion object {
|
|
@BeforeClass
|
|
@JvmStatic
|
|
fun setupOnce() {
|
|
val httpClient = ConceptFetchHttpUploader(lazy {
|
|
GeckoViewFetchClient(ApplicationProvider.getApplicationContext()) as Client
|
|
})
|
|
|
|
// Fenix does not initialize the Glean SDK in tests/debug builds, but this test
|
|
// requires Glean to be initialized so we need to do it manually. Additionally,
|
|
// we need to do this on the main thread, as the Glean SDK requires it.
|
|
GlobalScope.launch(Dispatchers.Main.immediate) {
|
|
Glean.initialize(
|
|
ApplicationProvider.getApplicationContext(),
|
|
true,
|
|
Configuration(httpClient = httpClient)
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for a specific ping to be received by the local server and
|
|
* return its parsed JSON content.
|
|
*
|
|
* @param pingName the name of the ping to wait for
|
|
* @param pingReason the value of the `reason` field for the received ping
|
|
* @param maxAttempts how many times should a wait be attempted
|
|
*/
|
|
private fun waitForPingContent(
|
|
pingName: String,
|
|
pingReason: String?,
|
|
maxAttempts: Int = 3
|
|
): JSONObject? {
|
|
var attempts = 0
|
|
do {
|
|
attempts += 1
|
|
val request = server.takeRequest(20L, TimeUnit.SECONDS) ?: break
|
|
val docType = request.path.split("/")[3]
|
|
if (pingName == docType) {
|
|
val parsedPayload = JSONObject(request.getPlainBody())
|
|
if (pingReason == null) {
|
|
return parsedPayload
|
|
}
|
|
|
|
// If we requested a specific ping reason, look for it.
|
|
val reason = parsedPayload.getJSONObject("ping_info").getString("reason")
|
|
if (reason == pingReason) {
|
|
return parsedPayload
|
|
}
|
|
}
|
|
} while (attempts < maxAttempts)
|
|
|
|
return null
|
|
}
|
|
|
|
@Test
|
|
fun validateBaselinePing() {
|
|
// Wait for the app to be idle/ready.
|
|
InstrumentationRegistry.getInstrumentation().waitForIdleSync()
|
|
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
|
device.waitForIdle()
|
|
|
|
// Wait for 1 second: this should guarantee we have some valid duration in the
|
|
// ping.
|
|
Thread.sleep(1000)
|
|
|
|
// Move it to background.
|
|
device.pressHome()
|
|
|
|
// Due to bug 1632184, we need move the activity to foreground again, in order
|
|
// for a 'background' ping with reason 'foreground' to be generated and also trigger
|
|
// sending the ping that was submitted on background. This can go away once bug 1634375
|
|
// is fixed.
|
|
device.pressRecentApps()
|
|
device.findObject(UiSelector().descriptionContains(
|
|
ApplicationProvider.getApplicationContext<Context>().getString(R.string.app_name)))
|
|
.click()
|
|
|
|
// Validate the received data.
|
|
val baselinePing = waitForPingContent("baseline", "background")!!
|
|
|
|
val metrics = baselinePing.getJSONObject("metrics")
|
|
|
|
// Make sure we have a 'duration' field with a reasonable value: it should be >= 1, since
|
|
// we slept for 1000ms.
|
|
val timespans = metrics.getJSONObject("timespan")
|
|
assertTrue(timespans.getJSONObject("glean.baseline.duration").getLong("value") >= 1L)
|
|
|
|
// Make sure there's no errors.
|
|
val errors = metrics.optJSONObject("labeled_counter")?.keys()
|
|
errors?.forEach {
|
|
assertFalse(it.startsWith("glean.error."))
|
|
}
|
|
}
|
|
}
|