Commit 9726108a authored by Tiger Oakes's avatar Tiger Oakes Committed by Tiger Oakes
Browse files

Issue #3481 - Support compling to Q in Glean

parent 999f9b1e
......@@ -4,19 +4,18 @@
package mozilla.components.service.glean.storages
import mozilla.components.service.glean.Glean
import android.annotation.SuppressLint
import android.content.Context
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.Dispatchers as KotlinDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import mozilla.components.service.glean.Dispatchers
import mozilla.components.service.glean.error.ErrorRecording.recordError
import mozilla.components.service.glean.Glean
import mozilla.components.service.glean.error.ErrorRecording.ErrorType
import mozilla.components.service.glean.error.ErrorRecording.recordError
import mozilla.components.service.glean.private.EventMetricType
import mozilla.components.service.glean.private.Lifetime
import mozilla.components.service.glean.utils.ensureDirectoryExists
......@@ -26,6 +25,7 @@ import org.json.JSONException
import org.json.JSONObject
import java.io.File
import java.io.IOException
import kotlinx.coroutines.Dispatchers as KotlinDispatchers
/**
* This singleton handles the in-memory storage logic for events. It is meant to be used by
......@@ -283,9 +283,10 @@ internal object EventsStorageEngine : StorageEngine {
private fun deserializeEvent(jsonContent: JSONObject): RecordedEventData {
val extra: Map<String, String>? = jsonContent.optJSONObject(EXTRA_FIELD)?.let {
val extraValues: MutableMap<String, String> = mutableMapOf()
val names = it.names()
for (i in 0..(names.length() - 1)) {
extraValues[names.getString(i)] = it.getString(names.getString(i))
it.names()?.let { names ->
for (i in 0 until names.length()) {
extraValues[names.getString(i)] = it.getString(names.getString(i))
}
}
extraValues
}
......
......@@ -201,7 +201,7 @@ internal abstract class GenericStorageEngine<MetricType> : StorageEngine {
*/
override fun getSnapshotAsJSON(storeName: String, clearStore: Boolean): Any? {
return getSnapshot(storeName, clearStore)?.let { dataMap ->
return JSONObject(dataMap)
return JSONObject(dataMap as MutableMap<*, *>)
}
}
......
......@@ -193,12 +193,13 @@ internal open class TimespansStorageEngineImplementation(
*/
override fun getSnapshotAsJSON(storeName: String, clearStore: Boolean): Any? {
return getSnapshotWithTimeUnit(storeName, clearStore)?.let { dataMap ->
return JSONObject(dataMap.mapValuesTo(mutableMapOf<String, JSONObject>()) {
val data = dataMap.mapValuesTo(mutableMapOf()) {
JSONObject(mapOf(
"time_unit" to it.value.first,
"value" to it.value.second
))
})
}
return JSONObject(data as MutableMap<*, *>)
}
}
......
......@@ -152,28 +152,28 @@ class GleanTest {
// Trigger worker task to upload the pings in the background
triggerWorkManager()
val requests: MutableMap<String, String> = mutableMapOf()
val requests = mutableMapOf<String, String>()
for (i in 0..1) {
val request = server.takeRequest(20L, TimeUnit.SECONDS)
val docType = request.path.split("/")[3]
requests.set(docType, request.body.readUtf8())
requests[docType] = request.body.readUtf8()
}
val eventsJson = JSONObject(requests["events"])
val eventsJson = JSONObject(requests["events"]!!)
checkPingSchema(eventsJson)
assertEquals("events", eventsJson.getJSONObject("ping_info")["ping_type"])
assertEquals(1, eventsJson.getJSONArray("events")!!.length())
assertEquals(1, eventsJson.getJSONArray("events").length())
val baselineJson = JSONObject(requests["baseline"])
val baselineJson = JSONObject(requests["baseline"]!!)
assertEquals("baseline", baselineJson.getJSONObject("ping_info")["ping_type"])
checkPingSchema(baselineJson)
val baselineMetricsObject = baselineJson.getJSONObject("metrics")!!
val baselineStringMetrics = baselineMetricsObject.getJSONObject("string")!!
val baselineMetricsObject = baselineJson.getJSONObject("metrics")
val baselineStringMetrics = baselineMetricsObject.getJSONObject("string")
assertEquals(1, baselineStringMetrics.length())
assertNotNull(baselineStringMetrics.get("glean.baseline.locale"))
val baselineTimespanMetrics = baselineMetricsObject.getJSONObject("timespan")!!
val baselineTimespanMetrics = baselineMetricsObject.getJSONObject("timespan")
assertEquals(1, baselineTimespanMetrics.length())
assertNotNull(baselineTimespanMetrics.get("glean.baseline.duration"))
} finally {
......@@ -261,7 +261,7 @@ class GleanTest {
// We should only have a baseline ping and no events or metrics pings since nothing was
// recorded
val files = Glean.pingStorageEngine.storageDirectory.listFiles()
val files = Glean.pingStorageEngine.storageDirectory.listFiles()!!
// Make sure only the baseline ping is present and no events or metrics pings
assertEquals(1, files.count())
......@@ -500,8 +500,8 @@ class GleanTest {
assertEquals(pingName, pingJson.getJSONObject("ping_info")["ping_type"])
checkPingSchema(pingJson)
val pingMetricsObject = pingJson.getJSONObject("metrics")!!
val pingStringMetrics = pingMetricsObject.getJSONObject("string")!!
val pingMetricsObject = pingJson.getJSONObject("metrics")
val pingStringMetrics = pingMetricsObject.getJSONObject("string")
assertEquals(1, pingStringMetrics.length())
assertEquals(testValue, pingStringMetrics.get("telemetry.string_metric"))
}
......
......@@ -12,13 +12,12 @@ import mozilla.components.service.glean.storages.MockStorageEngine
import mozilla.components.service.glean.storages.StorageEngineManager
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Test
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.robolectric.RobolectricTestRunner
......@@ -56,7 +55,7 @@ class PingMakerTest {
)
// Gather the data. We expect an empty ping with the "ping_info" information
val data = maker.collect(customPing)
val data = maker.collect(customPing).orEmpty()
assertTrue("We expect a non-empty JSON blob", "{}" != data)
// Parse the data so that we can easily check the other fields
......@@ -86,7 +85,7 @@ class PingMakerTest {
)
// Gather the data. We expect an empty ping with the "ping_info" information
val data = maker.collect(customPing)
val data = maker.collect(customPing).orEmpty()
assertTrue("We expect a non-empty JSON blob", "{}" != data)
// Parse the data so that we can easily check the other fields
......@@ -112,7 +111,7 @@ class PingMakerTest {
)
// Gather the data. We expect an empty ping with the "ping_info" information
val data = maker.collect(customPing)
val data = maker.collect(customPing).orEmpty()
assertTrue("We expect a non-empty JSON blob", "{}" != data)
// Parse the data so that we can easily check the other fields
......@@ -140,7 +139,7 @@ class PingMakerTest {
// Gather the data, this should have everything in the 'test' ping which is the default
// storex
val data = maker.collect(customPing)
val data = maker.collect(customPing).orEmpty()
assertNotNull("We expect a non-null JSON blob", data)
// Parse the data so that we can easily check the other fields
......@@ -208,7 +207,7 @@ class PingMakerTest {
name = pingName,
includeClientId = true
)
val data = maker.collect(ping)
val data = maker.collect(ping).orEmpty()
val jsonData = JSONObject(data)
val pingInfo = jsonData["ping_info"] as JSONObject
val seqNum = pingInfo.getInt("seq")
......
......@@ -38,14 +38,14 @@ class DatetimeMetricTypeTest {
sendInPings = listOf("store1")
)
val value = Calendar.getInstance()!!
val value = Calendar.getInstance()
value.set(2004, 11, 9, 8, 3, 29)
value.timeZone = TimeZone.getTimeZone("America/Los_Angeles")
datetimeMetric.set(value)
assertTrue(datetimeMetric.testHasValue())
assertEquals("2004-12-09T08:03-08:00", datetimeMetric.testGetValueAsString())
val value2 = Calendar.getInstance()!!
val value2 = Calendar.getInstance()
value2.set(1993, 1, 23, 9, 5, 43)
value2.timeZone = TimeZone.getTimeZone("GMT+0")
datetimeMetric.set(value2)
......@@ -54,7 +54,7 @@ class DatetimeMetricTypeTest {
assertEquals("1993-02-23T09:05+00:00", datetimeMetric.testGetValueAsString())
// A date prior to the UNIX epoch
val value3 = Calendar.getInstance()!!
val value3 = Calendar.getInstance()
value3.set(1969, 7, 20, 20, 17, 3)
value3.timeZone = TimeZone.getTimeZone("GMT-12")
datetimeMetric.set(value3)
......@@ -65,7 +65,7 @@ class DatetimeMetricTypeTest {
// A date following 2038 (the extent of signed 32-bits after UNIX epoch)
// This fails on some workers on Taskcluster. 32-bit platforms, perhaps?
// val value4 = Calendar.getInstance()!!
// val value4 = Calendar.getInstance()
// value4.set(2039, 7, 20, 20, 17, 3)
// datetimeMetric.set(value4)
// // Check that data was properly recorded.
......
......@@ -9,6 +9,8 @@ import android.content.SharedPreferences
import androidx.test.core.app.ApplicationProvider
import mozilla.components.service.glean.GleanMetrics.Pings
import mozilla.components.service.glean.collectAndCheckPingSchema
import mozilla.components.service.glean.error.ErrorRecording
import mozilla.components.service.glean.resetGlean
import mozilla.components.service.glean.storages.BooleansStorageEngine
import mozilla.components.service.glean.storages.CountersStorageEngine
import mozilla.components.service.glean.storages.MockGenericStorageEngine
......@@ -16,15 +18,12 @@ import mozilla.components.service.glean.storages.StringListsStorageEngine
import mozilla.components.service.glean.storages.StringsStorageEngine
import mozilla.components.service.glean.storages.TimespansStorageEngine
import mozilla.components.service.glean.storages.UuidsStorageEngine
import mozilla.components.service.glean.error.ErrorRecording
import mozilla.components.service.glean.resetGlean
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import java.util.UUID
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.`when`
......@@ -33,6 +32,7 @@ import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.robolectric.RobolectricTestRunner
import java.util.UUID
@RunWith(RobolectricTestRunner::class)
class LabeledMetricTypeTest {
......@@ -81,27 +81,27 @@ class LabeledMetricTypeTest {
val snapshot = CountersStorageEngine.getSnapshot(storeName = "metrics", clearStore = false)
assertEquals(3, snapshot!!.size)
assertEquals(1, snapshot.get("telemetry.labeled_counter_metric/label1"))
assertEquals(2, snapshot.get("telemetry.labeled_counter_metric/label2"))
assertEquals(3, snapshot.get("telemetry.labeled_counter_metric"))
assertEquals(1, snapshot["telemetry.labeled_counter_metric/label1"])
assertEquals(2, snapshot["telemetry.labeled_counter_metric/label2"])
assertEquals(3, snapshot["telemetry.labeled_counter_metric"])
val json = collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
val json = collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
// Do the same checks again on the JSON structure
assertEquals(
1,
json.getJSONObject("labeled_counter")!!
.getJSONObject("telemetry.labeled_counter_metric")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("label1")
)
assertEquals(
2,
json.getJSONObject("labeled_counter")!!
.getJSONObject("telemetry.labeled_counter_metric")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("label2")
)
assertEquals(
3,
json.getJSONObject("counter")!!
json.getJSONObject("counter")
.get("telemetry.labeled_counter_metric")
)
}
......@@ -143,23 +143,23 @@ class LabeledMetricTypeTest {
assertNull(snapshot.get("telemetry.labeled_counter_metric/baz"))
assertEquals(3, snapshot.get("telemetry.labeled_counter_metric/__other__"))
val json = collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
val json = collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
// Do the same checks again on the JSON structure
assertEquals(
2,
json.getJSONObject("labeled_counter")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("foo")
)
assertEquals(
1,
json.getJSONObject("labeled_counter")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("bar")
)
assertEquals(
3,
json.getJSONObject("labeled_counter")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("__other__")
)
......@@ -201,26 +201,26 @@ class LabeledMetricTypeTest {
}
assertEquals(5, snapshot.get("telemetry.labeled_counter_metric/__other__"))
val json = collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
val json = collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
// Do the same checks again on the JSON structure
assertEquals(
2,
json.getJSONObject("labeled_counter")!!
.getJSONObject("telemetry.labeled_counter_metric")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("label_0")
)
for (i in 1..15) {
assertEquals(
1,
json.getJSONObject("labeled_counter")!!
.getJSONObject("telemetry.labeled_counter_metric")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("label_$i")
)
}
assertEquals(
5,
json.getJSONObject("labeled_counter")!!
.getJSONObject("telemetry.labeled_counter_metric")!!
json.getJSONObject("labeled_counter")
.getJSONObject("telemetry.labeled_counter_metric")
.get("__other__")
)
}
......@@ -349,7 +349,7 @@ class LabeledMetricTypeTest {
assertTrue(labeledTimespanMetric["label1"].testHasValue())
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
}
@Test
......@@ -374,7 +374,7 @@ class LabeledMetricTypeTest {
UuidsStorageEngine.record(labeledUuidMetric["label1"], UUID.randomUUID())
UuidsStorageEngine.record(labeledUuidMetric["label2"], UUID.randomUUID())
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
}
@Test
......@@ -401,7 +401,7 @@ class LabeledMetricTypeTest {
StringListsStorageEngine.set(labeledStringListMetric["label1"], listOf("a", "b", "c"))
StringListsStorageEngine.set(labeledStringListMetric["label2"], listOf("a", "b", "c"))
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
}
@Test
......@@ -426,7 +426,7 @@ class LabeledMetricTypeTest {
StringsStorageEngine.record(labeledStringMetric["label1"], "foo")
StringsStorageEngine.record(labeledStringMetric["label2"], "bar")
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
}
@Test
......@@ -453,7 +453,7 @@ class LabeledMetricTypeTest {
BooleansStorageEngine.record(labeledBooleanMetric["label1"], false)
BooleansStorageEngine.record(labeledBooleanMetric["label2"], true)
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")!!
collectAndCheckPingSchema(Pings.metrics).getJSONObject("metrics")
}
@Test(expected = IllegalStateException::class)
......
......@@ -6,8 +6,8 @@ package mozilla.components.service.glean.storages
import android.content.Context
import android.content.SharedPreferences
import androidx.test.core.app.ApplicationProvider
import mozilla.components.service.glean.private.Lifetime
import mozilla.components.service.glean.private.DatetimeMetricType
import mozilla.components.service.glean.private.Lifetime
import mozilla.components.service.glean.private.TimeUnit
import mozilla.components.service.glean.resetGlean
import org.junit.Assert.assertEquals
......@@ -72,7 +72,7 @@ class DatetimesStorageEngineTest {
@Test
fun `datetime serializer should correctly serialize datetimes`() {
val value = Calendar.getInstance()!!
val value = Calendar.getInstance()
value.set(1993, 1, 23, 9, 5, 23)
value.set(Calendar.MILLISECOND, 0)
value.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
......@@ -173,7 +173,7 @@ class DatetimesStorageEngineTest {
@Test
fun `test that truncation works`() {
val value = Calendar.getInstance()!!
val value = Calendar.getInstance()
value.set(2004, 11, 9, 8, 3, 29)
value.set(Calendar.MILLISECOND, 320)
value.timeZone = TimeZone.getTimeZone("GMT-2")
......@@ -240,42 +240,42 @@ class DatetimesStorageEngineTest {
// Calendar objects don't have nanosecond resolution, so we don't expect any change,
// but test anyway so we're testing all of the TimeUnit enumeration values.
val expectedNanosecond = Calendar.getInstance()!!
val expectedNanosecond = Calendar.getInstance()
expectedNanosecond.timeZone = TimeZone.getTimeZone("GMT-2")
expectedNanosecond.set(2004, 11, 9, 8, 3, 29)
expectedNanosecond.set(Calendar.MILLISECOND, 320)
assertEquals(expectedNanosecond.getTime(), datetimeNanosecondTruncation.testGetValue())
assertEquals("2004-12-09T08:03:29.320-02:00", datetimeNanosecondTruncation.testGetValueAsString())
val expectedMillisecond = Calendar.getInstance()!!
val expectedMillisecond = Calendar.getInstance()
expectedMillisecond.timeZone = TimeZone.getTimeZone("GMT-2")
expectedMillisecond.set(2004, 11, 9, 8, 3, 29)
expectedMillisecond.set(Calendar.MILLISECOND, 320)
assertEquals(expectedMillisecond.getTime(), datetimeMillisecondTruncation.testGetValue())
assertEquals("2004-12-09T08:03:29.320-02:00", datetimeMillisecondTruncation.testGetValueAsString())
val expectedSecond = Calendar.getInstance()!!
val expectedSecond = Calendar.getInstance()
expectedSecond.timeZone = TimeZone.getTimeZone("GMT-2")
expectedSecond.set(2004, 11, 9, 8, 3, 29)
expectedSecond.set(Calendar.MILLISECOND, 0)
assertEquals(expectedSecond.getTime(), datetimeSecondTruncation.testGetValue())
assertEquals("2004-12-09T08:03:29-02:00", datetimeSecondTruncation.testGetValueAsString())
val expectedMinute = Calendar.getInstance()!!
val expectedMinute = Calendar.getInstance()
expectedMinute.timeZone = TimeZone.getTimeZone("GMT-2")
expectedMinute.set(2004, 11, 9, 8, 3, 0)
expectedMinute.set(Calendar.MILLISECOND, 0)
assertEquals(expectedMinute.getTime(), datetimeMinuteTruncation.testGetValue())
assertEquals("2004-12-09T08:03-02:00", datetimeMinuteTruncation.testGetValueAsString())
val expectedHour = Calendar.getInstance()!!
val expectedHour = Calendar.getInstance()
expectedHour.timeZone = TimeZone.getTimeZone("GMT-2")
expectedHour.set(2004, 11, 9, 8, 0, 0)
expectedHour.set(Calendar.MILLISECOND, 0)
assertEquals(expectedHour.getTime(), datetimeHourTruncation.testGetValue())
assertEquals("2004-12-09T08-02:00", datetimeHourTruncation.testGetValueAsString())
val expectedDay = Calendar.getInstance()!!
val expectedDay = Calendar.getInstance()
expectedDay.timeZone = TimeZone.getTimeZone("GMT-2")
expectedDay.set(2004, 11, 9, 0, 0, 0)
expectedDay.set(Calendar.MILLISECOND, 0)
......
......@@ -6,25 +6,25 @@ package mozilla.components.service.glean.storages
import android.os.SystemClock
import androidx.test.core.app.ApplicationProvider
import androidx.work.testing.WorkManagerTestInitHelper
import mozilla.components.service.glean.Glean
import mozilla.components.service.glean.checkPingSchema
import mozilla.components.service.glean.error.ErrorRecording.ErrorType
import mozilla.components.service.glean.error.ErrorRecording.testGetNumRecordedErrors
import mozilla.components.service.glean.private.Lifetime
import mozilla.components.service.glean.private.EventMetricType
import mozilla.components.service.glean.getContextWithMockedInfo
import mozilla.components.service.glean.Glean
import mozilla.components.service.glean.getMockWebServer
import mozilla.components.service.glean.private.EventMetricType
import mozilla.components.service.glean.private.Lifetime
import mozilla.components.service.glean.private.NoExtraKeys
import mozilla.components.service.glean.resetGlean
import mozilla.components.service.glean.triggerWorkManager
import org.json.JSONObject
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.util.concurrent.TimeUnit
......@@ -183,12 +183,12 @@ class EventsStorageEngineTest {
// Check that getting a new snapshot for "store1" returns an empty store.
assertNull("The engine must report 'null' on empty stores",
EventsStorageEngine.getSnapshot(storeName = "store1", clearStore = false))
val files = EventsStorageEngine.storageDirectory.listFiles()
val files = EventsStorageEngine.storageDirectory.listFiles()!!
assertEquals(1, files.size)
assertEquals(
"There should be no events on disk for store1, but there are for store2",
"store2",
EventsStorageEngine.storageDirectory.listFiles().first().name
EventsStorageEngine.storageDirectory.listFiles()?.first()?.name
)
// Check that we get the right data from both the stores. Clearing "store1" must
......@@ -291,7 +291,7 @@ class EventsStorageEngineTest {
assert(request.path.startsWith("/submit/$applicationId/events/${Glean.SCHEMA_VERSION}/"))
val eventsJsonData = request.body.readUtf8()
val eventsJson = checkPingSchema(eventsJsonData)
val eventsArray = eventsJson.getJSONArray("events")!!
val eventsArray = eventsJson.getJSONArray("events")
assertEquals(500, eventsArray.length())
for (i in 0..499) {
......@@ -347,7 +347,7 @@ class EventsStorageEngineTest {
assertEquals(
"There should be no events on disk to start",
0,
EventsStorageEngine.storageDirectory.listFiles().size
EventsStorageEngine.storageDirectory.listFiles()?.size
)
val server = getMockWebServer()
......@@ -456,7 +456,7 @@ class EventsStorageEngineTest {
assertNotNull(pingJson.opt("events"))
val events = pingJson.getJSONArray("events")
assertEquals(2, events.length())
assertEquals("bar", events.getJSONObject(0)!!.getJSONObject("extra")!!.getString("key1"))
assertEquals("baz", events.getJSONObject(1)!!.getJSONObject("extra")!!.getString("key1"))
assertEquals("bar", events.getJSONObject(0).getJSONObject("extra").getString("key1"))
assertEquals("baz", events.getJSONObject(1).getJSONObject("extra").getString("key1"))
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment