diff --git a/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml b/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml index 7cd4624af1..04d0db886f 100644 --- a/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml +++ b/features/fixtures/mazerunner/app/src/main/AndroidManifest.xml @@ -10,8 +10,11 @@ android:name=".MazerunnerApp" android:extractNativeLibs="false" > + diff --git a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt index f3589239c0..af8b1759d2 100644 --- a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt +++ b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/MainActivity.kt @@ -11,7 +11,6 @@ import android.os.Looper import android.view.Window import android.widget.Button import android.widget.EditText -import com.bugsnag.android.BugsnagInternals import com.bugsnag.android.mazerunner.scenarios.Scenario import org.json.JSONObject import java.io.File @@ -21,21 +20,23 @@ import java.net.URL import kotlin.concurrent.thread import kotlin.math.max -const val CONFIG_FILE_TIMEOUT = 5000 +const val CONFIG_FILE_TIMEOUT = 15000 class MainActivity : Activity() { private val mainHandler = Handler(Looper.getMainLooper()) private val apiKeyKey = "BUGSNAG_API_KEY" + private val commandUUIDKey = "MAZE_COMMAND_UUID" lateinit var prefs: SharedPreferences var scenario: Scenario? = null - var polling = false + var isActivityRecreate = false var mazeAddress: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + this.isActivityRecreate = savedInstanceState != null log("MainActivity.onCreate called") requestWindowFeature(Window.FEATURE_NO_TITLE) setContentView(R.layout.activity_main) @@ -71,7 +72,9 @@ class MainActivity : Activity() { super.onResume() log("MainActivity.onResume called") - if (!polling) { + // Don't start the command runner again if the activity is being recreated, + // as it results in two threads executing commands concurrently and causing flakes. + if (!this.isActivityRecreate) { startCommandRunner() } log("MainActivity.onResume complete") @@ -122,14 +125,33 @@ class MainActivity : Activity() { return jsonObject?.optString(key) ?: "" } + private fun setStoredCommandUUID(commandUUID: String) { + with(prefs.edit()) { + putString(commandUUIDKey, commandUUID) + commit() + } + CiLog.info("lastCommandUUID set to: $commandUUID") + } + + private fun clearStoredCommandUUID() { + with(prefs.edit()) { + remove(commandUUIDKey) + commit() + } + CiLog.info("lastCommandUUID set to empty") + } + + private fun getStoredCommandUUID(): String? { + return prefs.getString(commandUUIDKey, "").orEmpty() + } + // Starts a thread to poll for Maze Runner actions to perform private fun startCommandRunner() { - // Get the next maze runner command - polling = true thread(start = true) { if (mazeAddress == null) setMazeRunnerAddress() checkNetwork() + var polling = true while (polling) { Thread.sleep(1000) try { @@ -142,17 +164,13 @@ class MainActivity : Activity() { // Log the received command CiLog.info("Received command: $commandStr") - var command = JSONObject(commandStr) + val command = JSONObject(commandStr) val action = getStringSafely(command, "action") val scenarioName = getStringSafely(command, "scenario_name") val scenarioMode = getStringSafely(command, "scenario_mode") val sessionsUrl = getStringSafely(command, "sessions_endpoint") val notifyUrl = getStringSafely(command, "notify_endpoint") - log("command.action: $action") - log("command.scenarioName: $scenarioName") - log("command.scenarioMode: $scenarioMode") - log("command.sessionsUrl: $sessionsUrl") - log("command.notifyUrl: $notifyUrl") + val commandUUID = getStringSafely(command, "uuid") // Stop polling once we have a scenario action if ("start_bugsnag".equals(action) || "run_scenario".equals(action)) { @@ -172,13 +190,18 @@ class MainActivity : Activity() { CiLog.info("No Maze Runner command queuing, continuing to poll") } "start_bugsnag" -> { + setStoredCommandUUID(commandUUID) startBugsnag(scenarioName, scenarioMode, sessionsUrl, notifyUrl) } "run_scenario" -> { + setStoredCommandUUID(commandUUID) runScenario(scenarioName, scenarioMode, sessionsUrl, notifyUrl) } - "clear_persistent_data" -> clearPersistentData() - "flush" -> BugsnagInternals.flush() + "clear_persistent_data" -> { + setStoredCommandUUID(commandUUID) + PersistentData(applicationContext).clear() + } + "reset_uuid" -> clearStoredCommandUUID() else -> throw IllegalArgumentException("Unknown action: $action") } } @@ -190,7 +213,8 @@ class MainActivity : Activity() { } private fun readCommand(): String { - val commandUrl = "http://$mazeAddress/command" + val commandUrl = "http://$mazeAddress/idem-command?after=${getStoredCommandUUID()}" + CiLog.info("Requesting Maze Runner command from: $commandUrl") val urlConnection = URL(commandUrl).openConnection() as HttpURLConnection try { return urlConnection.inputStream.use { it.reader().readText() } @@ -246,52 +270,6 @@ class MainActivity : Activity() { } } - // Clear persistent data (used to stop scenarios bleeding into each other) - private fun clearPersistentData() { - CiLog.info("Clearing persistent data") - clearCacheFolder("bugsnag") - clearCacheFolder("StrictModeDiscScenarioFile") - clearFilesFolder("background-service-dir") - - removeFile("device-id") - removeFile("internal-device-id") - - listFolders() - } - - // Recursively deletes the contents of a folder beneath /cache - private fun clearCacheFolder(name: String) { - val folder = File(applicationContext.cacheDir, name) - log("Clearing folder: ${folder.path}") - folder.deleteRecursively() - } - - private fun clearFilesFolder(name: String) { - val folder = File(applicationContext.filesDir, name) - log("Clearing folder: ${folder.path}") - folder.deleteRecursively() - } - - // Deletes a file beneath /files - private fun removeFile(name: String) { - val file = File(applicationContext.filesDir, name) - log("Removing file: ${file.path}") - file.delete() - } - - // Logs out the contents of the /cache and /files folders - private fun listFolders() { - log("Contents of: ${applicationContext.cacheDir}") - applicationContext.cacheDir.walkTopDown().forEach { - log(it.absolutePath) - } - - log("Contents of: ${applicationContext.filesDir}") - applicationContext.filesDir.walkTopDown().forEach { - log(it.absolutePath) - } - } - private fun loadScenario( eventType: String, mode: String, diff --git a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/PersistentData.kt b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/PersistentData.kt new file mode 100644 index 0000000000..7065d10867 --- /dev/null +++ b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/PersistentData.kt @@ -0,0 +1,53 @@ +package com.bugsnag.android.mazerunner + +import android.content.Context +import java.io.File + +class PersistentData(private val applicationContext: Context) { + + // Clear persistent data (used to stop scenarios bleeding into each other) + fun clear() { + CiLog.info("Clearing persistent data") + clearCacheFolder("bugsnag") + clearCacheFolder("StrictModeDiscScenarioFile") + clearFilesFolder("background-service-dir") + + removeFile("device-id") + removeFile("internal-device-id") + + listFolders() + } + + // Recursively deletes the contents of a folder beneath /cache + private fun clearCacheFolder(name: String) { + val folder = File(applicationContext.cacheDir, name) + log("Clearing folder: ${folder.path}") + folder.deleteRecursively() + } + + private fun clearFilesFolder(name: String) { + val folder = File(applicationContext.filesDir, name) + log("Clearing folder: ${folder.path}") + folder.deleteRecursively() + } + + // Deletes a file beneath /files + private fun removeFile(name: String) { + val file = File(applicationContext.filesDir, name) + log("Removing file: ${file.path}") + file.delete() + } + + // Logs out the contents of the /cache and /files folders + private fun listFolders() { + log("Contents of: ${applicationContext.cacheDir}") + applicationContext.cacheDir.walkTopDown().forEach { + log(it.absolutePath) + } + + log("Contents of: ${applicationContext.filesDir}") + applicationContext.filesDir.walkTopDown().forEach { + log(it.absolutePath) + } + } +} diff --git a/features/steps/android_steps.rb b/features/steps/android_steps.rb index 27824dea8e..1cbce33408 100644 --- a/features/steps/android_steps.rb +++ b/features/steps/android_steps.rb @@ -26,12 +26,6 @@ def execute_command(action, scenario_name = '') $sessions_endpoint = 'http://bs-local.com:9339/sessions' $notify_endpoint = 'http://bs-local.com:9339/notify' end - - # Ensure fixture has read the command - count = 600 - sleep 0.1 until Maze::Server.commands.size_remaining == 0 || (count -= 1) < 1 - - raise 'Test fixture did not GET /command' unless Maze::Server.commands.size_remaining == 0 end def press_at(x, y)