[Gradle][MPP] MPPBuildReproducibilityIT: Use custom diff implementation

^KT-52740 Verification Pending
This commit is contained in:
sebastian.sellmair
2022-06-24 11:45:01 +02:00
committed by Space
parent 1e118c06af
commit c93b1d2aab
@@ -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<Exec>("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<Diff> {
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()
}