"Fossies" - the Fresh Open Source Software Archive

Member "gradle-8.1.1/build-logic/binary-compatibility/src/test/kotlin/gradlebuild/binarycompatibility/AbstractBinaryCompatibilityTest.kt" (20 Apr 2023, 13217 Bytes) of package /linux/misc/gradle-8.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Kotlin source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "AbstractBinaryCompatibilityTest.kt": 7.6.0_vs_8.0.0.

    1 /*
    2  * Copyright 2020 the original author or authors.
    3  *
    4  * Licensed under the Apache License, Version 2.0 (the "License");
    5  * you may not use this file except in compliance with the License.
    6  * You may obtain a copy of the License at
    7  *
    8  *      http://www.apache.org/licenses/LICENSE-2.0
    9  *
   10  * Unless required by applicable law or agreed to in writing, software
   11  * distributed under the License is distributed on an "AS IS" BASIS,
   12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13  * See the License for the specific language governing permissions and
   14  * limitations under the License.
   15  */
   16 
   17 package gradlebuild.binarycompatibility
   18 
   19 import org.gradle.kotlin.dsl.*
   20 import org.gradle.testkit.runner.BuildResult
   21 import org.gradle.testkit.runner.GradleRunner
   22 import org.gradle.testkit.runner.UnexpectedBuildFailure
   23 import org.hamcrest.CoreMatchers
   24 import org.hamcrest.MatcherAssert.assertThat
   25 import org.junit.Assert.assertFalse
   26 import org.junit.Assert.assertTrue
   27 import org.junit.Rule
   28 import org.junit.rules.TemporaryFolder
   29 import java.io.File
   30 import java.nio.file.Files
   31 
   32 
   33 abstract class AbstractBinaryCompatibilityTest {
   34 
   35     @get:Rule
   36     val tmpDir = TemporaryFolder()
   37 
   38     private
   39     val rootDir: File
   40         get() = tmpDir.root
   41 
   42     internal
   43     fun checkBinaryCompatibleKotlin(v1: String = "", v2: String, block: CheckResult.() -> Unit = {}): CheckResult =
   44         runKotlinBinaryCompatibilityCheck(v1, v2) {
   45             assertBinaryCompatible()
   46             block()
   47         }
   48 
   49     internal
   50     fun checkNotBinaryCompatibleKotlin(v1: String = "", v2: String, block: CheckResult.() -> Unit = {}): CheckResult =
   51         runKotlinBinaryCompatibilityCheck(v1, v2) {
   52             assertNotBinaryCompatible()
   53             block()
   54         }
   55 
   56     internal
   57     fun checkBinaryCompatibleJava(v1: String = "", v2: String, block: CheckResult.() -> Unit = {}): CheckResult =
   58         runJavaBinaryCompatibilityCheck(v1, v2) {
   59             assertBinaryCompatible()
   60             block()
   61         }
   62 
   63     internal
   64     fun checkNotBinaryCompatibleJava(v1: String = "", v2: String, block: CheckResult.() -> Unit = {}): CheckResult =
   65         runJavaBinaryCompatibilityCheck(v1, v2) {
   66             assertNotBinaryCompatible()
   67             block()
   68         }
   69 
   70     internal
   71     fun checkBinaryCompatible(v1: File.() -> Unit = {}, v2: File.() -> Unit = {}, block: CheckResult.() -> Unit = {}): CheckResult =
   72         runBinaryCompatibilityCheck(v1, v2) {
   73             assertBinaryCompatible()
   74             block()
   75         }
   76 
   77     internal
   78     fun checkNotBinaryCompatible(v1: File.() -> Unit = {}, v2: File.() -> Unit = {}, block: CheckResult.() -> Unit = {}): CheckResult =
   79         runBinaryCompatibilityCheck(v1, v2) {
   80             assertNotBinaryCompatible()
   81             block()
   82         }
   83 
   84     private
   85     fun CheckResult.assertBinaryCompatible() {
   86         assertTrue(richReport.toAssertionMessage("Expected to be compatible but the check failed"), isBinaryCompatible)
   87     }
   88 
   89     private
   90     fun CheckResult.assertNotBinaryCompatible() {
   91         assertFalse(richReport.toAssertionMessage("Expected to be breaking but the check passed"), isBinaryCompatible)
   92     }
   93 
   94     private
   95     fun RichReport.toAssertionMessage(message: String) =
   96         if (isEmpty) "$message with an empty report"
   97         else "$message\n${toText().prependIndent("    ")}"
   98 
   99     private
  100     fun runKotlinBinaryCompatibilityCheck(v1: String, v2: String, block: CheckResult.() -> Unit = {}): CheckResult =
  101         runBinaryCompatibilityCheck(
  102             v1 = {
  103                 withFile(
  104                     "kotlin/com/example/Source.kt",
  105                     """
  106                     package com.example
  107 
  108                     import org.gradle.api.Incubating
  109                     import javax.annotation.Nullable
  110 
  111                     $v1
  112                     """
  113                 )
  114             },
  115             v2 = {
  116                 withFile(
  117                     "kotlin/com/example/Source.kt",
  118                     """
  119                     package com.example
  120 
  121                     import org.gradle.api.Incubating
  122                     import javax.annotation.Nullable
  123 
  124                     $v2
  125                     """
  126                 )
  127             },
  128             block = block
  129         )
  130 
  131     private
  132     fun runJavaBinaryCompatibilityCheck(v1: String, v2: String, block: CheckResult.() -> Unit = {}): CheckResult =
  133         runBinaryCompatibilityCheck(
  134             v1 = {
  135                 withFile(
  136                     "java/com/example/Source.java",
  137                     """
  138                     package com.example;
  139 
  140                     import org.gradle.api.Incubating;
  141                     import javax.annotation.Nullable;
  142 
  143                     $v1
  144                     """
  145                 )
  146             },
  147             v2 = {
  148                 withFile(
  149                     "java/com/example/Source.java",
  150                     """
  151                     package com.example;
  152 
  153                     import org.gradle.api.Incubating;
  154                     import javax.annotation.Nullable;
  155 
  156                     $v2
  157                     """
  158                 )
  159             },
  160             block = block
  161         )
  162 
  163     /**
  164      * Runs the binary compatibility check against two source trees.
  165      *
  166      * The fixture build supports both Java and Kotlin sources.
  167      *
  168      * @param v1 sources producer for V1, receiver is the `src/main` directory
  169      * @param v2 sources producer for V2, receiver is the `src/main` directory
  170      * @param block convenience block invoked on the result
  171      * @return the check result
  172      */
  173     private
  174     fun runBinaryCompatibilityCheck(v1: File.() -> Unit, v2: File.() -> Unit, block: CheckResult.() -> Unit = {}): CheckResult {
  175         rootDir.withFile("version.txt", "1.0")
  176 
  177         val inputBuildDir = rootDir.withUniqueDirectory("input-build").apply {
  178 
  179             withSettings("""include("v1", "v2", "binary-compatibility")""")
  180             withBuildScript(
  181                 """
  182                     import gradlebuild.identity.extension.ModuleIdentityExtension
  183 
  184                     plugins {
  185                         base
  186                         kotlin("jvm") version "$embeddedKotlinVersion" apply false
  187                     }
  188                     subprojects {
  189                         apply(plugin = "gradlebuild.module-identity")
  190                         apply(plugin = "kotlin")
  191                         the<ModuleIdentityExtension>().baseName.set("api-module")
  192                         repositories {
  193                             mavenCentral()
  194                         }
  195                         dependencies {
  196                             "implementation"(gradleApi())
  197                             "implementation"(kotlin("stdlib"))
  198                         }
  199                     }
  200                     project(":v1") {
  201                         version = "1.0"
  202                     }
  203                     project(":v2") {
  204                         version = "2.0"
  205                     }
  206                 """
  207             )
  208             withDirectory("v1/src/main").v1()
  209             withDirectory("v2/src/main").v2()
  210             withDirectory("binary-compatibility").apply {
  211                 withBuildScript(
  212                     """
  213                     import japicmp.model.JApiChangeStatus
  214                     import gradlebuild.binarycompatibility.*
  215                     import gradlebuild.binarycompatibility.filters.*
  216 
  217                     tasks.register<JapicmpTask>("checkBinaryCompatibility") {
  218 
  219                         dependsOn(":v1:jar", ":v2:jar")
  220 
  221                         val v1 = rootProject.project(":v1")
  222                         val v1Jar = v1.tasks.named("jar")
  223                         val v2 = rootProject.project(":v2")
  224                         val v2Jar = v2.tasks.named("jar")
  225 
  226                         oldArchives.from(v1Jar)
  227                         oldClasspath.from(v1.configurations.named("runtimeClasspath"), v1Jar)
  228 
  229                         newArchives.from(v2Jar)
  230                         newClasspath.from(v2.configurations.named("runtimeClasspath"), v2Jar)
  231 
  232                         onlyModified.set(false)
  233                         failOnModification.set(false) // we rely on the rich report to fail
  234 
  235                         txtOutputFile.set(file("build/japi-report.txt"))
  236 
  237                         richReport {
  238 
  239                             title.set("Gradle Binary Compatibility Check")
  240                             destinationDir.set(file("build/japi"))
  241                             reportName.set("japi.html")
  242 
  243                             includedClasses.set(listOf(".*"))
  244                             excludedClasses.set(emptyList())
  245 
  246                         }
  247 
  248                         BinaryCompatibilityHelper.setupJApiCmpRichReportRules(
  249                             this,
  250                             AcceptedApiChanges.parse("{acceptedApiChanges:[]}"),
  251                             rootProject.files("v2/src/main/kotlin"),
  252                             "2.0",
  253                             file("test-api-changes.json"),
  254                             rootProject.layout.projectDirectory
  255                         )
  256                     }
  257                     """
  258                 )
  259             }
  260         }
  261 
  262         val runner = GradleRunner.create()
  263             .withProjectDir(inputBuildDir)
  264             .withPluginClasspath()
  265             .withArguments(":binary-compatibility:checkBinaryCompatibility", "-s")
  266 
  267         val (buildResult, failure) = try {
  268             runner.build()!! to null
  269         } catch (ex: UnexpectedBuildFailure) {
  270             ex.buildResult!! to ex
  271         }
  272 
  273         println(buildResult.output)
  274 
  275         val richReportFile = inputBuildDir.resolve("binary-compatibility/build/japi/japi.html").apply {
  276             assertTrue("Rich report file exists", isFile)
  277         }
  278 
  279         return CheckResult(failure, scrapeRichReport(richReportFile), buildResult).apply {
  280             println(richReport.toText())
  281             block()
  282         }
  283     }
  284 
  285     internal
  286     data class CheckResult(
  287         val checkFailure: UnexpectedBuildFailure?,
  288         val richReport: RichReport,
  289         val buildResult: BuildResult
  290     ) {
  291 
  292         val isBinaryCompatible = checkFailure == null
  293 
  294         fun assertEmptyReport() {
  295             assertHasNoError()
  296             assertHasNoWarning()
  297             assertHasNoInformation()
  298         }
  299 
  300         fun assertHasNoError() {
  301             assertTrue("Has no error (${richReport.errors})", richReport.errors.isEmpty())
  302         }
  303 
  304         fun assertHasNoWarning() {
  305             assertTrue("Has no warning (${richReport.warnings})", richReport.warnings.isEmpty())
  306         }
  307 
  308         fun assertHasNoInformation() {
  309             assertTrue("Has no information (${richReport.information})", richReport.information.isEmpty())
  310         }
  311 
  312         fun assertHasErrors(vararg errors: String) {
  313             assertThat("Has errors", richReport.errors.map { it.message }, CoreMatchers.equalTo(errors.toList()))
  314         }
  315 
  316         fun assertHasWarnings(vararg warnings: String) {
  317             assertThat("Has warnings", richReport.warnings.map { it.message }, CoreMatchers.equalTo(warnings.toList()))
  318         }
  319 
  320         fun assertHasInformation(vararg information: String) {
  321             assertThat("Has information", richReport.information.map { it.message }, CoreMatchers.equalTo(information.toList()))
  322         }
  323 
  324         fun assertHasErrors(vararg errors: List<String>) {
  325             assertHasErrors(*errors.toList().flatten().toTypedArray())
  326         }
  327 
  328         fun assertHasErrors(vararg errorWithDetail: Pair<String, List<String>>) {
  329             assertThat("Has errors", richReport.errors, CoreMatchers.equalTo(errorWithDetail.map { ReportMessage(it.first, it.second) }))
  330         }
  331 
  332         fun newApi(thing: String, desc: String): String =
  333             "$thing ${describe(thing, desc)}: New public API in 2.0 (@Incubating)"
  334 
  335         fun added(thing: String, desc: String): List<String> =
  336             listOf(
  337                 "$thing ${describe(thing, desc)}: Is not annotated with @Incubating.",
  338                 "$thing ${describe(thing, desc)}: Is not annotated with @since 2.0."
  339             )
  340 
  341         fun removed(thing: String, desc: String): Pair<String, List<String>> =
  342             "$thing ${describe(thing, desc)}: Is not binary compatible." to listOf("$thing has been removed")
  343 
  344         private
  345         fun describe(thing: String, desc: String) =
  346             if (thing == "Field") desc else "com.example.$desc"
  347     }
  348 
  349     protected
  350     fun File.withFile(path: String, text: String = ""): File =
  351         resolve(path).apply {
  352             parentFile.mkdirs()
  353             writeText(text.trimIndent())
  354         }
  355 
  356     private
  357     fun File.withUniqueDirectory(prefixPath: String): File =
  358         Files.createTempDirectory(
  359             withDirectory(prefixPath.substringBeforeLast("/")).toPath(),
  360             prefixPath.substringAfterLast("/")
  361         ).toFile()
  362 
  363     private
  364     fun File.withDirectory(path: String): File =
  365         resolve(path).apply {
  366             mkdirs()
  367         }
  368 
  369     private
  370     fun File.withSettings(text: String = ""): File =
  371         withFile("settings.gradle.kts", text)
  372 
  373     private
  374     fun File.withBuildScript(text: String = ""): File =
  375         withFile("build.gradle.kts", text)
  376 }