From 3be6667960ce7d23a6994a73f861a2596d7c8f22 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 27 Oct 2023 22:09:40 +0200 Subject: [PATCH 01/14] tests: add setup for functional tests - add dependencies - add test scaffold - introduce an empty, test-only activity --- parsely/build.gradle | 9 +++++++ .../parsely/parselyandroid/FunctionalTests.kt | 24 +++++++++++++++++++ parsely/src/debug/AndroidManifest.xml | 11 +++++++++ 3 files changed, 44 insertions(+) create mode 100644 parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt create mode 100644 parsely/src/debug/AndroidManifest.xml diff --git a/parsely/build.gradle b/parsely/build.gradle index b0767cf6..71d13a01 100644 --- a/parsely/build.gradle +++ b/parsely/build.gradle @@ -10,6 +10,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 33 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -22,6 +24,7 @@ android { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' } + testBuildType "debug" publishing { singleVariant('release') { @@ -58,6 +61,12 @@ dependencies { testImplementation 'org.assertj:assertj-core:3.24.2' testImplementation 'junit:junit:4.13.2' testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' + + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test:rules:1.5.0' + androidTestImplementation 'org.assertj:assertj-core:3.24.2' + androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' + androidTestImplementation 'androidx.test:runner:1.5.2' } apply from: "${rootProject.projectDir}/publication.gradle" diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt new file mode 100644 index 00000000..c2ab8312 --- /dev/null +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -0,0 +1,24 @@ +package com.parsely.parselyandroid + +import android.app.Activity +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class FunctionalTests { + + private lateinit var parselyTracker: ParselyTracker + + @Test + fun appTracksEventsAboveQueueSizeLimit() { + ActivityScenario.launch(SampleActivity::class.java).use { scenario -> + scenario.onActivity { activity: Activity -> + } + } + } + + class SampleActivity : Activity() +} diff --git a/parsely/src/debug/AndroidManifest.xml b/parsely/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..e1811077 --- /dev/null +++ b/parsely/src/debug/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + From ed56a27b99ef0c45cdcd466516f089b780cf910a Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 27 Oct 2023 22:18:18 +0200 Subject: [PATCH 02/14] tests: initialize ParselyTracker in functional test With address of mocked server as `ROOT_URL`, applied via reflection. `ROOT_URL` *must not* be a plain declaration, because otherwise the compiler will *inline* the value of the constant, and we won't be able to change it, even via reflection, for the functional test. --- .../parsely/parselyandroid/FunctionalTests.kt | 27 +++++++++++++++++++ .../parselyandroid/ParselyTracker.java | 5 +++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index c2ab8312..6903ed3e 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -3,6 +3,10 @@ package com.parsely.parselyandroid import android.app.Activity import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 +import java.lang.reflect.Field +import kotlin.time.Duration.Companion.seconds +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer import org.junit.Test import org.junit.runner.RunWith @@ -11,14 +15,37 @@ import org.junit.runner.RunWith class FunctionalTests { private lateinit var parselyTracker: ParselyTracker + private val server = MockWebServer() + private val url = server.url("/").toString() @Test fun appTracksEventsAboveQueueSizeLimit() { ActivityScenario.launch(SampleActivity::class.java).use { scenario -> scenario.onActivity { activity: Activity -> + server.enqueue(MockResponse().setResponseCode(200)) + parselyTracker = initializeTracker(activity) + + parselyTracker.trackPageview("url", null, null, null) + + server.takeRequest() } } } + private fun initializeTracker(activity: Activity): ParselyTracker { + return ParselyTracker.sharedInstance( + siteId, flushInterval.inWholeSeconds.toInt(), activity.application + ).apply { + val f: Field = this::class.java.getDeclaredField("ROOT_URL") + f.isAccessible = true + f.set(this, url) + } + } + + private companion object { + const val siteId = "123" + val flushInterval = 10.seconds + } + class SampleActivity : Activity() } diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java index ffcb109b..39506938 100644 --- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java +++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java @@ -61,7 +61,10 @@ public class ParselyTracker { private static final String STORAGE_KEY = "parsely-events.ser"; // emulator localhost // private static final String ROOT_URL = "http://10.0.2.2:5001/"; - private static final String ROOT_URL = "https://p1.parsely.com/"; + /** + * @noinspection StringOperationCanBeSimplified + */ + private static final String ROOT_URL = "https://p1.parsely.com/".intern(); protected ArrayList> eventQueue; private boolean isDebug; private final Context context; From 56c0fe864b332feaf7eae49894b06d8c232b3f95 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 27 Oct 2023 22:27:53 +0200 Subject: [PATCH 03/14] tests: assert lack of local storage file when running test By using AndroidX test orchestrator with `clearPackageData` flag. Each test should run in homogeneous environment. This change asserts, that the file used to store events locally won't be shared between tests. --- parsely/build.gradle | 5 +++++ .../com/parsely/parselyandroid/FunctionalTests.kt | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/parsely/build.gradle b/parsely/build.gradle index 71d13a01..ef2ab843 100644 --- a/parsely/build.gradle +++ b/parsely/build.gradle @@ -12,6 +12,10 @@ android { targetSdkVersion 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunnerArguments clearPackageData: 'true' + } + testOptions { + execution 'ANDROIDX_TEST_ORCHESTRATOR' } buildTypes { release { @@ -67,6 +71,7 @@ dependencies { androidTestImplementation 'org.assertj:assertj-core:3.24.2' androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' androidTestImplementation 'androidx.test:runner:1.5.2' + androidTestUtil 'androidx.test:orchestrator:1.4.2' } apply from: "${rootProject.projectDir}/publication.gradle" diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index 6903ed3e..4abb0e5e 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -3,7 +3,10 @@ package com.parsely.parselyandroid import android.app.Activity import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 +import java.io.File import java.lang.reflect.Field +import java.nio.file.Path +import kotlin.io.path.Path import kotlin.time.Duration.Companion.seconds import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer @@ -17,11 +20,21 @@ class FunctionalTests { private lateinit var parselyTracker: ParselyTracker private val server = MockWebServer() private val url = server.url("/").toString() + private lateinit var appsFiles: Path + + private fun beforeEach(activity: Activity) { + appsFiles = Path(activity.filesDir.path) + + if (File("$appsFiles/parsely-events.ser").exists()) { + throw RuntimeException("Local storage file exists. Something went wrong with orchestrating the tests.") + } + } @Test fun appTracksEventsAboveQueueSizeLimit() { ActivityScenario.launch(SampleActivity::class.java).use { scenario -> scenario.onActivity { activity: Activity -> + beforeEach(activity) server.enqueue(MockResponse().setResponseCode(200)) parselyTracker = initializeTracker(activity) From e97a7a5c3c9ffd2bf75b6b986969e231e021a09a Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 27 Oct 2023 22:34:08 +0200 Subject: [PATCH 04/14] tests: test case for "app tracks events above queue size limit" This commit adds test that checks if sending events over the limit of 50 events, works as intended. To not use flaky `Thread#sleep` approach, this test checks for: - local storage file changes via `java.nio.file.WatchService` API - finish of HTTP requests via `okhttp3.mockwebserver.takeRequest` API The `waitForFileEvents` method works in a way as `app.cash.turbine`, without a timeout though. --- .../parsely/parselyandroid/FunctionalTests.kt | 71 ++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index 4abb0e5e..f4d80511 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -4,12 +4,19 @@ import android.app.Activity import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 import java.io.File +import java.io.FileInputStream +import java.io.ObjectInputStream import java.lang.reflect.Field import java.nio.file.Path +import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE +import java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY +import java.nio.file.WatchEvent +import java.nio.file.WatchService import kotlin.io.path.Path import kotlin.time.Duration.Companion.seconds import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer +import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -30,6 +37,11 @@ class FunctionalTests { } } + /** + * In this scenario, the consumer application tracks more than 50 events-threshold during a flush interval. + * The SDK will save the events to disk and send them in the next flush interval. + * At the end, when all events are sent, the SDK will delete the content of local storage file. + */ @Test fun appTracksEventsAboveQueueSizeLimit() { ActivityScenario.launch(SampleActivity::class.java).use { scenario -> @@ -38,11 +50,66 @@ class FunctionalTests { server.enqueue(MockResponse().setResponseCode(200)) parselyTracker = initializeTracker(activity) - parselyTracker.trackPageview("url", null, null, null) + repeat(51) { + parselyTracker.trackPageview("url", null, null, null) + } - server.takeRequest() + // Waits for the SDK to save events to disk + val createLocalStorageEvents = appsFiles.waitForFileEvents(2) + assertThat(createLocalStorageEvents).satisfiesExactly( + // Checks for local storage file creation + { event -> assertThat(event.kind()).isEqualTo(ENTRY_CREATE) }, + // Checks if local storage file was modified + { event -> assertThat(event.kind()).isEqualTo(ENTRY_MODIFY) }, + ) + assertThat(locallyStoredEvents).hasSize(51) + } + + val dropLocalStorageEvent = appsFiles.waitForFileEvents(1) + + // Waits for the SDK to send events (flush interval passes) + server.takeRequest() + + assertThat(dropLocalStorageEvent).satisfiesExactly( + { event -> assertThat(event.kind()).isEqualTo(ENTRY_MODIFY) }, + ) + assertThat(locallyStoredEvents).hasSize(0) + } + } + + private val locallyStoredEvents + get() = FileInputStream(File("$appsFiles/parsely-events.ser")).use { + ObjectInputStream(it).use { objectInputStream -> + @Suppress("UNCHECKED_CAST") + objectInputStream.readObject() as ArrayList> } } + + + private fun Path.waitForFileEvents(numberOfEvents: Int): List> { + val service = watch() + val events = LinkedHashSet>() + while (true) { + val key = service.poll() + val polledEvents = + key?.pollEvents()?.filter { it.context().toString() == "parsely-events.ser" } + .orEmpty() + events.addAll(polledEvents) + println("[Parsely] Caught ${events.size} file events") + if (events.size == numberOfEvents) { + key?.reset() + break + } + Thread.sleep(500) + } + return events.toList() + } + + private fun Path.watch(): WatchService { + val watchService = this.fileSystem.newWatchService() + register(watchService, ENTRY_CREATE, ENTRY_MODIFY) + + return watchService } private fun initializeTracker(activity: Activity): ParselyTracker { From 8d4a0d39e04b57a6a05bb260e6978e23905c7c12 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 27 Oct 2023 23:20:08 +0200 Subject: [PATCH 05/14] tests: do not wait for file changes in functional tests This commit changes the approach for functional tests to more blackbox style, which seems to be more suitable here. As we don't care for implementation details (e.g. how or if events are stored locally), we no longer observe file changes. --- .../parsely/parselyandroid/FunctionalTests.kt | 63 +++++-------------- 1 file changed, 16 insertions(+), 47 deletions(-) diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index f4d80511..0545345e 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -8,15 +8,16 @@ import java.io.FileInputStream import java.io.ObjectInputStream import java.lang.reflect.Field import java.nio.file.Path -import java.nio.file.StandardWatchEventKinds.ENTRY_CREATE -import java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY -import java.nio.file.WatchEvent -import java.nio.file.WatchService import kotlin.io.path.Path +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.coroutines.yield import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.fail import org.junit.Test import org.junit.runner.RunWith @@ -53,27 +54,22 @@ class FunctionalTests { repeat(51) { parselyTracker.trackPageview("url", null, null, null) } - - // Waits for the SDK to save events to disk - val createLocalStorageEvents = appsFiles.waitForFileEvents(2) - assertThat(createLocalStorageEvents).satisfiesExactly( - // Checks for local storage file creation - { event -> assertThat(event.kind()).isEqualTo(ENTRY_CREATE) }, - // Checks if local storage file was modified - { event -> assertThat(event.kind()).isEqualTo(ENTRY_MODIFY) }, - ) - assertThat(locallyStoredEvents).hasSize(51) } - val dropLocalStorageEvent = appsFiles.waitForFileEvents(1) - // Waits for the SDK to send events (flush interval passes) server.takeRequest() - assertThat(dropLocalStorageEvent).satisfiesExactly( - { event -> assertThat(event.kind()).isEqualTo(ENTRY_MODIFY) }, - ) - assertThat(locallyStoredEvents).hasSize(0) + runBlocking { + withTimeoutOrNull(500.milliseconds) { + while (true) { + yield() + if (locallyStoredEvents.size == 0) { + break + } + } + } ?: fail("Local storage file is not empty!") + } + } } @@ -85,33 +81,6 @@ class FunctionalTests { } } - - private fun Path.waitForFileEvents(numberOfEvents: Int): List> { - val service = watch() - val events = LinkedHashSet>() - while (true) { - val key = service.poll() - val polledEvents = - key?.pollEvents()?.filter { it.context().toString() == "parsely-events.ser" } - .orEmpty() - events.addAll(polledEvents) - println("[Parsely] Caught ${events.size} file events") - if (events.size == numberOfEvents) { - key?.reset() - break - } - Thread.sleep(500) - } - return events.toList() - } - - private fun Path.watch(): WatchService { - val watchService = this.fileSystem.newWatchService() - register(watchService, ENTRY_CREATE, ENTRY_MODIFY) - - return watchService - } - private fun initializeTracker(activity: Activity): ParselyTracker { return ParselyTracker.sharedInstance( siteId, flushInterval.inWholeSeconds.toInt(), activity.application From aa7d1be53962f707e11992f32b248784e0242402 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Fri, 27 Oct 2023 23:55:25 +0200 Subject: [PATCH 06/14] tests: assert the correct payload size of triggered HTTP request. --- .../parsely/parselyandroid/FunctionalTests.kt | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index 0545345e..52d592d2 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -3,6 +3,10 @@ package com.parsely.parselyandroid import android.app.Activity import androidx.test.core.app.ActivityScenario import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.ObjectMapper import java.io.File import java.io.FileInputStream import java.io.ObjectInputStream @@ -16,6 +20,7 @@ import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.yield import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.fail import org.junit.Test @@ -57,7 +62,8 @@ class FunctionalTests { } // Waits for the SDK to send events (flush interval passes) - server.takeRequest() + val requestPayload = server.takeRequest().toMap() + assertThat(requestPayload["events"]).hasSize(51) runBlocking { withTimeoutOrNull(500.milliseconds) { @@ -69,10 +75,21 @@ class FunctionalTests { } } ?: fail("Local storage file is not empty!") } - } } + private fun RecordedRequest.toMap(): Map> { + val listType: TypeReference>> = + object : TypeReference>>() {} + + return ObjectMapper().readValue(body.readUtf8(), listType) + } + + @JsonIgnoreProperties(ignoreUnknown = true) + data class Event ( + @JsonProperty("idsite") var idsite: String, + ) + private val locallyStoredEvents get() = FileInputStream(File("$appsFiles/parsely-events.ser")).use { ObjectInputStream(it).use { objectInputStream -> From 87e0e3aa47de2291e22e32d7141c090bc52ac616 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Sat, 28 Oct 2023 00:01:50 +0200 Subject: [PATCH 07/14] ci: run functional tests on CI On a separate job, as it has to run on macOS. --- .github/workflows/readme.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml index 59e66bed..347dbb75 100644 --- a/.github/workflows/readme.yml +++ b/.github/workflows/readme.yml @@ -43,3 +43,33 @@ jobs: with: name: artifact path: ~/.m2/repository/com/parsely/parsely/* + functional-tests: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: set up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + - name: Functional Tests + uses: reactivecircus/android-emulator-runner@v2.28.0 + with: + working-directory: . + api-level: 31 + profile: Nexus 6 + arch: x86_64 + force-avd-creation: false + avd-name: macOS-avd-x86_64-31 + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: ./gradlew :parsely:connectedCheck + - name: Publish build artifacts + uses: actions/upload-artifact@v3 + if: always() + with: + name: artifact + path: ./parsely/build/reports/* From 493445d96662cbb25ecde69b23171895fe8fa37c Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Sun, 29 Oct 2023 13:04:25 +0100 Subject: [PATCH 08/14] build: extract dependencies version of unit and instrumentation tests --- parsely/build.gradle | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/parsely/build.gradle b/parsely/build.gradle index ef2ab843..3fbfade1 100644 --- a/parsely/build.gradle +++ b/parsely/build.gradle @@ -4,6 +4,11 @@ plugins { id 'org.jetbrains.kotlinx.kover' } +ext { + assertJVersion = '3.24.2' + mockWebServerVersion = '4.12.0' +} + android { compileSdkVersion 33 @@ -59,17 +64,17 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3' implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' implementation 'androidx.lifecycle:lifecycle-process:2.6.2' - + testImplementation 'org.robolectric:robolectric:4.10.3' testImplementation 'androidx.test:core:1.5.0' - testImplementation 'org.assertj:assertj-core:3.24.2' + testImplementation "org.assertj:assertj-core:$assertJVersion" testImplementation 'junit:junit:4.13.2' - testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' + testImplementation "com.squareup.okhttp3:mockwebserver:$mockWebServerVersion" androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'org.assertj:assertj-core:3.24.2' - androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' + androidTestImplementation "org.assertj:assertj-core:$assertJVersion" + androidTestImplementation "com.squareup.okhttp3:mockwebserver:$mockWebServerVersion" androidTestImplementation 'androidx.test:runner:1.5.2' androidTestUtil 'androidx.test:orchestrator:1.4.2' } From 36203de034139957ceea66105d33046efaf45e04 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Sun, 29 Oct 2023 15:12:45 +0100 Subject: [PATCH 09/14] style: extract name of local storage file to val --- .../java/com/parsely/parselyandroid/FunctionalTests.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index 52d592d2..ef4646c5 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -38,7 +38,7 @@ class FunctionalTests { private fun beforeEach(activity: Activity) { appsFiles = Path(activity.filesDir.path) - if (File("$appsFiles/parsely-events.ser").exists()) { + if (File("$appsFiles/$localStorageFileName").exists()) { throw RuntimeException("Local storage file exists. Something went wrong with orchestrating the tests.") } } @@ -91,7 +91,7 @@ class FunctionalTests { ) private val locallyStoredEvents - get() = FileInputStream(File("$appsFiles/parsely-events.ser")).use { + get() = FileInputStream(File("$appsFiles/$localStorageFileName")).use { ObjectInputStream(it).use { objectInputStream -> @Suppress("UNCHECKED_CAST") objectInputStream.readObject() as ArrayList> @@ -110,6 +110,7 @@ class FunctionalTests { private companion object { const val siteId = "123" + const val localStorageFileName = "parsely-events.ser" val flushInterval = 10.seconds } From 5900d93eff388bc5b2d98c44072cee07d91b1a96 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Sun, 29 Oct 2023 15:27:37 +0100 Subject: [PATCH 10/14] refactor: simplify code with waitFor function for locally stored events --- .../parsely/parselyandroid/FunctionalTests.kt | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index ef4646c5..e82fd24e 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -22,7 +22,6 @@ import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.fail import org.junit.Test import org.junit.runner.RunWith @@ -65,16 +64,9 @@ class FunctionalTests { val requestPayload = server.takeRequest().toMap() assertThat(requestPayload["events"]).hasSize(51) - runBlocking { - withTimeoutOrNull(500.milliseconds) { - while (true) { - yield() - if (locallyStoredEvents.size == 0) { - break - } - } - } ?: fail("Local storage file is not empty!") - } + // Wait a moment to give SDK time to delete the content of local storage file + waitFor { locallyStoredEvents.isEmpty() } + assertThat(locallyStoredEvents).isEmpty() } } @@ -86,7 +78,7 @@ class FunctionalTests { } @JsonIgnoreProperties(ignoreUnknown = true) - data class Event ( + data class Event( @JsonProperty("idsite") var idsite: String, ) @@ -115,4 +107,15 @@ class FunctionalTests { } class SampleActivity : Activity() + + private fun waitFor(condition: () -> Boolean) = runBlocking { + withTimeoutOrNull(500.milliseconds) { + while (true) { + yield() + if (condition()) { + break + } + } + } + } } From 5dcb5ffb5e8db8a8fe373c7874816529f4b44b9e Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 30 Oct 2023 13:07:49 +0100 Subject: [PATCH 11/14] style: use `@SuppressWarnings` instead of a comment --- .../main/java/com/parsely/parselyandroid/ParselyTracker.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java index 39506938..467f219f 100644 --- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java +++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java @@ -61,9 +61,7 @@ public class ParselyTracker { private static final String STORAGE_KEY = "parsely-events.ser"; // emulator localhost // private static final String ROOT_URL = "http://10.0.2.2:5001/"; - /** - * @noinspection StringOperationCanBeSimplified - */ + @SuppressWarnings("StringOperationCanBeSimplified") private static final String ROOT_URL = "https://p1.parsely.com/".intern(); protected ArrayList> eventQueue; private boolean isDebug; From a266ab00eacdb003f6d282bbc80b294de556c797 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 30 Oct 2023 13:08:49 +0100 Subject: [PATCH 12/14] fix: add `intern` to `ROOT_URL` of emulator localhost --- .../main/java/com/parsely/parselyandroid/ParselyTracker.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java index 467f219f..7784970e 100644 --- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java +++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java @@ -59,9 +59,8 @@ public class ParselyTracker { private static final int QUEUE_SIZE_LIMIT = 50; private static final int STORAGE_SIZE_LIMIT = 100; private static final String STORAGE_KEY = "parsely-events.ser"; -// emulator localhost -// private static final String ROOT_URL = "http://10.0.2.2:5001/"; @SuppressWarnings("StringOperationCanBeSimplified") +// private static final String ROOT_URL = "http://10.0.2.2:5001/".intern(); // emulator localhost private static final String ROOT_URL = "https://p1.parsely.com/".intern(); protected ArrayList> eventQueue; private boolean isDebug; From d2a25dbc864396d67dc517943a5a079cffb86304 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 30 Oct 2023 13:12:22 +0100 Subject: [PATCH 13/14] ci: use `connectedDebugAndroidTest` to run functional tests --- .github/workflows/readme.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml index 347dbb75..257d0df3 100644 --- a/.github/workflows/readme.yml +++ b/.github/workflows/readme.yml @@ -66,7 +66,7 @@ jobs: avd-name: macOS-avd-x86_64-31 emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: ./gradlew :parsely:connectedCheck + script: ./gradlew :parsely:connectedDebugAndroidTest - name: Publish build artifacts uses: actions/upload-artifact@v3 if: always() From e6727a0bd213dd72cad11c45988142894c237d42 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Mon, 30 Oct 2023 13:32:18 +0100 Subject: [PATCH 14/14] chore: remove unused `testBuildType` config Because we only now run instrumentation tests in the `debug` build type by default, we no longer need to configure `debug` as the `testBuildType`. --- parsely/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/parsely/build.gradle b/parsely/build.gradle index 3fbfade1..c1206c88 100644 --- a/parsely/build.gradle +++ b/parsely/build.gradle @@ -33,7 +33,6 @@ android { exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' } - testBuildType "debug" publishing { singleVariant('release') {