Skip to content

Commit

Permalink
Add new lint rule to avoid robolectric runner (#2784)
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosholgado authored Jan 30, 2023
1 parent de6c5a6 commit adf1d62
Show file tree
Hide file tree
Showing 23 changed files with 214 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.adclick.impl

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.adclick.api.AdClickManager
import com.duckduckgo.adclick.impl.pixels.AdClickPixelName
import com.duckduckgo.adclick.impl.pixels.AdClickPixels
Expand All @@ -31,11 +32,8 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoInteractions
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
@RunWith(AndroidJUnit4::class)
class DuckDuckGoAdClickManagerTest {

private val mockAdClickData: AdClickData = mock()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.duckduckgo.adclick.impl.pixels
import android.content.Context
import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.adclick.impl.Exemption
import com.duckduckgo.app.global.api.InMemorySharedPreferences
import com.duckduckgo.app.statistics.pixels.Pixel
Expand All @@ -35,12 +36,9 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.threeten.bp.Instant

@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
@RunWith(AndroidJUnit4::class)
class RealAdClickPixelsTest {

private lateinit var testee: AdClickPixels
Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ android {
}
lint {
abortOnError true
ignoreTestSources true
ignoreTestSources false
baseline file("lint-baseline.xml")
}
testOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.app.browser

import android.annotation.SuppressLint
import android.content.Context
import android.webkit.WebStorage
import android.webkit.WebView
Expand All @@ -35,6 +36,7 @@ import org.mockito.kotlin.verify

@ExperimentalCoroutinesApi
@Suppress("RemoveExplicitTypeArguments")
@SuppressLint("NoHardcodedCoroutineDispatcher")
class WebViewDataManagerTest {

private val mockCookieManager: DuckDuckGoCookieManager = mock()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.app.referencetests

import android.annotation.SuppressLint
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.os.Build
Expand Down Expand Up @@ -57,6 +58,7 @@ import org.mockito.kotlin.mock

@ExperimentalCoroutinesApi
@RunWith(Parameterized::class)
@SuppressLint("NoHardcodedCoroutineDispatcher")
class FireproofingReferenceTest(private val testCase: TestCase) {

private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.app.referencetests

import android.annotation.SuppressLint
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.os.Build
Expand Down Expand Up @@ -68,6 +69,7 @@ import org.threeten.bp.temporal.ChronoUnit

@ExperimentalCoroutinesApi
@RunWith(Parameterized::class)
@SuppressLint("NoHardcodedCoroutineDispatcher")
class FirstPartyCookiesReferenceTest(private val testCase: TestCase) {

private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.cookies.impl

import android.annotation.SuppressLint
import android.webkit.CookieManager
import androidx.room.Room
import androidx.test.platform.app.InstrumentationRegistry
Expand Down Expand Up @@ -45,6 +46,7 @@ import org.junit.Test
import org.mockito.kotlin.*

@ExperimentalCoroutinesApi
@SuppressLint("NoHardcodedCoroutineDispatcher")
class SQLCookieRemoverTest {

private val context = InstrumentationRegistry.getInstrumentation().targetContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.duckduckgo.cookies.impl.features.firstparty

import android.annotation.SuppressLint
import android.database.sqlite.SQLiteDatabase
import android.os.Build
import android.webkit.CookieManager
Expand Down Expand Up @@ -56,6 +57,7 @@ import org.threeten.bp.format.DateTimeFormatter
import org.threeten.bp.temporal.ChronoUnit

@ExperimentalCoroutinesApi
@SuppressLint("NoHardcodedCoroutineDispatcher")
class RealFirstPartyCookiesModifierTest {

private val context = InstrumentationRegistry.getInstrumentation().targetContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@

package com.duckduckgo.autofill.impl.ui.credential.management.suggestion

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
import com.duckduckgo.autofill.impl.ui.credential.management.AutofillManagementRecyclerAdapter.ListItem
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
@RunWith(AndroidJUnit4::class)
class SuggestionListBuilderTest {

val testee = SuggestionListBuilder(context = InstrumentationRegistry.getInstrumentation().targetContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@

package com.duckduckgo.autofill.impl.ui.credential.management.suggestion

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
import com.duckduckgo.autofill.store.urlmatcher.AutofillDomainNameUrlMatcher
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
@RunWith(AndroidJUnit4::class)
class SuggestionMatcherTest {

private val testee = SuggestionMatcher(AutofillDomainNameUrlMatcher())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@

package com.duckduckgo.autofill.store.urlmatcher

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
@RunWith(AndroidJUnit4::class)
class AutofillDomainNameUrlMatcherTest {

private val testee = AutofillDomainNameUrlMatcher()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import android.webkit.ValueCallback
import com.duckduckgo.app.CoroutineTestRule
import com.duckduckgo.cookies.api.CookieManagerProvider
import com.duckduckgo.cookies.api.RemoveCookiesStrategy
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -64,7 +63,7 @@ class WebViewCookieManagerTest {
fun whenCookiesRemovedThenInternalCookiesRecreated() = runTest {
givenCookieManagerWithCookies(ddgCookie, externalHostCookie)

withContext(Dispatchers.Main) {
withContext(coroutineRule.testDispatcherProvider.main()) {
testee.removeExternalCookies()
}

Expand All @@ -75,7 +74,7 @@ class WebViewCookieManagerTest {
fun whenCookiesStoredThenRemoveCookiesExecuted() = runTest {
givenCookieManagerWithCookies(ddgCookie, externalHostCookie)

withContext(Dispatchers.Main) {
withContext(coroutineRule.testDispatcherProvider.main()) {
testee.removeExternalCookies()
}

Expand All @@ -86,7 +85,7 @@ class WebViewCookieManagerTest {
fun whenCookiesStoredThenFlushBeforeAndAfterInteractingWithCookieManager() = runTest {
givenCookieManagerWithCookies(ddgCookie, externalHostCookie)

withContext(Dispatchers.Main) {
withContext(coroutineRule.testDispatcherProvider.main()) {
testee.removeExternalCookies()
}

Expand All @@ -103,7 +102,7 @@ class WebViewCookieManagerTest {
fun whenNoCookiesThenRemoveProcessNotExecuted() = runTest {
givenCookieManagerWithCookies()

withContext(Dispatchers.Main) {
withContext(coroutineRule.testDispatcherProvider.main()) {
testee.removeExternalCookies()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,16 @@

package com.duckduckgo.downloads.impl

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.duckduckgo.downloads.api.FileDownloader
import java.io.File
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
@RunWith(AndroidJUnit4::class)
class PendingFileDownloadCompressTest {

@Test
Expand Down
2 changes: 1 addition & 1 deletion gradle/android-library.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ android {

lint {
abortOnError true
ignoreTestSources true
ignoreTestSources false
}

buildTypes {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2023 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.lint

import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope.JAVA_FILE
import com.android.tools.lint.detector.api.Scope.TEST_SOURCES
import com.android.tools.lint.detector.api.Severity.ERROR
import com.android.tools.lint.detector.api.SourceCodeScanner
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.skipParenthesizedExprDown
import java.util.*

@Suppress("UnstableApiUsage")
class NoRobolectricTestRunnerDetector : Detector(), SourceCodeScanner {
override fun getApplicableUastTypes() = listOf(UAnnotation::class.java)

override fun createUastHandler(context: JavaContext): UElementHandler = NoInternalImportHandler(context)

internal class NoInternalImportHandler(private val context: JavaContext) : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
if (node.qualifiedName?.contains("RunWith") == true) {
val attributes = node.attributeValues
if (attributes.size == 1) {
val attribute = attributes[0]
val value = attribute.expression.skipParenthesizedExprDown()
val klass = value?.asSourceString() ?: return
if (!klass.contains("Parameterized") && klass.contains("RobolectricTestRunner")) {
context.report(
NO_ROBOLECTRIC_TEST_RUNNER_ISSUE,
node,
context.getNameLocation(node),
"The RobolectricTestRunner parameter must not be used in the RunWith annotation."
)
}
}
}
}
}

companion object {
val NO_ROBOLECTRIC_TEST_RUNNER_ISSUE = Issue.create("NoRobolectricTestRunnerAnnotation",
"The RobolectricTestRunner parameter must not be used in the RunWith annotation",
"""
The @RunWith(RobolectricTestRunner::class) annotation must not be used.
Use @RunWith(AndroidJUnit4::class) instead.
""".trimIndent(),
Category.CORRECTNESS, 10, ERROR,
Implementation(NoRobolectricTestRunnerDetector::class.java, EnumSet.of(JAVA_FILE, TEST_SOURCES))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.duckduckgo.lint.NoFragmentDetector.Companion.NO_FRAGMENT_ISSUE
import com.duckduckgo.lint.NoHardcodedCoroutineDispatcherDetector.Companion.NO_HARCODED_COROUTINE_DISPATCHER
import com.duckduckgo.lint.NoLifecycleObserverDetector.Companion.NO_LIFECYCLE_OBSERVER_ISSUE
import com.duckduckgo.lint.NoRetrofitCreateMethodCallDetector.Companion.NO_RETROFIT_CREATE_CALL
import com.duckduckgo.lint.NoRobolectricTestRunnerDetector.Companion.NO_ROBOLECTRIC_TEST_RUNNER_ISSUE
import com.duckduckgo.lint.NoSingletonDetector.Companion.NO_SINGLETON_ISSUE
import com.duckduckgo.lint.NoSystemLoadLibraryDetector.Companion.NO_SYSTEM_LOAD_LIBRARY
import com.duckduckgo.lint.strings.MissingInstructionDetector.Companion.MISSING_INSTRUCTION
Expand All @@ -51,6 +52,7 @@ class DuckDuckGoIssueRegistry : IssueRegistry() {
MISSING_INSTRUCTION,
PLACEHOLDER_MISSING_POSITION,
NO_RETROFIT_CREATE_CALL,
NO_ROBOLECTRIC_TEST_RUNNER_ISSUE,

// Android Design System
DEPRECATED_BUTTON_IN_XML,
Expand Down
Loading

0 comments on commit adf1d62

Please sign in to comment.