[go: nahoru, domu]

blob: d1c01d06e8f0447205c834e2a8edeef2c07b7984 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.plugins.kotlin
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtilRt
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.testFramework.LightVirtualFile
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.codegen.GeneratedClassLoader
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import java.io.File
abstract class AbstractCodegenTest : AbstractCompilerTest() {
override fun setUp() {
super.setUp()
val classPath = createClasspath() + additionalPaths
val configuration = newConfiguration()
configuration.addJvmClasspathRoots(classPath)
updateConfiguration(configuration)
myEnvironment = KotlinCoreEnvironment.createForTests(
myTestRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
).also { setupEnvironment(it) }
}
open fun updateConfiguration(configuration: CompilerConfiguration) {
configuration.put(JVMConfigurationKeys.IR, true)
configuration.put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
}
protected open fun helperFiles(): List<KtFile> = emptyList()
protected fun dumpClasses(loader: GeneratedClassLoader) {
for (file in loader.allGeneratedFiles.filter {
it.relativePath.endsWith(".class")
}) {
println("------\nFILE: ${file.relativePath}\n------")
println(file.asText())
}
}
protected fun validateBytecode(
src: String,
dumpClasses: Boolean = false,
validate: (String) -> Unit
): Unit = ensureSetup {
val className = "Test_REPLACEME_${uniqueNumber++}"
val fileName = "$className.kt"
val loader = classLoader("""
@file:OptIn(
ExperimentalComposeApi::class,
InternalComposeApi::class,
ComposeCompilerApi::class
)
package test
import androidx.compose.*
$src
""", fileName, dumpClasses)
val apiString = loader
.allGeneratedFiles
.filter { it.relativePath.endsWith(".class") }
.map {
it.asText().replace('$', '%').replace(className, "Test")
}.joinToString("\n")
validate(apiString)
}
protected fun classLoader(
source: String,
fileName: String,
dumpClasses: Boolean = false
): GeneratedClassLoader {
val files = mutableListOf<KtFile>()
files.addAll(helperFiles())
files.add(sourceFile(fileName, source))
myFiles = CodegenTestFiles.create(files)
val loader = createClassLoader()
if (dumpClasses) dumpClasses(loader)
return loader
}
protected fun classLoader(
sources: Map<String, String>,
dumpClasses: Boolean = false
): GeneratedClassLoader {
val files = mutableListOf<KtFile>()
files.addAll(helperFiles())
for ((fileName, source) in sources) {
files.add(sourceFile(fileName, source))
}
myFiles = CodegenTestFiles.create(files)
val loader = createClassLoader()
if (dumpClasses) dumpClasses(loader)
return loader
}
protected fun testFile(source: String, dumpClasses: Boolean = false) {
val files = mutableListOf<KtFile>()
files.addAll(helperFiles())
files.add(sourceFile("Test.kt", source))
myFiles = CodegenTestFiles.create(files)
val loader = createClassLoader()
if (dumpClasses) dumpClasses(loader)
val loadedClass = loader.loadClass("Test")
val instance = loadedClass.newInstance()
val instanceClass = instance::class.java
val testMethod = instanceClass.getMethod("test")
testMethod.invoke(instance)
}
protected fun testCompile(source: String, dumpClasses: Boolean = false) {
val files = mutableListOf<KtFile>()
files.addAll(helperFiles())
files.add(sourceFile("Test.kt", source))
myFiles = CodegenTestFiles.create(files)
val loader = createClassLoader()
if (dumpClasses) dumpClasses(loader)
}
protected val COMPOSE_VIEW_STUBS_IMPORTS = """
import android.view.View
import android.widget.TextView
import android.widget.Button
import android.view.Gravity
import android.widget.LinearLayout
import androidx.compose.Composable
import androidx.ui.viewinterop.emitView
""".trimIndent()
protected val COMPOSE_VIEW_STUBS = """
@Composable
fun TextView(
id: Int = 0,
gravity: Int = Gravity.TOP or Gravity.START,
text: String = "",
onClick: (() -> Unit)? = null,
onClickListener: View.OnClickListener? = null
) {
emitView(::TextView) {
if (id != 0) it.id = id
it.text = text
it.gravity = gravity
if (onClickListener != null) it.setOnClickListener(onClickListener)
if (onClick != null) it.setOnClickListener(View.OnClickListener { onClick() })
}
}
@Composable
fun Button(
id: Int = 0,
text: String = "",
onClick: (() -> Unit)? = null,
onClickListener: View.OnClickListener? = null
) {
emitView(::Button) {
if (id != 0) it.id = id
it.text = text
if (onClickListener != null) it.setOnClickListener(onClickListener)
if (onClick != null) it.setOnClickListener(View.OnClickListener { onClick() })
}
}
@Composable
fun LinearLayout(
id: Int = 0,
orientation: Int = LinearLayout.VERTICAL,
onClickListener: View.OnClickListener? = null,
children: @Composable () -> Unit
) {
emitView(
::LinearLayout,
{
if (id != 0) it.id = id
if (onClickListener != null) it.setOnClickListener(onClickListener)
it.orientation = orientation
},
children
)
}
""".trimIndent()
protected fun testCompileWithViewStubs(source: String, dumpClasses: Boolean = false) =
testCompile(
"""
$COMPOSE_VIEW_STUBS_IMPORTS
$source
$COMPOSE_VIEW_STUBS
""",
dumpClasses
)
protected fun sourceFile(name: String, source: String): KtFile {
val result =
createFile(name, source, myEnvironment!!.project)
val ranges = AnalyzingUtils.getSyntaxErrorRanges(result)
assert(ranges.isEmpty()) { "Syntax errors found in $name: $ranges" }
return result
}
protected fun loadClass(className: String, source: String): Class<*> {
myFiles = CodegenTestFiles.create(
"file.kt",
source,
myEnvironment!!.project
)
val loader = createClassLoader()
return loader.loadClass(className)
}
protected open val additionalPaths = emptyList<File>()
}
fun createFile(name: String, text: String, project: Project): KtFile {
var shortName = name.substring(name.lastIndexOf('/') + 1)
shortName = shortName.substring(shortName.lastIndexOf('\\') + 1)
val virtualFile = object : LightVirtualFile(
shortName,
KotlinLanguage.INSTANCE,
StringUtilRt.convertLineSeparators(text)
) {
override fun getPath(): String = "/$name"
}
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET)
val factory = PsiFileFactory.getInstance(project) as PsiFileFactoryImpl
return factory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile
}