Skip to content

Commit cb69f8e

Browse files
committed
Add tests for remapClass
1 parent 02756fb commit cb69f8e

File tree

1 file changed

+207
-0
lines changed

1 file changed

+207
-0
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package com.github.jengelman.gradle.plugins.shadow.internal
2+
3+
import assertk.assertThat
4+
import assertk.assertions.contains
5+
import assertk.assertions.isEqualTo
6+
import assertk.assertions.isTrue
7+
import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator
8+
import com.github.jengelman.gradle.plugins.shadow.testkit.requireResourceAsPath
9+
import com.github.jengelman.gradle.plugins.shadow.util.noOpDelegate
10+
import java.io.File
11+
import java.lang.classfile.Attributes
12+
import java.lang.classfile.ClassFile
13+
import java.nio.file.Path
14+
import kotlin.io.path.copyTo
15+
import kotlin.io.path.createParentDirectories
16+
import kotlin.reflect.KClass
17+
import org.gradle.api.file.FileCopyDetails
18+
import org.junit.jupiter.api.Test
19+
import org.junit.jupiter.api.io.TempDir
20+
21+
/**
22+
* The cases reflect the cases in
23+
* [com.github.jengelman.gradle.plugins.shadow.relocation.RelocatorsTest], but operate on the
24+
* bytecode level to verify that the remapper correctly transforms class names in all relevant
25+
* bytecode structures.
26+
*/
27+
class RelocatorRemapperTest {
28+
@TempDir lateinit var tempDir: Path
29+
30+
// Relocator used across all relocation tests: moves the test package to a distinct target.
31+
private val relocators =
32+
setOf(
33+
SimpleRelocator(
34+
"com.github.jengelman.gradle.plugins.shadow.internal",
35+
"com.example.relocated",
36+
)
37+
)
38+
39+
// Internal name of the relocated FixtureBase for use in assertions.
40+
private val relocatedFixtureBase = $$"com/example/relocated/RelocatorRemapperTest$FixtureBase"
41+
42+
@Test
43+
fun remapClassNotModified() {
44+
val details = FixtureSubject::class.toFileCopyDetails()
45+
// Relocator pattern does not match – original bytes must be returned as-is.
46+
val noMatchRelocators = setOf(SimpleRelocator("org.unrelated", "org.other"))
47+
48+
val result = details.remapClass(noMatchRelocators)
49+
50+
assertThat(result).isEqualTo(details.file.readBytes())
51+
}
52+
53+
@Test
54+
fun remapClassNameIsRelocated() {
55+
val details = FixtureSubject::class.toFileCopyDetails()
56+
57+
val result = details.remapClass(relocators)
58+
59+
val classModel = ClassFile.of().parse(result)
60+
assertThat(classModel.thisClass().asInternalName())
61+
.isEqualTo($$"com/example/relocated/RelocatorRemapperTest$FixtureSubject")
62+
}
63+
64+
@Test
65+
fun remapSuperclassIsRelocated() {
66+
val details = FixtureSubject::class.toFileCopyDetails()
67+
68+
val result = details.remapClass(relocators)
69+
70+
val classModel = ClassFile.of().parse(result)
71+
assertThat(classModel.superclass().get().asInternalName()).isEqualTo(relocatedFixtureBase)
72+
}
73+
74+
@Test
75+
fun remapFieldDescriptorIsRelocated() {
76+
val details = FixtureSubject::class.toFileCopyDetails()
77+
78+
val result = details.remapClass(relocators)
79+
80+
val classModel = ClassFile.of().parse(result)
81+
val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() }
82+
assertThat(fieldDescriptors).contains("L$relocatedFixtureBase;")
83+
}
84+
85+
@Test
86+
fun remapMethodDescriptorIsRelocated() {
87+
val details = FixtureSubject::class.toFileCopyDetails()
88+
89+
val result = details.remapClass(relocators)
90+
91+
val classModel = ClassFile.of().parse(result)
92+
val methodDescriptors = classModel.methods().map { it.methodType().stringValue() }
93+
assertThat(methodDescriptors).contains("(L$relocatedFixtureBase;)L$relocatedFixtureBase;")
94+
}
95+
96+
@Test
97+
fun remapAnnotationIsRelocated() {
98+
val details = FixtureSubject::class.toFileCopyDetails()
99+
100+
val result = details.remapClass(relocators)
101+
102+
val classModel = ClassFile.of().parse(result)
103+
val annotationsAttr = classModel.findAttribute(Attributes.runtimeVisibleAnnotations())
104+
assertThat(annotationsAttr.isPresent).isTrue()
105+
val annotationDescriptors =
106+
annotationsAttr.get().annotations().map { it.className().stringValue() }
107+
assertThat(annotationDescriptors)
108+
.contains($$"Lcom/example/relocated/RelocatorRemapperTest$FixtureAnnotation;")
109+
}
110+
111+
@Test
112+
fun remapArrayFieldDescriptorIsRelocated() {
113+
val details = FixtureSubject::class.toFileCopyDetails()
114+
115+
val result = details.remapClass(relocators)
116+
117+
val classModel = ClassFile.of().parse(result)
118+
val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() }
119+
assertThat(fieldDescriptors).contains("[L$relocatedFixtureBase;")
120+
}
121+
122+
@Test
123+
fun remapArray2dFieldDescriptorIsRelocated() {
124+
val details = FixtureSubject::class.toFileCopyDetails()
125+
126+
val result = details.remapClass(relocators)
127+
128+
val classModel = ClassFile.of().parse(result)
129+
val fieldDescriptors = classModel.fields().map { it.fieldType().stringValue() }
130+
assertThat(fieldDescriptors).contains("[[L$relocatedFixtureBase;")
131+
}
132+
133+
@Test
134+
fun remapMethodMultipleArgsIsRelocated() {
135+
val details = FixtureSubject::class.toFileCopyDetails()
136+
137+
val result = details.remapClass(relocators)
138+
139+
val classModel = ClassFile.of().parse(result)
140+
val methodDescriptors = classModel.methods().map { it.methodType().stringValue() }
141+
assertThat(methodDescriptors)
142+
.contains("(L$relocatedFixtureBase;L$relocatedFixtureBase;)L$relocatedFixtureBase;")
143+
}
144+
145+
@Test
146+
fun remapMethodPrimitivePlusClassIsRelocated() {
147+
val details = FixtureSubject::class.toFileCopyDetails()
148+
149+
val result = details.remapClass(relocators)
150+
151+
val classModel = ClassFile.of().parse(result)
152+
val methodDescriptors = classModel.methods().map { it.methodType().stringValue() }
153+
assertThat(methodDescriptors).contains("(BL$relocatedFixtureBase;)L$relocatedFixtureBase;")
154+
}
155+
156+
@Test
157+
fun remapBaseClassNameIsRelocated() {
158+
// Verify relocation also works on a simple class (FixtureBase has no fields/methods
159+
// referencing the target package beyond its own class name).
160+
val details = FixtureBase::class.toFileCopyDetails()
161+
162+
val result = details.remapClass(relocators)
163+
164+
val classModel = ClassFile.of().parse(result)
165+
assertThat(classModel.thisClass().asInternalName()).isEqualTo(relocatedFixtureBase)
166+
}
167+
168+
private fun KClass<*>.toFileCopyDetails() =
169+
object : FileCopyDetails by noOpDelegate() {
170+
private val _path = java.name.replace('.', '/') + ".class"
171+
private val _file =
172+
tempDir
173+
.resolve(_path)
174+
.createParentDirectories()
175+
.also { requireResourceAsPath(_path).copyTo(it) }
176+
.toFile()
177+
178+
override fun getPath(): String = _path
179+
180+
override fun getFile(): File = _file
181+
}
182+
183+
// ---------------------------------------------------------------------------
184+
// Fixture classes – declared as nested classes so their bytecode is compiled
185+
// into the test output directory and can be fetched via requireResourceAsPath.
186+
// ---------------------------------------------------------------------------
187+
188+
@Retention(AnnotationRetention.RUNTIME)
189+
@Target(AnnotationTarget.CLASS)
190+
annotation class FixtureAnnotation
191+
192+
open class FixtureBase
193+
194+
@Suppress("unused") // Used by parsing bytecode.
195+
@FixtureAnnotation
196+
class FixtureSubject : FixtureBase() {
197+
val field: FixtureBase = FixtureBase()
198+
val arrayField: Array<FixtureBase> = emptyArray()
199+
val array2dField: Array<Array<FixtureBase>> = emptyArray()
200+
201+
fun method(arg: FixtureBase): FixtureBase = arg
202+
203+
fun methodMultiArgs(a: FixtureBase, b: FixtureBase): FixtureBase = a
204+
205+
fun methodWithPrimitivePlusClass(b: Byte, arg: FixtureBase): FixtureBase = arg
206+
}
207+
}

0 commit comments

Comments
 (0)