[Gradle][MPP] Refine zipUtils.kt & implement ZipUtilsTest.kt

^KT-46198
This commit is contained in:
sebastian.sellmair
2021-10-22 11:39:57 +02:00
committed by Space
parent dea434b6d5
commit e73273b139
4 changed files with 227 additions and 65 deletions
@@ -7,7 +7,6 @@
package org.jetbrains.kotlin.gradle
import org.gradle.kotlin.dsl.support.unzipTo
import org.gradle.kotlin.dsl.support.zipTo
import org.jetbrains.kotlin.gradle.plugin.mpp.CompositeMetadataJar
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinProjectStructureMetadata
@@ -120,6 +119,7 @@ class CompositeMetadataJarTest {
"Expected correct archiveExtension for extracted sourceSetA"
)
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetA"), sourceSetAMetadataFile,
"Expected correct content of extracted 'sourceSetA'"
)
@@ -132,6 +132,7 @@ class CompositeMetadataJarTest {
"Expected correct archiveExtension for extracted sourceSetB"
)
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetB"), sourceSetBMetadataFile,
"Expected correct content of extracted 'sourceSetA'"
)
@@ -180,6 +181,7 @@ class CompositeMetadataJarTest {
val interopA0MetadataFile = sourceSetAInteropMetadataFiles.firstOrNull { it.name == "interopA0.klib" }
?: fail("Failed to find 'interopA0.klib'")
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetA-cinterop/interopA0"), interopA0MetadataFile,
"Expected correct content for extracted 'interopA0'"
)
@@ -190,6 +192,7 @@ class CompositeMetadataJarTest {
val interopA1MetadataFile = sourceSetAInteropMetadataFiles.firstOrNull { it.name == "interopA1.klib" }
?: fail("Failed to find 'interopA1.klib")
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetA-cinterop/interopA1"), interopA1MetadataFile,
"Expected correct content for extracted 'interopA1'"
)
@@ -205,44 +208,12 @@ class CompositeMetadataJarTest {
val interopB0MetadataFile = sourceSetBInteropMetadataFiles.firstOrNull { it.name == "interopB0.klib" }
?: fail("Failed to find 'interopB0.klib'")
assertZipContentEquals(
temporaryFolder,
primaryArtifactContent.resolve("sourceSetB-cinterop/interopB0"), interopB0MetadataFile,
"Expected correct content for extracted 'interopB0'"
)
}
}
private fun assertZipContentEquals(expectedContent: File, zipFile: File, message: String) {
val zipOutputDirectory = temporaryFolder.newFolder()
unzipTo(zipOutputDirectory, zipFile)
assertContentEquals(expectedContent, zipOutputDirectory, message)
}
private fun assertContentEquals(expected: File, actual: File, message: String) {
assertTrue(expected.isDirectory, "Expected $expected to be directory")
assertTrue(actual.isDirectory, "Expected $actual to be directory")
val expectedFiles = expected.listFiles().orEmpty()
val actualFiles = actual.listFiles().orEmpty()
val expectedFileNames = expectedFiles.map { it.name }.sorted().toSet()
val actualFileNames = actualFiles.map { it.name }.sorted().toSet()
assertEquals(expectedFileNames, actualFileNames, "$message: ${expected.name} does not contain the same files")
expectedFiles.forEach { expectedFile ->
val actualFile = actualFiles.single { it.name == expectedFile.name }
if (expectedFile.isFile && actualFile.isFile) {
assertTrue(
expectedFile.readBytes().contentEquals(actualFile.readBytes()),
"$message: ${expectedFile.name} does not match in ${expected.name}"
)
} else if (expectedFile.isDirectory && actualFile.isDirectory) {
assertContentEquals(expectedFile, actualFile, message)
} else {
fail("Expected $expectedFile to be 'file' or 'directory'")
}
}
}
}
private fun createProjectStructureMetadata(
@@ -0,0 +1,208 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle
import org.gradle.kotlin.dsl.support.unzipTo
import org.gradle.kotlin.dsl.support.zipTo
import org.jetbrains.kotlin.gradle.utils.copyZipFilePartially
import org.jetbrains.kotlin.gradle.utils.listDescendants
import org.junit.Rule
import org.junit.Test
import org.junit.jupiter.api.assertThrows
import org.junit.rules.TemporaryFolder
import java.io.File
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
class ZipUtilsTest {
@get:Rule
val temporaryFolder = TemporaryFolder()
private val zipContentFolder by lazy {
temporaryFolder.newFolder().apply {
resolve("stub0.txt").apply {
parentFile.mkdirs()
writeText("stub0 content")
}
resolve("a/stub1.txt").apply {
parentFile.mkdirs()
writeText("stub1 content")
}
resolve("a/b/stub2.txt").apply {
parentFile.mkdirs()
writeText("stub2 content")
}
resolve("a/b/stub3.txt").apply {
parentFile.mkdirs()
writeText("stub3 content")
}
resolve("c/stub4.txt").apply {
parentFile.mkdirs()
writeText("stub4 content")
}
}
}
private val zipFile by lazy {
temporaryFolder.newFile().apply {
zipTo(this, zipContentFolder)
}
}
@Test
fun `test - listDescendants - malformed path string`() {
ZipFile(zipFile).use { zip ->
assertThrows<IllegalArgumentException> { zip.listDescendants("a") }
assertThrows<IllegalArgumentException> { zip.listDescendants("a/b") }
assertThrows<IllegalArgumentException> { zip.listDescendants("c") }
}
}
@Test
fun `test listDescendants`() {
ZipFile(zipFile).use { zip ->
assertEquals(
setOf("stub0.txt", "a/stub1.txt", "a/b/stub2.txt", "a/b/stub3.txt", "c/stub4.txt").sorted().toSet(),
zip.listDescendants("").filter { it.isDirectory.not() }.map { it.name }.sorted().toSet(),
"Expected all descendants being listed"
)
assertEquals(
setOf("a/stub1.txt", "a/b/stub2.txt", "a/b/stub3.txt").sorted().toSet(),
zip.listDescendants("a/").filter { it.isDirectory.not() }.map { it.name }.sorted().toSet(),
"Expected all children of 'a/' being listed"
)
assertEquals(
setOf("a/b/stub2.txt", "a/b/stub3.txt").sorted().toSet(),
zip.listDescendants("a/b/").filter { it.isDirectory.not() }.map { it.name }.sorted().toSet(),
"Expected all descendants of 'a/b/' being listed"
)
assertEquals(
setOf("c/stub4.txt"),
zip.listDescendants("c/").filter { it.isDirectory.not() }.map { it.name }.sorted().toSet(),
"Expected all descendants of 'c/' being listed"
)
}
}
@Test
fun `test copyZipFilePartially - root path`() {
val copiedFile = temporaryFolder.newFile()
copyZipFilePartially(zipFile, copiedFile, path = "")
assertZipContentEquals(
temporaryFolder, zipContentFolder, copiedFile,
"Expected correct content for copied file with root path"
)
}
@Test
fun `test copyZipFilePartially - a`() {
val copiedFile = temporaryFolder.newFile()
copyZipFilePartially(zipFile, copiedFile, path = "a/")
assertZipContentEquals(
temporaryFolder, zipContentFolder.resolve("a"), copiedFile,
"Expected correct content for copied file with path 'a/'"
)
}
@Test
fun `test copyZipFilePartially - a b`() {
val copiedFile = temporaryFolder.newFile()
copyZipFilePartially(zipFile, copiedFile, path = "a/b/")
assertZipContentEquals(
temporaryFolder, zipContentFolder.resolve("a/b"), copiedFile,
"Expected correct content for copied file with path 'a/b/'"
)
}
@Test
fun `test copyZipFilePartially - c`() {
val copiedFile = temporaryFolder.newFile()
copyZipFilePartially(zipFile, copiedFile, path = "c/")
assertZipContentEquals(
temporaryFolder, zipContentFolder.resolve("c"), copiedFile,
"Expected correct content for copied file with path 'c/'"
)
}
@Test
fun `test copyZipFilePartially - retains folder entries`() {
val sourceZipFile = temporaryFolder.newFile()
ZipOutputStream(sourceZipFile.outputStream()).use { zipOutputStream ->
zipOutputStream.putNextEntry(ZipEntry("a/"))
zipOutputStream.closeEntry()
zipOutputStream.putNextEntry(ZipEntry("a/b/"))
zipOutputStream.closeEntry()
zipOutputStream.putNextEntry(ZipEntry("a/b/c/"))
zipOutputStream.closeEntry()
zipOutputStream.putNextEntry(ZipEntry("a/b/c/stub.txt"))
zipOutputStream.write("Text".toByteArray())
zipOutputStream.closeEntry()
}
val destinationZipFile = temporaryFolder.newFile()
copyZipFilePartially(sourceZipFile, destinationZipFile, "a/")
ZipFile(destinationZipFile).use { zip ->
assertEquals(
setOf("b/", "b/c/", "b/c/stub.txt"),
zip.entries().toList().map { it.name }.sorted().toSet()
)
}
}
}
fun assertZipContentEquals(
temporaryFolder: TemporaryFolder,
expectedContent: File, zipFile: File, message: String
) {
val zipOutputDirectory = temporaryFolder.newFolder()
unzipTo(zipOutputDirectory, zipFile)
assertDirectoryContentEquals(expectedContent, zipOutputDirectory, message)
}
fun assertDirectoryContentEquals(expected: File, actual: File, message: String) {
assertTrue(expected.isDirectory, "Expected $expected to be directory")
assertTrue(actual.isDirectory, "Expected $actual to be directory")
val expectedFiles = expected.listFiles().orEmpty()
val actualFiles = actual.listFiles().orEmpty()
val expectedFileNames = expectedFiles.map { it.name }.sorted().toSet()
val actualFileNames = actualFiles.map { it.name }.sorted().toSet()
assertEquals(expectedFileNames, actualFileNames, "$message: ${expected.name} does not contain the same files")
expectedFiles.forEach { expectedFile ->
val actualFile = actualFiles.single { it.name == expectedFile.name }
if (expectedFile.isFile && actualFile.isFile) {
assertTrue(
expectedFile.readBytes().contentEquals(actualFile.readBytes()),
"$message: ${expectedFile.name} does not match in ${expected.name}"
)
} else if (expectedFile.isDirectory && actualFile.isDirectory) {
assertDirectoryContentEquals(expectedFile, actualFile, message)
} else {
fail("Expected $expectedFile to be 'file' or 'directory'")
}
}
}
@@ -6,7 +6,7 @@
package org.jetbrains.kotlin.gradle.plugin.mpp
import org.jetbrains.kotlin.gradle.utils.copyZipFilePartially
import org.jetbrains.kotlin.gradle.utils.listChildren
import org.jetbrains.kotlin.gradle.utils.listDescendants
import java.io.File
import java.util.zip.ZipFile
@@ -73,7 +73,7 @@ private class CompositeMetadataJarImpl(
ZipFile(getArtifactFile(sourceSetName)).use { artifactZipFile ->
val cinteropRootPath = "$sourceSetName-cinterop/"
val cinteropEntries = artifactZipFile.listChildren(cinteropRootPath)
val cinteropEntries = artifactZipFile.listDescendants(cinteropRootPath)
val cinteropNames = cinteropEntries.map { entry ->
entry.name.removePrefix(cinteropRootPath).split("/", limit = 2).first()
}.toSet()
@@ -14,22 +14,21 @@ internal fun copyZipFilePartially(sourceZipFile: File, destinationZipFile: File,
requireValidZipPath(path)
ZipFile(sourceZipFile).use { zip ->
val entries = zip.entries().asSequence()
.filter { it.name.startsWith(path) }
.filter { !it.isDirectory }.toList()
val entries = zip.listDescendants(path).toList()
if (entries.isEmpty()) return
ZipOutputStream(destinationZipFile.outputStream()).use { destinationZipOutputStream ->
entries.forEach { entry ->
// Drop the source set name from the entry path
val destinationEntry = ZipEntry(entry.name.substringAfter(path))
entries.forEach { sourceEntry ->
val destinationEntry = ZipEntry(sourceEntry.name.substringAfter(path))
destinationZipOutputStream.putNextEntry(destinationEntry)
zip.getInputStream(entry).use { inputStream ->
destinationZipOutputStream.putNextEntry(destinationEntry)
inputStream.copyTo(destinationZipOutputStream)
destinationZipOutputStream.closeEntry()
if (!sourceEntry.isDirectory) {
zip.getInputStream(sourceEntry).use { inputStream ->
inputStream.copyTo(destinationZipOutputStream)
}
}
destinationZipOutputStream.closeEntry()
}
}
}
@@ -42,22 +41,6 @@ internal fun ZipFile.listDescendants(path: String): Sequence<ZipEntry> {
}
}
internal fun ZipFile.listChildren(path: String): Sequence<ZipEntry> {
requireValidZipPath(path)
return listDescendants(path).filter { entry ->
entry.name.removePrefix(path).count { it == '/' } == 1
}
private fun requireValidZipPath(path: String) = require(path.isEmpty() || path.endsWith("/")) {
"Expected path to end with '/', found '$path'"
}
internal fun ZipFile.listDescendants(zipEntry: ZipEntry): Sequence<ZipEntry> {
require(zipEntry.isDirectory)
return listDescendants(zipEntry.name)
}
internal fun ZipFile.listChildren(zipEntry: ZipEntry): Sequence<ZipEntry> {
return listDescendants(zipEntry).filter { entry ->
entry.name.removePrefix(zipEntry.name).count { it == '/' } == 1
}
}
private fun requireValidZipPath(path: String) = require(path.endsWith("/")) { "Expected path to end with '/', found '$path'" }