15278 detekt rule runblocking (#15942)

* For #15278: added CoroutineManager to count runBlocking calls

* For #15278: Added actual detekt rule for runblocking and its config to the yaml

* For #15278: Added unit test for RunblockingCounter

* For #15278: renamed StrictModeStartupSuppressionCountTest.kt to PerformanceStartupTest.kt and added runBlockingCount test

* Lint fix

* For #15278: made runblocking a Long to prevent overflow

* For #15278: fixed MozRunblocking name, description and moved RunBlockingCounter to perf package

* For #15278:Renamed MozillaRunblockingCheck to MozillaRunBlockingCheck

* For #15278: Added setup for unit test, since it failed without restting counter

* For #15278: Fixed naming for RunBlocking lint check

* For #15278: removed changes made to test to use runBlockingIncrement

* For #15728: added test exclusion for runBlocking check

* For #15278: changed null check and added Synchronized to count setter

* For #15278: fix for nits

* For #15278: added StartupExcessiveResourceUseTest to CODEOWNERS

* For #15278: fixed for nits

* For #15278: Moved increment function to extension function and fixed indentation

* For #15278: Added tests for Atomic Integer extension and nit fix
upstream-sync
MarcLeclair 4 years ago committed by GitHub
parent d46fc7b142
commit 7b1af41b40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -50,6 +50,8 @@
*Application.kt @mozilla-mobile/Performance *Application.kt @mozilla-mobile/Performance
*StrictMode*kt @mozilla-mobile/Performance *StrictMode*kt @mozilla-mobile/Performance
*ConstraintLayoutPerfDetector* @mozilla-mobile/Performance *ConstraintLayoutPerfDetector* @mozilla-mobile/Performance
*MozillaRunBlockingCheck.kt @mozilla-mobile/Performance
*StartupExcessiveResourceUseTest.kt @mozilla-mobile/Performance
# We want to be aware of new features behind flags as well as features # We want to be aware of new features behind flags as well as features
# about to be enabled. # about to be enabled.

@ -9,13 +9,15 @@ import androidx.test.uiautomator.UiDevice
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.perf.RunBlockingCounter
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.HomeActivityTestRule import org.mozilla.fenix.helpers.HomeActivityTestRule
// PLEASE CONSULT WITH PERF TEAM BEFORE CHANGING THIS VALUE. // PLEASE CONSULT WITH PERF TEAM BEFORE CHANGING THIS VALUE.
private const val EXPECTED_SUPPRESSION_COUNT = 11 private const val EXPECTED_SUPPRESSION_COUNT = 11
private const val FAILURE_MSG = """StrictMode startup suppression count does not match expected count. private const val EXPECTED_RUNBLOCKING_COUNT = 2
private const val STRICTMODE_FAILURE_MSG = """StrictMode startup suppression count does not match expected count.
If this PR removed code that suppressed StrictMode, great! Please decrement the suppression count. If this PR removed code that suppressed StrictMode, great! Please decrement the suppression count.
@ -28,31 +30,53 @@ private const val FAILURE_MSG = """StrictMode startup suppression count does not
""" """
private const val RUNBLOCKING_FAILURE_MSG = """RunblockingCounter startup count does not match expected count.
If this PR removed code that removed a RunBlocking call, great! Please decrement the suppression count.
Did this PR add or call code that incremented the RunBlockingCounter?
Did you know that using runBlocking can have negative perfomance implications?
If so, please do your best to implement a solution without blocking the main thread.
Please consult the perf team if you have questions or believe that using runBlocking
is the optimal solution.
"""
/** /**
* A performance test to limit the number of StrictMode suppressions on startup. * A performance test to limit the number of StrictMode suppressions and number of runBlocking used
* on startup.
*
* This test was written by the perf team. * This test was written by the perf team.
* *
* StrictMode detects main thread IO, which is often indicative of a performance issue. * StrictMode detects main thread IO, which is often indicative of a performance issue.
* It's easy to suppress StrictMode so we wrote a test to ensure we have a discussion * It's easy to suppress StrictMode so we wrote a test to ensure we have a discussion
* if the StrictMode count changes. The perf team is code owners for this file so they * if the StrictMode count changes.
* should be notified when the count is modified. *
* RunBlocking is mostly used to return values to a thread from a coroutine. However, if that
* coroutine takes too long, it can lead that thread to block every other operations.
*
* The perf team is code owners for this file so they should be notified when the count is modified.
* *
* IF YOU UPDATE THE TEST NAME, UPDATE CODE OWNERS. * IF YOU UPDATE THE TEST NAME, UPDATE CODE OWNERS.
*/ */
class StrictModeStartupSuppressionCountTest { class StartupExcessiveResourceUseTest {
@get:Rule @get:Rule
val activityTestRule = HomeActivityTestRule(skipOnboarding = true) val activityTestRule = HomeActivityTestRule(skipOnboarding = true)
private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@Test @Test
fun verifyStrictModeSuppressionCount() { fun verifyRunBlockingAndStrictModeSuppresionCount() {
uiDevice.waitForIdle() // wait for async UI to load. uiDevice.waitForIdle() // wait for async UI to load.
// This might cause intermittents: at an arbitrary point after start up (such as the visual // This might cause intermittents: at an arbitrary point after start up (such as the visual
// completeness queue), we might run code on the main thread that suppresses StrictMode, // completeness queue), we might run code on the main thread that suppresses StrictMode,
// causing this number to fluctuate depending on device speed. We'll deal with it if it occurs. // causing this number to fluctuate depending on device speed. We'll deal with it if it occurs.
val actual = activityTestRule.activity.components.strictMode.suppressionCount.toInt() val actualSuppresionCount = activityTestRule.activity.components.strictMode.suppressionCount.toInt()
assertEquals(FAILURE_MSG, EXPECTED_SUPPRESSION_COUNT, actual) val actualRunBlocking = RunBlockingCounter.count.get()
assertEquals(STRICTMODE_FAILURE_MSG, EXPECTED_SUPPRESSION_COUNT, actualSuppresionCount)
assertEquals(RUNBLOCKING_FAILURE_MSG, EXPECTED_RUNBLOCKING_COUNT, actualRunBlocking)
} }
} }

@ -19,7 +19,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import mozilla.appservices.Megazord import mozilla.appservices.Megazord
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.state.action.SystemAction import mozilla.components.browser.state.action.SystemAction
@ -44,6 +43,7 @@ import org.mozilla.fenix.components.metrics.MetricServiceType
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.StorageStatsMetrics import org.mozilla.fenix.perf.StorageStatsMetrics
import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.perf.runBlockingIncrement
import org.mozilla.fenix.push.PushFxaIntegration import org.mozilla.fenix.push.PushFxaIntegration
import org.mozilla.fenix.push.WebPushEngineIntegration import org.mozilla.fenix.push.WebPushEngineIntegration
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
@ -137,7 +137,7 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
// to invoke parts of itself that require complete megazord initialization // to invoke parts of itself that require complete megazord initialization
// before that process completes, we wait here, if necessary. // before that process completes, we wait here, if necessary.
if (!megazordSetup.isCompleted) { if (!megazordSetup.isCompleted) {
runBlocking { megazordSetup.await(); } runBlockingIncrement { megazordSetup.await() }
} }
} }

@ -4,10 +4,10 @@
package org.mozilla.fenix.components.history package org.mozilla.fenix.components.history
import kotlinx.coroutines.runBlocking
import mozilla.components.concept.storage.HistoryStorage import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.concept.storage.VisitInfo import mozilla.components.concept.storage.VisitInfo
import mozilla.components.concept.storage.VisitType import mozilla.components.concept.storage.VisitType
import org.mozilla.fenix.perf.runBlockingIncrement
/** /**
* An Interface for providing a paginated list of [VisitInfo] * An Interface for providing a paginated list of [VisitInfo]
@ -32,7 +32,7 @@ fun HistoryStorage.createSynchronousPagedHistoryProvider(): PagedHistoryProvider
numberOfItems: Long, numberOfItems: Long,
onComplete: (List<VisitInfo>) -> Unit onComplete: (List<VisitInfo>) -> Unit
) { ) {
runBlocking { runBlockingIncrement {
val history = getVisitsPaginated( val history = getVisitsPaginated(
offset, offset,
numberOfItems, numberOfItems,

@ -12,7 +12,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import kotlinx.coroutines.runBlocking
import mozilla.components.support.utils.SafeIntent import mozilla.components.support.utils.SafeIntent
import org.mozilla.fenix.components.metrics.Event.AppAllStartup import org.mozilla.fenix.components.metrics.Event.AppAllStartup
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Source
@ -25,6 +24,7 @@ import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.ERROR
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.HOT
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.COLD
import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM import org.mozilla.fenix.components.metrics.Event.AppAllStartup.Type.WARM
import org.mozilla.fenix.perf.runBlockingIncrement
import java.lang.reflect.Modifier.PRIVATE import java.lang.reflect.Modifier.PRIVATE
/** /**
@ -186,7 +186,7 @@ class AppStartupTelemetry(
* the application potentially closes. * the application potentially closes.
*/ */
fun onStop() { fun onStop() {
runBlocking { runBlockingIncrement {
recordMetric() recordMetric()
} }
} }

@ -12,7 +12,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.search.provider.AssetsSearchEngineProvider import mozilla.components.browser.search.provider.AssetsSearchEngineProvider
import mozilla.components.browser.search.provider.SearchEngineList import mozilla.components.browser.search.provider.SearchEngineList
@ -27,6 +26,7 @@ import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.runBlockingIncrement
import java.util.Locale import java.util.Locale
@SuppressWarnings("TooManyFunctions") @SuppressWarnings("TooManyFunctions")
@ -141,7 +141,7 @@ open class FenixSearchEngineProvider(
* are readily available throughout the app. Includes all installed engines, both * are readily available throughout the app. Includes all installed engines, both
* default and custom * default and custom
*/ */
fun installedSearchEngines(context: Context): SearchEngineList = runBlocking { fun installedSearchEngines(context: Context): SearchEngineList = runBlockingIncrement {
val installedIdentifiers = installedSearchEngineIdentifiers(context) val installedIdentifiers = installedSearchEngineIdentifiers(context)
val defaultList = searchEngines.await() val defaultList = searchEngines.await()
@ -161,15 +161,15 @@ open class FenixSearchEngineProvider(
) )
} }
fun allSearchEngineIdentifiers() = runBlocking { fun allSearchEngineIdentifiers() = runBlockingIncrement {
loadedSearchEngines.await().list.map { it.identifier } loadedSearchEngines.await().list.map { it.identifier }
} }
fun uninstalledSearchEngines(context: Context): SearchEngineList = runBlocking { fun uninstalledSearchEngines(context: Context): SearchEngineList = runBlockingIncrement {
val installedIdentifiers = installedSearchEngineIdentifiers(context) val installedIdentifiers = installedSearchEngineIdentifiers(context)
val engineList = loadedSearchEngines.await() val engineList = loadedSearchEngines.await()
return@runBlocking engineList.copy( return@runBlockingIncrement engineList.copy(
list = engineList.list.filterNot { installedIdentifiers.contains(it.identifier) } list = engineList.list.filterNot { installedIdentifiers.contains(it.identifier) }
) )
} }
@ -182,7 +182,7 @@ open class FenixSearchEngineProvider(
context: Context, context: Context,
searchEngine: SearchEngine, searchEngine: SearchEngine,
isCustom: Boolean = false isCustom: Boolean = false
) = runBlocking { ) = runBlockingIncrement {
if (isCustom) { if (isCustom) {
val searchUrl = searchEngine.getSearchTemplate() val searchUrl = searchEngine.getSearchTemplate()
CustomSearchEngineStore.addSearchEngine(context, searchEngine.name, searchUrl) CustomSearchEngineStore.addSearchEngine(context, searchEngine.name, searchUrl)
@ -201,7 +201,7 @@ open class FenixSearchEngineProvider(
context: Context, context: Context,
searchEngine: SearchEngine, searchEngine: SearchEngine,
isCustom: Boolean = false isCustom: Boolean = false
) = runBlocking { ) = runBlockingIncrement {
if (isCustom) { if (isCustom) {
CustomSearchEngineStore.removeSearchEngine(context, searchEngine.identifier) CustomSearchEngineStore.removeSearchEngine(context, searchEngine.identifier)
reload() reload()

@ -9,7 +9,6 @@ import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.CustomTabConfig import mozilla.components.browser.state.state.CustomTabConfig
@ -30,6 +29,7 @@ import mozilla.components.support.utils.toSafeIntent
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.perf.runBlockingIncrement
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -61,7 +61,7 @@ class FennecWebAppIntentProcessor(
val url = safeIntent.dataString val url = safeIntent.dataString
return if (!url.isNullOrEmpty() && matches(intent)) { return if (!url.isNullOrEmpty() && matches(intent)) {
val webAppManifest = runBlocking { loadManifest(safeIntent, url) } val webAppManifest = runBlockingIncrement { loadManifest(safeIntent, url) }
val session = Session(url, private = false, source = SessionState.Source.HOME_SCREEN) val session = Session(url, private = false, source = SessionState.Source.HOME_SCREEN)
session.webAppManifest = webAppManifest session.webAppManifest = webAppManifest

@ -0,0 +1,19 @@
/* 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.ext
import java.util.concurrent.atomic.AtomicInteger
/**
* Increases an AtomicInteger safely.
*/
fun AtomicInteger.getAndIncrementNoOverflow() {
var prev: Int
var next: Int
do {
prev = this.get()
next = if (prev == Integer.MAX_VALUE) prev else prev + 1
} while (!this.compareAndSet(prev, next))
}

@ -11,13 +11,13 @@ import android.util.Patterns
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.core.net.toUri import androidx.core.net.toUri
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.components.concept.fetch.Client import mozilla.components.concept.fetch.Client
import mozilla.components.concept.fetch.Request import mozilla.components.concept.fetch.Request
import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.lib.publicsuffixlist.PublicSuffixList
import mozilla.components.lib.publicsuffixlist.ext.urlToTrimmedHost import mozilla.components.lib.publicsuffixlist.ext.urlToTrimmedHost
import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes
import org.mozilla.fenix.perf.runBlockingIncrement
import java.io.IOException import java.io.IOException
import java.net.IDN import java.net.IDN
import java.util.Locale import java.util.Locale
@ -92,9 +92,10 @@ private fun Uri.isIpv6(): Boolean {
/** /**
* Trim a host's prefix and suffix * Trim a host's prefix and suffix
*/ */
fun String.urlToTrimmedHost(publicSuffixList: PublicSuffixList): String = runBlocking { fun String.urlToTrimmedHost(publicSuffixList: PublicSuffixList): String =
urlToTrimmedHost(publicSuffixList).await() runBlockingIncrement {
} urlToTrimmedHost(publicSuffixList).await()
}
/** /**
* Trims a URL string of its scheme and common prefixes. * Trims a URL string of its scheme and common prefixes.

@ -0,0 +1,45 @@
/* 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/. */
// This class implements the alternative ways to invoke runBlocking with some
// monitoring by wrapping the raw methods. This lint check tells us not to use the raw
// methods so we suppress the check.
@file:Suppress("MozillaRunBlockingCheck")
package org.mozilla.fenix.perf
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import org.mozilla.fenix.ext.getAndIncrementNoOverflow
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext
/**
* Counts the number of runBlocking calls made
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
object RunBlockingCounter {
val count = AtomicInteger(0)
}
/**
* Wrapper around `runBlocking`. RunBlocking seems to be a "fix-all" to return values to the thread
* where the coroutine is called. The official doc explains runBlocking: "Runs a new coroutine and
* blocks the current thread interruptibly until its completion`. This can block our main thread
* which could lead to significant jank. This wrapper aims to count the number of runBlocking call
* to try to limit them as much as possible to encourage alternatives solutions whenever this function
* might be needed.
*/
fun <T> runBlockingIncrement(
context: CoroutineContext? = null,
action: suspend CoroutineScope.() -> T
): T {
RunBlockingCounter.count.getAndIncrementNoOverflow()
return if (context != null) {
runBlocking(context) { action() }
} else {
runBlocking { action() }
}
}

@ -25,7 +25,6 @@ import kotlinx.android.synthetic.main.search_engine_radio_button.view.*
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
@ -37,6 +36,7 @@ import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.perf.runBlockingIncrement
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
import java.util.Locale import java.util.Locale
@ -51,7 +51,7 @@ class AddSearchEngineFragment : Fragment(R.layout.fragment_add_search_engine),
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
availableEngines = runBlocking { availableEngines = runBlockingIncrement {
requireContext() requireContext()
.components .components
.search .search

@ -5,10 +5,10 @@
package org.mozilla.fenix package org.mozilla.fenix
import android.content.Context import android.content.Context
import kotlinx.coroutines.runBlocking
import mozilla.components.support.migration.FennecMigrator import mozilla.components.support.migration.FennecMigrator
import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
import org.mozilla.fenix.migration.MigrationTelemetryListener import org.mozilla.fenix.migration.MigrationTelemetryListener
import org.mozilla.fenix.perf.runBlockingIncrement
/** /**
* An application class which knows how to migrate Fennec data. * An application class which knows how to migrate Fennec data.
@ -81,7 +81,7 @@ class MigratingFenixApplication : FenixApplication() {
.migrateSettings() .migrateSettings()
.build() .build()
runBlocking { runBlockingIncrement {
migrator.migrateAsync(components.migrationStore).await() migrator.migrateAsync(components.migrationStore).await()
} }
} }

@ -0,0 +1,40 @@
/* 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.ext
import org.junit.Assert.assertEquals
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
/* 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/. */
class AtomicIntegerTest {
@Test
fun `Safely increment an AtomicInteger from different coroutines`() {
val integer = AtomicInteger(0)
runBlocking {
for (i in 1..2) {
launch(Dispatchers.Default) {
integer.getAndIncrementNoOverflow()
}
}
}
assertEquals(integer.get(), 2)
}
@Test
fun `Incrementing the AtomicInteger should not overflow`() {
val integer = AtomicInteger(Integer.MAX_VALUE)
integer.getAndIncrementNoOverflow()
assertEquals(integer.get(), Integer.MAX_VALUE)
}
}

@ -0,0 +1,24 @@
/* 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.perf
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
class RunBlockingCounterTest {
@Before
fun setup() {
RunBlockingCounter.count.set(0)
}
@Test
fun `GIVEN we call our custom runBlocking method with counter THEN the latter should increase`() {
assertEquals(0, RunBlockingCounter.count.get())
runBlockingIncrement {}
assertEquals(1, RunBlockingCounter.count.get())
}
}

@ -117,6 +117,9 @@ mozilla-detekt-rules:
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**" excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
MozillaCorrectUnitTestRunner: MozillaCorrectUnitTestRunner:
active: true active: true
MozillaRunBlockingCheck:
active: true
excludes: "**/*Test.kt, **/*Spec.kt, **/test/**, **/androidTest/**"
empty-blocks: empty-blocks:
active: true active: true

@ -16,7 +16,8 @@ class CustomRulesetProvider : RuleSetProvider {
listOf( listOf(
MozillaBannedPropertyAccess(config), MozillaBannedPropertyAccess(config),
MozillaStrictModeSuppression(config), MozillaStrictModeSuppression(config),
MozillaCorrectUnitTestRunner(config) MozillaCorrectUnitTestRunner(config),
MozillaRunBlockingCheck(config)
) )
) )
} }

@ -0,0 +1,39 @@
/* 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.detektrules
import io.gitlab.arturbosch.detekt.api.*
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getCalleeExpressionIfAny
import kotlin.math.exp
private const val VIOLATION_MSG = "Please use `org.mozilla.fenix.perf.runBlockingImplement` instead" +
"because it allows us to monitor the code for performance regressions."
/**
* A check to prevent us from working around mechanisms we implemented in
* @see org.mozilla.fenix.perf.RunBlockingCounter.runBlockingIncrement to count how many runBlocking
* are used.
*
* IF YOU UPDATE THIS FILE NAME, UPDATE CODE OWNERS.
*/
class MozillaRunBlockingCheck(config: Config) : Rule(config) {
override val issue = Issue(
"MozillaRunBlockingCheck",
Severity.Performance,
"Prevents us from working around mechanisms we implemented to count how many " +
"runBlocking are used",
Debt.TWENTY_MINS
)
override fun visitImportDirective(importDirective: KtImportDirective) {
if (importDirective.importPath?.toString() == "kotlinx.coroutines.runBlocking") {
report(CodeSmell(issue, Entity.from(importDirective), VIOLATION_MSG))
}
}
}
Loading…
Cancel
Save