Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,16 @@ internal fun FileCopyDetails.remapClass(relocators: Set<Relocator>): 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<GradleException> { details.remapClass(relocators) }

assertThat(failure.message).isEqualTo("Error in ASM processing class $path")
}

@Test
fun classNameIsRelocated() {
val result = fixtureSubjectDetails.remapClass(relocators)
Expand Down
Original file line number Diff line number Diff line change
@@ -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")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down