diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt index 3d239f543..c9daa0df9 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/RelocatorRemapper.kt @@ -23,18 +23,16 @@ internal fun FileCopyDetails.remapClass(relocators: Set): ByteArray = // original class names. This is not a problem at runtime (because these entries in the constant // pool are never used), but confuses some tools such as Felix's maven-bundle-plugin that use // the constant pool to determine the dependencies of a class. - val cw = ClassWriter(0) - val cr = ClassReader(bytes) - val cv = ClassRemapper(cw, remapper) - try { + val cw = ClassWriter(0) + val cr = ClassReader(bytes) + val cv = ClassRemapper(cw, remapper) cr.accept(cv, ClassReader.EXPAND_FRAMES) + // If we didn't need to change anything, keep the original bytes as-is. + if (modified) cw.toByteArray() else bytes } catch (t: Throwable) { throw GradleException("Error in ASM processing class $path", t) } - - // If we didn't need to change anything, keep the original bytes as-is. - if (modified) cw.toByteArray() else bytes } private class RelocatorRemapper( diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt index f3bc50921..d7840ada6 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/BytecodeRemappingTest.kt @@ -12,8 +12,10 @@ import java.nio.file.Path import kotlin.io.path.copyTo import kotlin.io.path.createParentDirectories import kotlin.reflect.KClass +import org.gradle.api.GradleException import org.gradle.api.file.FileCopyDetails import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource @@ -60,6 +62,22 @@ class BytecodeRemappingTest { assertThat(result).isEqualTo(details.file.readBytes()) } + @Test + fun asmFailureIsWrappedWithClassPath() { + val path = "broken/Example.class" + val file = tempDir.resolve("broken.class").toFile().apply { writeText("not bytecode") } + val details = + object : FileCopyDetails by noOpDelegate() { + override fun getPath(): String = path + + override fun getFile(): File = file + } + + val failure = assertThrows { details.remapClass(relocators) } + + assertThat(failure.message).isEqualTo("Error in ASM processing class $path") + } + @Test fun classNameIsRelocated() { val result = fixtureSubjectDetails.remapClass(relocators) diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeSpecsTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeSpecsTest.kt new file mode 100644 index 000000000..afd087e31 --- /dev/null +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/MinimizeSpecsTest.kt @@ -0,0 +1,80 @@ +package com.github.jengelman.gradle.plugins.shadow.internal + +import assertk.assertThat +import assertk.assertions.containsExactly +import assertk.assertions.isEmpty +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isNull +import assertk.assertions.isSameInstanceAs +import assertk.assertions.isTrue +import com.github.jengelman.gradle.plugins.shadow.tasks.MinimizeTool +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.Test + +class MinimizeSpecsTest { + private val project = ProjectBuilder.builder().build() + + @Test + fun defaultMinimizeSpecUsesDependencyAnalyzer() { + val spec = project.objects.newInstance(DefaultMinimizeSpec::class.java, project) + + assertThat(spec.tool.get()).isEqualTo(MinimizeTool.DEPENDENCY_ANALYZER) + assertThat(spec.r8SpecForInputs).isNull() + } + + @Test + fun r8ConfiguresToolAndExposesSameSpecAsInput() { + val spec = project.objects.newInstance(DefaultMinimizeSpec::class.java, project) + lateinit var configured: Any + + spec.r8 { configured = it } + + assertThat(spec.tool.get()).isEqualTo(MinimizeTool.R8) + assertThat(spec.r8SpecForInputs).isSameInstanceAs(configured) + assertThat(spec.r8Spec).isSameInstanceAs(configured) + } + + @Test + fun defaultR8SpecIsShrinkOnly() { + val spec = project.objects.newInstance(DefaultR8Spec::class.java) + + assertThat(spec.args.get()).containsExactly(DefaultR8Spec.NO_MINIFICATION_ARG) + assertThat(spec.obfuscationEnabled.get()).isFalse() + assertThat(spec.optimizationEnabled.get()).isFalse() + assertThat(spec.keepRules.get()).isEmpty() + assertThat(spec.keepRuleFiles.files).isEmpty() + } + + @Test + fun enablingObfuscationRemovesDefaultArgument() { + val spec = project.objects.newInstance(DefaultR8Spec::class.java) + + spec.enableObfuscation() + + assertThat(spec.args.get()).isEmpty() + assertThat(spec.obfuscationEnabled.get()).isTrue() + assertThat(spec.optimizationEnabled.get()).isFalse() + } + + @Test + fun enablingOptimizationOnlyChangesOptimizationFlag() { + val spec = project.objects.newInstance(DefaultR8Spec::class.java) + + spec.enableOptimization() + + assertThat(spec.args.get()).containsExactly(DefaultR8Spec.NO_MINIFICATION_ARG) + assertThat(spec.obfuscationEnabled.get()).isFalse() + assertThat(spec.optimizationEnabled.get()).isTrue() + } + + @Test + fun explicitArgumentsTakePrecedenceOverChangedDefaults() { + val spec = project.objects.newInstance(DefaultR8Spec::class.java) + spec.args.set(listOf("--debug")) + + spec.enableObfuscation() + + assertThat(spec.args.get()).containsExactly("--debug") + } +} diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ReproduciblePropertiesTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ReproduciblePropertiesTest.kt index 42b6b08eb..ea31df77a 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ReproduciblePropertiesTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ReproduciblePropertiesTest.kt @@ -76,6 +76,25 @@ class ReproduciblePropertiesTest { ) } + @ParameterizedTest + @MethodSource("generalCharsetsProvider") + fun escapesSpecialCharacters(charset: Charset) { + val output = + ReproducibleProperties() + .also { properties -> + properties[" leading:=#!"] = "line1\nline2\t\\" + } + .writeToString(charset) + + assertThat(output) + .isEqualTo( + """ + |\ leading\:\=\#\!=line1\nline2\t\\ + |""" + .trimMargin() + ) + } + private companion object Companion { @JvmStatic fun generalCharsetsProvider() = diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ZipEntryValidationTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ZipEntryValidationTest.kt index 0bdae4cd4..0018595f4 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ZipEntryValidationTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ZipEntryValidationTest.kt @@ -2,12 +2,39 @@ package com.github.jengelman.gradle.plugins.shadow.internal import assertk.assertThat import assertk.assertions.isEqualTo +import assertk.assertions.isTrue +import java.nio.charset.StandardCharsets +import java.util.Properties import org.gradle.api.GradleException import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows class ZipEntryValidationTest { + @Test + fun zipEntryUsesRequestedOrReproducibleTimestampAndAppliesConfiguration() { + val preserved = zipEntry("file", lastModified = 1234) { comment = "configured" } + val reproducible = zipEntry("file", preserveLastModified = false, lastModified = 1234) + val missingTimestamp = zipEntry("file", lastModified = -1) + + assertThat(preserved.time).isEqualTo(1234) + assertThat(preserved.comment).isEqualTo("configured") + assertThat(reproducible.time).isEqualTo(missingTimestamp.time) + } + + @Test + fun propertiesInputStreamUsesRequestedCharsetAndComments() { + val properties = Properties().apply { setProperty("greeting", "你好") } + + val charset = StandardCharsets.UTF_16 + val bytes = properties.inputStream(charset, "header").readBytes() + val content = String(bytes, charset) + + assertThat(content.startsWith("#header")).isTrue() + val loaded = Properties().apply { load(bytes.inputStream().reader(charset)) } + assertThat(loaded.getProperty("greeting")).isEqualTo("你好") + } + @Test fun validZipEntryNamesDoNotThrow() { val validNames =