From c93b1d2aabfc7b125ca0775c856176e1552c3114 Mon Sep 17 00:00:00 2001 From: "sebastian.sellmair" Date: Fri, 24 Jun 2022 11:45:01 +0200 Subject: [PATCH] [Gradle][MPP] MPPBuildReproducibilityIT: Use custom diff implementation ^KT-52740 Verification Pending --- .../gradle/MPPBuildReproducibilityIT.kt | 93 +++++++++++++++---- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MPPBuildReproducibilityIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MPPBuildReproducibilityIT.kt index 3baa6ef6087..4ff5ca8029f 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MPPBuildReproducibilityIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/MPPBuildReproducibilityIT.kt @@ -10,11 +10,11 @@ import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.condition.OS +import java.io.File import kotlin.io.path.appendText import kotlin.test.assertTrue +import kotlin.test.fail -/* Only supported on platforms that support the 'diff' util */ -@OsCondition(supportedOn = [OS.LINUX, OS.MAC]) @MppGradlePluginTests @DisplayName("Multiplatform Build Reproducibility") class MPPBuildReproducibilityIT : KGPBaseTest() { @@ -38,6 +38,14 @@ class MPPBuildReproducibilityIT : KGPBaseTest() { * - Run the 'diff' tool to ensure that the published artifacts are equal in repo1 & repo2 */ private fun TestProject.assertConsecutiveBuildsProduceSameBinaries() { + fun File.relativeToProject() = this.relativeTo(projectPath.toFile()) + + fun Diff.reportString(): String = when (this) { + is Diff.DifferentContent -> "Different File Contents: ${first.relativeToProject()} <-> ${second.relativeToProject()}" + is Diff.MissingFile -> "Missing File: ${file.relativeToProject()}" + is Diff.TypeMismatch -> "Expected directory, but is file: ${file.relativeToProject()}" + } + buildGradleKts.appendText( """ val repo1 = buildDir.resolve("repo1") @@ -49,22 +57,6 @@ private fun TestProject.assertConsecutiveBuildsProduceSameBinaries() { maven(repo2) { name = "repo2" } } } - - tasks.register("checkBuildConsistency") { - doFirst { - if (!repo1.exists()) { - throw IllegalStateException("Missing repo1") - } - - if (!repo2.exists()) { - throw IllegalStateException("Missing repo2") - } - } - - commandLine( - "diff", repo1.absolutePath, repo2.absolutePath, "-r", "-x", "maven-metadata**" - ) - } """.trimIndent() ) @@ -72,11 +64,72 @@ private fun TestProject.assertConsecutiveBuildsProduceSameBinaries() { build("publishAllPublicationsToRepo2Repository", "--rerun-tasks") { tasks.forEach { task -> assertTrue( - task.outcome in setOf(TaskOutcome.SUCCESS, TaskOutcome.NO_SOURCE), + task.outcome in setOf(TaskOutcome.SUCCESS, TaskOutcome.SKIPPED, TaskOutcome.NO_SOURCE), "Expected all tasks to be re-executed. Task ${task.path} was ${task.outcome}" ) } } - build("checkBuildConsistency", forceOutput = true) + val repo1 = projectPath.resolve("build/repo1").toFile() + val repo2 = projectPath.resolve("build/repo2").toFile() + + val diffs = diff(repo1, repo2).filter { diff -> "maven-metadata" !in diff.fileName } + if (diffs.isNotEmpty()) { + fail( + buildString { + appendLine("${repo1.relativeToProject()} <-> ${repo2.relativeToProject()} are inconsistent!") + diffs.forEach { diff -> + appendLine(diff.reportString()) + } + } + ) + } +} + +private sealed class Diff { + data class MissingFile(val file: File) : Diff() + data class DifferentContent(val first: File, val second: File) : Diff() + data class TypeMismatch(val directory: File, val file: File) : Diff() + + val fileName: String + get() = when (this) { + is DifferentContent -> first.name + is MissingFile -> file.name + is TypeMismatch -> directory.name + } +} + +private fun diff(firstRoot: File, secondRoot: File): Set { + return firstRoot.walkTopDown().flatMap { firstFile -> + val secondFile = secondRoot.resolve(firstFile.relativeTo(firstRoot)) + + if (!secondFile.exists()) { + return@flatMap listOf(Diff.MissingFile(secondFile)) + } + + /* File is directory */ + if (firstFile.isDirectory) { + if (!secondFile.isDirectory) { + return@flatMap listOf(Diff.TypeMismatch(directory = firstFile, file = secondFile)) + } + + /* Check if all children in the second directory are also present in firstFile */ + return@flatMap secondFile.listFiles().orEmpty().mapNotNull { secondFileChild -> + val firstFileChild = firstRoot.resolve(secondFileChild.relativeTo(secondRoot)) + if (!firstFileChild.exists()) Diff.MissingFile(firstFileChild) + else null + } + } + + /* File is not a directory */ + if (firstFile.length() != secondFile.length()) { + return@flatMap listOf(Diff.DifferentContent(firstFile, secondFile)) + } + + if (!firstFile.readBytes().contentEquals(secondFile.readBytes())) { + return@flatMap listOf(Diff.DifferentContent(firstFile, secondFile)) + } + + emptyList() + }.toSet() }