Skip to content

Commit

Permalink
test: Improve 'rewrite[Sync]Error' performance
Browse files Browse the repository at this point in the history
* rename: 'rewriteError' -> 'rewriteSyncError'
* remove 'DeckPicker' dependency
* remove 'DeckPicker' dependency from tests
* AndroidTest code copied from `RobolectricTesr`

Affects methods:

* verifyBadCodesNoMessage
* verifyCodeMessages

Outcomes:

* Tests are not run twice
* Tests do not need DeckPicker's `onCreate` etc...

Stats:

* Removes ~5s from targeted test run (startup costs)
* Tests take ~50ms (down from ~225ms): ~900ms savings
  • Loading branch information
david-allison authored and mikehardy committed Feb 28, 2023
1 parent 55f08d2 commit 83b0611
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 54 deletions.
31 changes: 12 additions & 19 deletions AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ package com.ichi2.anki
import android.content.Context
import android.content.DialogInterface
import android.content.SharedPreferences
import android.content.res.Resources
import android.view.KeyEvent
import android.view.WindowManager
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import anki.sync.SyncAuth
Expand Down Expand Up @@ -660,7 +660,7 @@ fun DeckPicker.createSyncListener() = object : Connection.CancellableTaskListene
else -> {
if (result.isNotEmpty() && result[0] is Int) {
val code = result[0] as Int
dialogMessage = rewriteError(code)
dialogMessage = getMessageFromSyncErrorCode(resources, code)
if (dialogMessage == null) {
dialogMessage = res.getString(
R.string.sync_log_error_specific,
Expand Down Expand Up @@ -765,23 +765,16 @@ fun DeckPicker.showSyncLogMessage(@StringRes messageResource: Int, syncMessage:
}
}

@VisibleForTesting
fun DeckPicker.rewriteError(code: Int): String? {
// PREF: Unit tests on this method can be sped up (remove 'DeckPicker' reference)
val msg: String?
val res = resources
msg = when (code) {
407 -> res.getString(R.string.sync_error_407_proxy_required)
409 -> res.getString(R.string.sync_error_409)
413 -> res.getString(R.string.sync_error_413_collection_size)
500 -> res.getString(R.string.sync_error_500_unknown)
501 -> res.getString(R.string.sync_error_501_upgrade_required)
502 -> res.getString(R.string.sync_error_502_maintenance)
503 -> res.getString(R.string.sync_too_busy)
504 -> res.getString(R.string.sync_error_504_gateway_timeout)
else -> null
}
return msg
fun getMessageFromSyncErrorCode(res: Resources, code: Int): String? = when (code) {
407 -> res.getString(R.string.sync_error_407_proxy_required)
409 -> res.getString(R.string.sync_error_409)
413 -> res.getString(R.string.sync_error_413_collection_size)
500 -> res.getString(R.string.sync_error_500_unknown)
501 -> res.getString(R.string.sync_error_501_upgrade_required)
502 -> res.getString(R.string.sync_error_502_maintenance)
503 -> res.getString(R.string.sync_too_busy)
504 -> res.getString(R.string.sync_error_504_gateway_timeout)
else -> null
}

fun joinSyncMessages(dialogMessage: String?, syncMessage: String?): String? {
Expand Down
35 changes: 0 additions & 35 deletions AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import org.robolectric.RuntimeEnvironment
import java.io.File
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertNull

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(ParameterizedRobolectricTestRunner::class)
Expand All @@ -59,40 +58,6 @@ class DeckPickerTest : RobolectricTest() {
}
}

@Test
fun verifyCodeMessages() {
val codeResponsePairs = hashMapOf(
407 to getResourceString(R.string.sync_error_407_proxy_required),
409 to getResourceString(R.string.sync_error_409),
413 to getResourceString(R.string.sync_error_413_collection_size),
500 to getResourceString(R.string.sync_error_500_unknown),
501 to getResourceString(R.string.sync_error_501_upgrade_required),
502 to getResourceString(R.string.sync_error_502_maintenance),
503 to getResourceString(R.string.sync_too_busy),
504 to getResourceString(R.string.sync_error_504_gateway_timeout)
)
ActivityScenario.launch(DeckPicker::class.java).use { scenario ->
scenario.onActivity { deckPicker: DeckPicker ->
for ((key, value) in codeResponsePairs) {
assertEquals(deckPicker.rewriteError(key), value)
}
}
}
}

@Test
fun verifyBadCodesNoMessage() {
ActivityScenario.launch(DeckPicker::class.java).use { scenario ->
scenario.onActivity { deckPicker: DeckPicker ->
assertNull(deckPicker.rewriteError(0))
assertNull(deckPicker.rewriteError(-1))
assertNull(deckPicker.rewriteError(1))
assertNull(deckPicker.rewriteError(Int.MIN_VALUE))
assertNull(deckPicker.rewriteError(Int.MAX_VALUE))
}
}
}

@Test
fun getPreviousVersionUpgradeFrom201to292() {
val newVersion = 20900302 // 2.9.2
Expand Down
62 changes: 62 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/anki/SyncTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023 David Allison <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.ichi2.anki

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.testutils.AndroidTest
import com.ichi2.testutils.EmptyApplication
import com.ichi2.testutils.getString
import com.ichi2.testutils.targetContext
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import kotlin.test.assertNull

@RunWith(AndroidJUnit4::class)
@Config(application = EmptyApplication::class)
class SyncTest : AndroidTest {

@Test
fun verifyCodeMessages() {
val codeResponsePairs = hashMapOf(
407 to getString(R.string.sync_error_407_proxy_required),
409 to getString(R.string.sync_error_409),
413 to getString(R.string.sync_error_413_collection_size),
500 to getString(R.string.sync_error_500_unknown),
501 to getString(R.string.sync_error_501_upgrade_required),
502 to getString(R.string.sync_error_502_maintenance),
503 to getString(R.string.sync_too_busy),
504 to getString(R.string.sync_error_504_gateway_timeout)
)

for ((key, value) in codeResponsePairs) {
Assert.assertEquals(getMessageFromSyncErrorCode(key), value)
}
}

@Test
fun verifyBadCodesNoMessage() {
assertNull(getMessageFromSyncErrorCode(0))
assertNull(getMessageFromSyncErrorCode(-1))
assertNull(getMessageFromSyncErrorCode(1))
assertNull(getMessageFromSyncErrorCode(Int.MIN_VALUE))
assertNull(getMessageFromSyncErrorCode(Int.MAX_VALUE))
}

private fun getMessageFromSyncErrorCode(key: Int) = getMessageFromSyncErrorCode(targetContext.resources, key)
}
61 changes: 61 additions & 0 deletions AnkiDroid/src/test/java/com/ichi2/testutils/AndroidTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2023 David Allison <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.ichi2.testutils

import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.test.core.app.ApplicationProvider
import com.ichi2.anki.AnkiDroidApp

/** Marker interface for classes annotated with `@RunWith(AndroidJUnit4.class)` */
interface AndroidTest

val AndroidTest.targetContext: Context
get() {
return try {
ApplicationProvider.getApplicationContext()
} catch (e: IllegalStateException) {
if (e.message != null && e.message!!.startsWith("No instrumentation registered!")) {
// Explicitly ignore the inner exception - generates line noise
throw IllegalStateException("Annotate class: '${javaClass.simpleName}' with '@RunWith(AndroidJUnit4.class)'")
}
throw e
}
}

/**
* Returns an instance of [SharedPreferences] using the test context
* @see [editPreferences] for editing
*/
fun AndroidTest.getSharedPrefs(): SharedPreferences = AnkiDroidApp.getSharedPrefs(targetContext)

fun AndroidTest.getString(res: Int): String = targetContext.getString(res)

@Suppress("unused")
fun AndroidTest.getQuantityString(res: Int, quantity: Int, vararg formatArgs: Any): String =
targetContext.resources.getQuantityString(res, quantity, *formatArgs)

/**
* Allows editing of preferences, followed by a call to [apply][SharedPreferences.Editor.apply]:
*
* ```
* editPreferences { putString("key", value) }
* ```
*/
fun AndroidTest.editPreferences(action: SharedPreferences.Editor.() -> Unit) =
getSharedPrefs().edit(action = action)

0 comments on commit 83b0611

Please sign in to comment.