'com.android.build.gradle.LibraryExtension_Decorated cannot be cast to (.....).BaseExtension

Objective :
I have developed Test for CodeCoveragePluginForAndroidConnectedTest .

Steps to reproduce error : ./gradlew clean build

A problem occurred evaluating root project 'QualityToolsTestProject'. > Failed to apply plugin 'kotlin-android'.

class com.android.build.gradle.LibraryExtension_Decorated cannot be cast to class com.android.build.gradle.BaseExtension

(com.android.build.gradle.LibraryExtension_Decorated is in unnamed module of loader

org.gradle.internal.classloader.VisitableURLClassLoader @5a8b9853; com.android.build.gradle.BaseExtension is in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader @7685facc)

Note :

So "Failed to apply plugin 'kotlin-android' in my below test case is because of two different type of gradle implementation ? Project with kts exension (build.gradle.kts)

Test cases with groovy(build.gradle) because in below code i am creating build.gradle for Test cases and corresponding file in vitural gradle memory using groovy.

To understand complete code are below.

Input : TEST_PROJECT_NAME is QualityToolsTestProject in below code

package com.oujji.sdk.android.gradle.quality

import com.ing.sdk.android.gradle.quality.test.*
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.io.File

class CodeCoveragePluginForAndroidConnectedTest : BaseTest() {

    private lateinit var buildFile: File
    private lateinit var sourceMainDir: File
    private lateinit var sourceAndroidTestDir: File

    @Before
    fun setup() {
        testProjectDir.apply {
            createGradleProperties()
            createSettingsFile(projectName = TEST_PROJECT_NAME)
            buildFile = createProjectFile("build.gradle") {
                """
                    buildscript {
                        repositories {
                            google()
                            mavenCentral()
                        }
                    
                        dependencies {
                            classpath 'com.android.tools.build:gradle:4.0.1'
                            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
                        }
                    }
                    
                    plugins {
                        id 'code-coverage'
                    }
                    
                    apply plugin: 'com.android.library'
                    apply plugin: 'kotlin-android'
                    apply plugin: 'kotlin-android-extensions'
                    
                    repositories {
                        google()
                        mavenCentral()
                    }
                    
                    android {
                        compileSdkVersion 29
                        buildToolsVersion "29.0.2"
                        defaultConfig {
                            targetSdkVersion 29
                            minSdkVersion 21
                            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
                        }
                        
                        buildTypes {
                            debug {
                                testCoverageEnabled = true
                            }
                        }
                    }
                    
                    dependencies {
                        implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61"
                        
                        androidTestImplementation "androidx.test.ext:junit:1.1.1"
                        androidTestImplementation "androidx.test:runner:1.2.0"
                        
                        testImplementation "junit:junit:4.12"
                    }
                
                """
            }
            createManifestFile()
            sourceMainDir = createSourceMainDirectories()
            sourceAndroidTestDir = createSourceAndroidTestDirectories()
        }
    }

    @Test
    fun `when applying code coverage for android, code coverage tasks should be added`() {
        runBuild("tasks") {
            checkOutputContains("coverage - Generate Jacoco coverage reports on test builds.")
            checkOutputContains("androidTestCoverageDebug - Generate Jacoco coverage reports on the Debug build.")
            checkOutputContains("coveragePrintDebug - Print the code coverage of the Debug build using the coverageDebug report.")
            checkOutputContains("coveragePrintRelease - Print the code coverage of the Release build using the coverageRelease report.")
            checkOutputContains("coverageVerificationDebug - Run Jacoco coverage verification on the Debug build.")
            checkOutputContains("coverageVerificationRelease - Run Jacoco coverage verification on the Release build.")
        }
    }

    @Test
    fun `when coverage below default minimum, verify task outcome should be FAILED`() {
        // Setup with 40% coverage, default is 50%
        buildFile {
            """
                codeCoverage {
                    failOnBelowMinimumCoverage true
                }
            """
        }
        "One".also { className ->
            sourceMainDir.createSourceKotlinFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        "Two".also { className ->
            sourceMainDir.createSourceJavaFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        sourceMainDir.createSourceJavaFile(className = "Three")
        sourceMainDir.createSourceKotlinFile(className = "Four")
        sourceMainDir.createSourceKotlinFile(className = "Five")

        runBuildAndFail("androidTestCoverageDebug") {
            assertEquals(TaskOutcome.FAILED, task(":coverageVerificationDebug")?.outcome)
            checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 40.00%")
            checkOutputContains("Rule violated for bundle $TEST_PROJECT_NAME: instructions covered ratio is 0.4, but expected minimum is 0.5")
        }
    }

    @Test
    fun `when coverage above default minimum, verify task outcome should be SUCCESS`() {
        // Setup with 66% coverage, default is 50%
        "One".also { className ->
            sourceMainDir.createSourceJavaFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        "Two".also { className ->
            sourceMainDir.createSourceKotlinFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        sourceMainDir.createSourceJavaFile(className = "Three")

        runBuild("androidTestCoverageDebug") {
            assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
            checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 66.67%")
        }
    }

    @Test
    fun `when coverage below configured minimum, verify task outcome should be FAILED`() {
        // Setup with 66% coverage, configured to 80%
        buildFile {
            """
                codeCoverage {
                    minimumCoverage 0.8
                    failOnBelowMinimumCoverage true
                }
            """
        }
        "One".also { className ->
            sourceMainDir.createSourceKotlinFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        "Two".also { className ->
            sourceMainDir.createSourceJavaFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        sourceMainDir.createSourceJavaFile(className = "Three")

        runBuildAndFail("androidTestCoverageDebug") {
            assertEquals(TaskOutcome.FAILED, task(":coverageVerificationDebug")?.outcome)
            checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 66.67%")
        }
    }

    @Test
    fun `when coverage below configured minimum but allowed, verify task outcome should be SUCCESS with warning`() {
        // Setup with 66% coverage, configured to 80%, with only warning
        buildFile {
            """
                codeCoverage {
                    minimumCoverage 0.8
                    failOnBelowMinimumCoverage false
                }
            """
        }
        "One".also { className ->
            sourceMainDir.createSourceKotlinFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        "Two".also { className ->
            sourceMainDir.createSourceJavaFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        sourceMainDir.createSourceJavaFile(className = "Three")

        runBuild("androidTestCoverageDebug") {
            assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
            checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 66.67%")
            checkOutputContains("Rule violated for bundle $TEST_PROJECT_NAME: instructions covered ratio is 0.6")
        }
    }

    @Test
    fun `when coverage above configured minimum, verify task outcome should be SUCCESS`() {
        // Setup with 80% coverage, configured to 75%
        buildFile {
            """
                codeCoverage {
                    minimumCoverage 0.75
                }
            """
        }
        "One".also { className ->
            sourceMainDir.createSourceKotlinFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        "Two".also { className ->
            sourceMainDir.createSourceJavaFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        "Three".also { className ->
            sourceMainDir.createSourceJavaFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        "Four".also { className ->
            sourceMainDir.createSourceKotlinFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        sourceMainDir.createSourceJavaFile(className = "Five")

        runBuild("androidTestCoverageDebug") {
            assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
            checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 80.00%")
        }
    }

    @Test
    fun `when coverage has configured excludes, verify task should ignore classes matching excludes`() {
        // Setup with 50% coverage when excludes are disregarded, configured high but just with warning
        buildFile {
            """
                codeCoverage {
                    testExcludes = [
                        '**/Two*',
                        '**/Three*',
                        '**/Four*'
                    ]
                    minimumCoverage 0.9
                    failOnBelowMinimumCoverage false
                }
            """
        }
        "One".also { className ->
            sourceMainDir.createSourceKotlinFile(className = className)
            sourceAndroidTestDir.createAndroidTestKotlinFile(testFor = className)
        }
        sourceMainDir.createSourceKotlinFile(className = "Two")
        sourceMainDir.createSourceJavaFile(className = "Three")
        sourceMainDir.createSourceKotlinFile(className = "Four")
        sourceMainDir.createSourceJavaFile(className = "Five")

        runBuild("androidTestCoverageDebug") {
            assertEquals(TaskOutcome.SUCCESS, task(":coverageVerificationDebug")?.outcome)
            checkOutputContains("Code coverage for bundle $TEST_PROJECT_NAME: 50.00%")
            checkOutputContains("Rule violated for bundle $TEST_PROJECT_NAME: instructions covered ratio is 0.5")
        }
    }
}

Abstract BaseTest claas

package com.oujji.sdk.android.gradle.quality.test

import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.junit.Rule
import org.junit.rules.TemporaryFolder

abstract class BaseTest {
    @Rule
    @JvmField
    val testProjectDir = TemporaryFolder()

    private fun createBuild(arguments: List<String> = listOf()): GradleRunner = GradleRunner.create()
        .withPluginClasspath()
        .withProjectDir(testProjectDir.root)
        .withArguments(arguments)

    fun runBuild(vararg arguments: String, withResult: BuildResult.() -> (Unit) = {}): BuildResult =
        createBuild(arguments.asList()).build().apply { withResult() }

    fun runBuildAndFail(vararg arguments: String, withResult: BuildResult.() -> (Unit) = {}): BuildResult =
        createBuild(arguments.asList()).buildAndFail().apply { withResult() }

    companion object {
        const val TEST_PROJECT_NAME = "QualityToolsTestProject"
    }
}

GradleFiles.kt

package com.oujji.sdk.android.gradle.quality.test

import org.junit.rules.TemporaryFolder
import java.net.URL

internal fun TemporaryFolder.createGradleProperties() {
    createProjectFile("gradle.properties") {
        val proxyProps = System.getenv("https_proxy") ?: System.getenv("http_proxy")
            ?.let { if(it.startsWith("http")) it else "http://$it" }
            ?.let { URL(it) }
            ?.let {
                "\nsystemProp.http.proxyHost=${it.host}\nsystemProp.http.proxyPort=${it.port}"
            } ?: ""

        "android.useAndroidX=true\n$proxyProps"
    }
}

SetingsFile.kt

package com.oujji.sdk.android.gradle.quality.test

import org.junit.rules.TemporaryFolder

internal fun TemporaryFolder.createSettingsFile(projectName: String) = createProjectFile("settings.gradle") {
    """
        rootProject.name = '${projectName}'
    """
}

ProjectFile.kt

package com.oujji.sdk.android.gradle.quality.test

import org.junit.rules.TemporaryFolder
import java.io.File

operator fun File.invoke(append: () -> String) = appendText(append().trimIndent())

 fun File.createProjectFile(fileName: String, contents: () -> String) =
    File(this, fileName).also { sourceFile ->
        sourceFile.createNewFile()
        sourceFile.writeText(contents().trimIndent())
    }

 fun TemporaryFolder.createProjectFile(fileName: String, contents: () -> String) =
    newFile(fileName).apply { writeText(contents().trimIndent()) }

Environement :

------------------------------------------------------------
Gradle 7.1.1
------------------------------------------------------------

Build time:   2021-07-02 12:16:43 UTC
Revision:     774525a055494e0ece39f522ac7ad17498ce032c

Kotlin:       1.4.31
Groovy:       3.0.7
Ant:          Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM:          11.0.12 (Oracle Corporation 11.0.12+8-LTS-237)
OS:           Mac OS X 11.5 x86_64


Solution 1:[1]

I had a problem that was giving me the same error message as Kumar states. I can't decern whether my problem and Kumar problem are the same, but I too was trying to manipulate a gradle build. In Kumar's cast, he seems to be trying to generate a build.gradle. In my case, I'm only trying to manipulate the "android" block of a build.gradle. So, there's at least some reason to believe that my solution might be related.

I did two things to solve the problem, but I'm not sure if both are necessary. First, I upgraded gradle from version 7.4 to 7.4.2. The release notes for 7.4.2 indicated numerous fixes and of course recommended developers to upgrade to this version. After doing that, I tried to do the build again. As a result of doing this, the error I was getting shifted. Instead of complaining about the bad cast, it now indicated that I had a problem in my AndroidManifest.xml file. My manifest file had the wrong class name in an application activity section of the manifest. I corrected the name, and the build worked fine.

For the record, I will state that I actual got displayed a different error message in my build, but by debugging my gradle build, I was able to catch the initial exception, and that exception was exactly the same as Kumar exception and that's why I say I got same error message.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tom Rutchik