-
-
Notifications
You must be signed in to change notification settings - Fork 31
/
VisitorTest.kt
194 lines (180 loc) · 8.22 KB
/
VisitorTest.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package io.sentry.android.gradle.instrumentation
import com.android.build.api.instrumentation.ClassContext
import io.sentry.android.gradle.instrumentation.androidx.room.AndroidXRoomDao
import io.sentry.android.gradle.instrumentation.androidx.sqlite.database.AndroidXSQLiteDatabase
import io.sentry.android.gradle.instrumentation.androidx.sqlite.statement.AndroidXSQLiteStatement
import io.sentry.android.gradle.instrumentation.classloader.GeneratingMissingClassesClassLoader
import io.sentry.android.gradle.instrumentation.fakes.TestClassContext
import io.sentry.android.gradle.instrumentation.fakes.TestClassData
import io.sentry.android.gradle.instrumentation.fakes.TestSpanAddingParameters
import io.sentry.android.gradle.instrumentation.okhttp.OkHttp
import io.sentry.android.gradle.instrumentation.remap.RemappingInstrumentable
import io.sentry.android.gradle.instrumentation.wrap.WrappingInstrumentable
import java.io.FileInputStream
import java.io.PrintWriter
import java.io.StringWriter
import junit.framework.TestCase.assertEquals
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.util.CheckClassAdapter
@Suppress("UnstableApiUsage")
@RunWith(Parameterized::class)
class VisitorTest(
private val instrumentedProject: String,
private val className: String,
private val instrumentable: Instrumentable<ClassVisitor, ClassContext>,
private val classContext: ClassContext?
) {
@get:Rule
val tmpDir = TemporaryFolder()
@Test
fun `instrumented class passes Java verifier`() {
// first we read the original bytecode and pass it through the ClassWriter, so it computes
// MAXS for us automatically (that's what AGP will do as well)
val inputStream =
FileInputStream(
"src/test/resources/testFixtures/instrumentation/" +
"$instrumentedProject/$className.class"
)
val classReader = ClassReader(inputStream)
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
val classContext = this.classContext ?: TestClassContext(instrumentable.fqName)
val classVisitor = instrumentable.getVisitor(
classContext,
Opcodes.ASM7,
classWriter,
parameters = TestSpanAddingParameters(inMemoryDir = tmpDir.root)
)
// here we visit the bytecode, so it gets modified by our instrumentation visitor
// the ClassReader flags here are identical to those that are set by AGP and R8
classReader.accept(classVisitor, ClassReader.SKIP_FRAMES)
// after that we convert the modified bytecode with computed MAXS back to byte array
// and pass it through CheckClassAdapter to verify that the bytecode is correct and can be accepted by JVM
val bytes = classWriter.toByteArray()
val verifyReader = ClassReader(bytes)
val checkAdapter = CheckClassAdapter(ClassWriter(0), true)
// val methodNamePrintingVisitor = MethodNamePrintingVisitor(Opcodes.ASM7, checkAdapter)
verifyReader.accept(checkAdapter, 0)
// this will verify that the output of the above verifyReader is empty
val stringWriter = StringWriter()
val printWriter = PrintWriter(stringWriter)
CheckClassAdapter.verify(
verifyReader,
GeneratingMissingClassesClassLoader(),
false,
printWriter
)
assertEquals(stringWriter.toString(), "")
}
@After
fun printLogs() {
// only print bytecode when running locally
if (System.getenv("CI")?.toBoolean() != true) {
tmpDir.root.listFiles()
?.filter { it.name.contains("instrumentation") }
?.forEach {
print(it.readText())
}
}
}
companion object {
@Parameterized.Parameters(name = "{0}/{1}")
@JvmStatic
fun parameters() = listOf(
arrayOf("androidxSqlite", "FrameworkSQLiteDatabase", AndroidXSQLiteDatabase(), null),
arrayOf("androidxSqlite", "FrameworkSQLiteStatement", AndroidXSQLiteStatement(), null),
roomDaoTestParameters("DeleteAndReturnUnit"),
roomDaoTestParameters("InsertAndReturnLong"),
roomDaoTestParameters("InsertAndReturnUnit"),
roomDaoTestParameters("UpdateAndReturnUnit"),
roomDaoTestParameters("SelectInTransaction"),
deletionDaoTestParameters("DeleteAndReturnInteger"),
deletionDaoTestParameters("DeleteAndReturnVoid"),
deletionDaoTestParameters("DeleteQuery"),
deletionDaoTestParameters("DeleteQueryAndReturnInteger"),
deletionDaoTestParameters("Impl"),
insertionDaoTestParameters("Impl"),
updateDaoTestParameters("UpdateAndReturnInteger"),
updateDaoTestParameters("UpdateAndReturnVoid"),
updateDaoTestParameters("UpdateQuery"),
updateDaoTestParameters("UpdateQueryAndReturnInteger"),
updateDaoTestParameters("Impl"),
selectDaoTestParameters("FlowSingle"),
selectDaoTestParameters("FlowList"),
selectDaoTestParameters("LiveDataSingle"),
selectDaoTestParameters("LiveDataList"),
selectDaoTestParameters("Paging"),
selectDaoTestParameters("Impl"),
arrayOf("fileIO", "SQLiteCopyOpenHelper", WrappingInstrumentable(), null),
arrayOf("fileIO", "TypefaceCompatUtil", WrappingInstrumentable(), null),
arrayOf(
"fileIO",
"Test",
ChainedInstrumentable(listOf(WrappingInstrumentable(), RemappingInstrumentable())),
null
),
arrayOf(
"fileIO",
"zzhm",
ChainedInstrumentable(listOf(WrappingInstrumentable(), RemappingInstrumentable())),
null
),
arrayOf(
"fileIO",
"BrazeImageUtils",
ChainedInstrumentable(listOf(WrappingInstrumentable(), RemappingInstrumentable())),
null
),
arrayOf("okhttp/v3", "RealCall", OkHttp(), null),
arrayOf("okhttp/v4", "RealCall", OkHttp(), null)
)
private fun roomDaoTestParameters(suffix: String = "") = arrayOf(
"androidxRoom",
"TracksDao_$suffix",
AndroidXRoomDao(),
TestClassContext("TracksDao_$suffix") { lookupName ->
TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName))
}
)
private fun deletionDaoTestParameters(suffix: String = "") = arrayOf(
"androidxRoom/delete",
"DeletionDao_$suffix",
AndroidXRoomDao(),
TestClassContext("DeletionDao_$suffix") { lookupName ->
TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName))
}
)
private fun insertionDaoTestParameters(suffix: String = "") = arrayOf(
"androidxRoom/insert",
"InsertionDao_$suffix",
AndroidXRoomDao(),
TestClassContext("InsertionDao_$suffix") { lookupName ->
TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName))
}
)
private fun updateDaoTestParameters(suffix: String = "") = arrayOf(
"androidxRoom/update",
"UpdateDao_$suffix",
AndroidXRoomDao(),
TestClassContext("UpdateDao_$suffix") { lookupName ->
TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName))
}
)
private fun selectDaoTestParameters(suffix: String = "") = arrayOf(
"androidxRoom/select",
"SelectDao_$suffix",
AndroidXRoomDao(),
TestClassContext("SelectDao_$suffix") { lookupName ->
TestClassData(lookupName, classAnnotations = listOf(AndroidXRoomDao().fqName))
}
)
}
}