Convert CodegenTestsOnAndroidGenerator to Kotlin

This commit is contained in:
Mikhael Bogdanov
2018-01-17 11:25:56 +01:00
parent 7e6542b8c8
commit ef1a3ec32d
@@ -14,306 +14,308 @@
* limitations under the License. * limitations under the License.
*/ */
package org.jetbrains.kotlin.android.tests; package org.jetbrains.kotlin.android.tests
import com.google.common.collect.Lists; import com.google.common.collect.Lists
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.backend.common.output.OutputFileCollection
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection; import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAllTo
import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.codegen.CodegenTestFiles
import org.jetbrains.kotlin.codegen.CodegenTestFiles; import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.GenerationUtils; import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CommonConfigurationKeys; import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.config.JVMConfigurationKeys; import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.idea.KotlinFileType; import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.test.*
import org.jetbrains.kotlin.name.NameUtils; import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.kotlin.test.*; import org.junit.Assert
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase; import org.junit.Ignore
import org.jetbrains.kotlin.utils.Printer; import java.io.File
import org.junit.Assert; import java.io.IOException
import org.junit.Ignore; import java.util.*
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Ignore @Ignore
public class CodegenTestsOnAndroidGenerator extends KtUsefulTestCase { class CodegenTestsOnAndroidGenerator private constructor(private val pathManager: PathManager) : KtUsefulTestCase() {
private var WRITED_FILES_COUNT = 0
private final PathManager pathManager; private var MODULE_INDEX = 1
private static final String testClassPackage = "org.jetbrains.kotlin.android.tests";
private static final String testClassName = "CodegenTestCaseOnAndroid";
private static final String baseTestClassPackage = "org.jetbrains.kotlin.android.tests";
private static final String baseTestClassName = "AbstractCodegenTestCaseOnAndroid";
private static final String generatorName = "CodegenTestsOnAndroidGenerator";
private static int MODULE_INDEX = 1; private val generatedTestNames = Lists.newArrayList<String>()
private int WRITED_FILES_COUNT = 0;
private final List<String> generatedTestNames = Lists.newArrayList(); @Throws(Throwable::class)
private fun generateOutputFiles() {
public static void generate(PathManager pathManager) throws Throwable { prepareAndroidModule()
new CodegenTestsOnAndroidGenerator(pathManager).generateOutputFiles(); generateAndSave()
} }
private CodegenTestsOnAndroidGenerator(PathManager pathManager) { @Throws(IOException::class)
this.pathManager = pathManager; private fun prepareAndroidModule() {
} println("Copying kotlin-runtime.jar and kotlin-reflect.jar in android module...")
copyKotlinRuntimeJars()
private void generateOutputFiles() throws Throwable { println("Check \"libs\" folder in tested android module...")
prepareAndroidModule(); val libsFolderInTestedModule = File(pathManager.libsFolderInAndroidTestedModuleTmpFolder)
generateAndSave();
}
private void prepareAndroidModule() throws IOException {
System.out.println("Copying kotlin-runtime.jar and kotlin-reflect.jar in android module...");
copyKotlinRuntimeJars();
System.out.println("Check \"libs\" folder in tested android module...");
File libsFolderInTestedModule = new File(pathManager.getLibsFolderInAndroidTestedModuleTmpFolder());
if (!libsFolderInTestedModule.exists()) { if (!libsFolderInTestedModule.exists()) {
libsFolderInTestedModule.mkdirs(); libsFolderInTestedModule.mkdirs()
} }
} }
private void copyKotlinRuntimeJars() throws IOException { @Throws(IOException::class)
private fun copyKotlinRuntimeJars() {
FileUtil.copy( FileUtil.copy(
ForTestCompileRuntime.runtimeJarForTests(), ForTestCompileRuntime.runtimeJarForTests(),
new File(pathManager.getLibsFolderInAndroidTmpFolder() + "/kotlin-runtime.jar") File(pathManager.libsFolderInAndroidTmpFolder + "/kotlin-runtime.jar")
); )
FileUtil.copy( FileUtil.copy(
ForTestCompileRuntime.reflectJarForTests(), ForTestCompileRuntime.reflectJarForTests(),
new File(pathManager.getLibsFolderInAndroidTmpFolder() + "/kotlin-reflect.jar") File(pathManager.libsFolderInAndroidTmpFolder + "/kotlin-reflect.jar")
); )
FileUtil.copy( FileUtil.copy(
ForTestCompileRuntime.kotlinTestJarForTests(), ForTestCompileRuntime.kotlinTestJarForTests(),
new File(pathManager.getLibsFolderInAndroidTmpFolder() + "/kotlin-test.jar") File(pathManager.libsFolderInAndroidTmpFolder + "/kotlin-test.jar")
); )
} }
private void generateAndSave() throws Throwable { @Throws(Throwable::class)
System.out.println("Generating test files..."); private fun generateAndSave() {
StringBuilder out = new StringBuilder(); println("Generating test files...")
Printer p = new Printer(out); val out = StringBuilder()
val p = Printer(out)
p.print(FileUtil.loadFile(new File("license/LICENSE.txt"))); p.print(FileUtil.loadFile(File("license/LICENSE.txt")))
p.println("package " + testClassPackage + ";"); p.println("package $testClassPackage;")
p.println(); p.println()
p.println("import ", baseTestClassPackage, ".", baseTestClassName, ";"); p.println("import ", baseTestClassPackage, ".", baseTestClassName, ";")
p.println(); p.println()
p.println("/* This class is generated by " + generatorName + ". DO NOT MODIFY MANUALLY */"); p.println("/* This class is generated by $generatorName. DO NOT MODIFY MANUALLY */")
p.println("public class ", testClassName, " extends ", baseTestClassName, " {"); p.println("public class ", testClassName, " extends ", baseTestClassName, " {")
p.pushIndent(); p.pushIndent()
generateTestMethodsForDirectories(p, new File("compiler/testData/codegen/box"), new File("compiler/testData/codegen/boxInline")); generateTestMethodsForDirectories(p, File("compiler/testData/codegen/box"), File("compiler/testData/codegen/boxInline"))
p.popIndent(); p.popIndent()
p.println("}"); p.println("}")
String testSourceFilePath = val testSourceFilePath =
pathManager.getSrcFolderInAndroidTmpFolder() + "/" + testClassPackage.replace(".", "/") + "/" + testClassName + ".java"; pathManager.srcFolderInAndroidTmpFolder + "/" + testClassPackage.replace(".", "/") + "/" + testClassName + ".java"
FileUtil.writeToFile(new File(testSourceFilePath), out.toString()); FileUtil.writeToFile(File(testSourceFilePath), out.toString())
} }
private void generateTestMethodsForDirectories(Printer p, File... dirs) throws IOException { @Throws(IOException::class)
FilesWriter holderMock = new FilesWriter(false, false); private fun generateTestMethodsForDirectories(p: Printer, vararg dirs: File) {
FilesWriter holderFull = new FilesWriter(true, false); val holderMock = FilesWriter(false, false)
FilesWriter holderInheritMFP = new FilesWriter(true, true); val holderFull = FilesWriter(true, false)
val holderInheritMFP = FilesWriter(true, true)
for (File dir : dirs) { for (dir in dirs) {
File[] files = dir.listFiles(); val files = dir.listFiles()
Assert.assertNotNull("Folder with testData is empty: " + dir.getAbsolutePath(), files); Assert.assertNotNull("Folder with testData is empty: " + dir.absolutePath, files)
processFiles(p, files, holderFull, holderMock, holderInheritMFP); processFiles(p, files!!, holderFull, holderMock, holderInheritMFP)
} }
holderFull.writeFilesOnDisk(); holderFull.writeFilesOnDisk()
holderMock.writeFilesOnDisk(); holderMock.writeFilesOnDisk()
holderInheritMFP.writeFilesOnDisk(); holderInheritMFP.writeFilesOnDisk()
} }
class FilesWriter { internal inner class FilesWriter constructor(
private final boolean isFullJdkAndRuntime; private val isFullJdkAndRuntime: Boolean,
private final boolean inheritMultifileParts; private val inheritMultifileParts: Boolean
) {
public List<KtFile> files = new ArrayList<>(); var files: MutableList<KtFile> = ArrayList()
private KotlinCoreEnvironment environment; private var environment: KotlinCoreEnvironment? = null
private Disposable disposable; private var disposable: Disposable? = null
private FilesWriter(boolean isFullJdkAndRuntime, boolean inheritMultifileParts) { init {
this.isFullJdkAndRuntime = isFullJdkAndRuntime; this.disposable = TestDisposable()
this.inheritMultifileParts = inheritMultifileParts; this.environment = createEnvironment(isFullJdkAndRuntime, disposable!!)
this.disposable = new TestDisposable();
this.environment = createEnvironment(isFullJdkAndRuntime, disposable);
} }
private KotlinCoreEnvironment createEnvironment(boolean isFullJdkAndRuntime, @NotNull Disposable disposable) { private fun createEnvironment(isFullJdkAndRuntime: Boolean, disposable: Disposable): KotlinCoreEnvironment {
ConfigurationKind configurationKind = isFullJdkAndRuntime ? ConfigurationKind.ALL : ConfigurationKind.NO_KOTLIN_REFLECT; val configurationKind = if (isFullJdkAndRuntime) ConfigurationKind.ALL else ConfigurationKind.NO_KOTLIN_REFLECT
TestJdkKind testJdkKind = isFullJdkAndRuntime ? TestJdkKind.FULL_JDK : TestJdkKind.MOCK_JDK; val testJdkKind = if (isFullJdkAndRuntime) TestJdkKind.FULL_JDK else TestJdkKind.MOCK_JDK
CompilerConfiguration configuration = val configuration = KotlinTestUtils.newConfiguration(configurationKind, testJdkKind, KotlinTestUtils.getAnnotationsJar())
KotlinTestUtils.newConfiguration(configurationKind, testJdkKind, KotlinTestUtils.getAnnotationsJar()); configuration.put(CommonConfigurationKeys.MODULE_NAME, "android-module-" + MODULE_INDEX++)
configuration.put(CommonConfigurationKeys.MODULE_NAME, "android-module-" + MODULE_INDEX++);
if (inheritMultifileParts) { if (inheritMultifileParts) {
configuration.put(JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS, true); configuration.put(JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS, true)
} }
return KotlinCoreEnvironment.createForTests(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES); return KotlinCoreEnvironment.createForTests(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
} }
public boolean shouldWriteFilesOnDisk() { fun shouldWriteFilesOnDisk(): Boolean {
return files.size() > 300; return files.size > 300
} }
public void writeFilesOnDiskIfNeeded() { fun writeFilesOnDiskIfNeeded() {
if (shouldWriteFilesOnDisk()) { if (shouldWriteFilesOnDisk()) {
writeFilesOnDisk(); writeFilesOnDisk()
} }
} }
public void writeFilesOnDisk() { fun writeFilesOnDisk() {
writeFiles(files); writeFiles(files)
files = new ArrayList<>(); files = ArrayList()
if (disposable != null) { if (disposable != null) {
Disposer.dispose(disposable); Disposer.dispose(disposable!!)
disposable = new TestDisposable(); disposable = TestDisposable()
} }
environment = createEnvironment(isFullJdkAndRuntime, disposable); environment = createEnvironment(isFullJdkAndRuntime, disposable!!)
} }
public void addFile(String name, String content) { fun addFile(name: String, content: String) {
try { try {
files.add(CodegenTestFiles.create(name, content, environment.getProject()).getPsiFile()); files.add(CodegenTestFiles.create(name, content, environment!!.project).psiFile)
} } catch (e: Throwable) {
catch (Throwable e) { throw RuntimeException("Problem during creating file $name: \n$content", e)
throw new RuntimeException("Problem during creating file " + name + ": \n" + content, e);
} }
} }
private void writeFiles(List<KtFile> filesToCompile) { private fun writeFiles(filesToCompile: List<KtFile>) {
if (filesToCompile.isEmpty()) return; if (filesToCompile.isEmpty()) return
//1000 files per folder, each folder would be jared by build.gradle script //1000 files per folder, each folder would be jared by build.gradle script
// We can't create one big jar with all test cause dex has problem with memory on teamcity // We can't create one big jar with all test cause dex has problem with memory on teamcity
WRITED_FILES_COUNT += filesToCompile.size(); WRITED_FILES_COUNT += filesToCompile.size
File outputDir = new File(pathManager.getOutputForCompiledFiles(WRITED_FILES_COUNT / 1000)); val outputDir = File(pathManager.getOutputForCompiledFiles(WRITED_FILES_COUNT / 1000))
System.out.println("Generating " + filesToCompile.size() + " files" + println(
(inheritMultifileParts "Generating " + filesToCompile.size + " files" +
? " (JVM.INHERIT_MULTIFILE_PARTS)" (if (inheritMultifileParts)
: isFullJdkAndRuntime ? " (full jdk and runtime)" : "") + " into " + outputDir.getName() + "..."); " (JVM.INHERIT_MULTIFILE_PARTS)"
OutputFileCollection outputFiles; else if (isFullJdkAndRuntime) " (full jdk and runtime)" else "") + " into " + outputDir.name + "..."
GenerationState state = null; )
val outputFiles: OutputFileCollection
var state: GenerationState? = null
try { try {
state = GenerationUtils.compileFiles(filesToCompile, environment); state = GenerationUtils.compileFiles(filesToCompile, environment!!)
outputFiles = state.getFactory(); outputFiles = state.factory
} } catch (e: Throwable) {
catch (Throwable e) { throw RuntimeException(e)
throw new RuntimeException(e); } finally {
}
finally {
if (state != null) { if (state != null) {
state.destroy(); state.destroy()
} }
} }
if (!outputDir.exists()) { if (!outputDir.exists()) {
outputDir.mkdirs(); outputDir.mkdirs()
} }
Assert.assertTrue("Cannot create directory for compiled files", outputDir.exists()); Assert.assertTrue("Cannot create directory for compiled files", outputDir.exists())
OutputUtilsKt.writeAllTo(outputFiles, outputDir); outputFiles.writeAllTo(outputDir)
} }
} }
private void processFiles( @Throws(IOException::class)
@NotNull Printer printer, private fun processFiles(
@NotNull File[] files, printer: Printer,
@NotNull FilesWriter holderFull, files: Array<File>,
@NotNull FilesWriter holderMock, holderFull: FilesWriter,
@NotNull FilesWriter holderInheritMFP holderMock: FilesWriter,
) throws IOException { holderInheritMFP: FilesWriter
holderFull.writeFilesOnDiskIfNeeded(); ) {
holderMock.writeFilesOnDiskIfNeeded(); holderFull.writeFilesOnDiskIfNeeded()
holderInheritMFP.writeFilesOnDiskIfNeeded(); holderMock.writeFilesOnDiskIfNeeded()
holderInheritMFP.writeFilesOnDiskIfNeeded()
for (File file : files) { for (file in files) {
if (SpecialFiles.getExcludedFiles().contains(file.getName())) { if (SpecialFiles.getExcludedFiles().contains(file.name)) {
continue; continue
} }
if (file.isDirectory()) { if (file.isDirectory) {
File[] listFiles = file.listFiles(); val listFiles = file.listFiles()
if (listFiles != null) { if (listFiles != null) {
processFiles(printer, listFiles, holderFull, holderMock, holderInheritMFP); processFiles(printer, listFiles, holderFull, holderMock, holderInheritMFP)
} }
} } else if (FileUtilRt.getExtension(file.name) != KotlinFileType.INSTANCE.defaultExtension) {
else if (!FileUtilRt.getExtension(file.getName()).equals(KotlinFileType.INSTANCE.getDefaultExtension())) {
// skip non kotlin files // skip non kotlin files
} } else {
else { val fullFileText = FileUtil.loadFile(file, true)
String fullFileText = FileUtil.loadFile(file, true);
if (!InTextDirectivesUtils.isPassingTarget(TargetBackend.JVM, file)) { if (!InTextDirectivesUtils.isPassingTarget(TargetBackend.JVM, file)) {
continue; continue
} }
//TODO: support LANGUAGE_VERSION //TODO: support LANGUAGE_VERSION
if (InTextDirectivesUtils.isDirectiveDefined(fullFileText, "LANGUAGE_VERSION:")) { if (InTextDirectivesUtils.isDirectiveDefined(fullFileText, "LANGUAGE_VERSION:")) {
continue; continue
} }
//TODO: support multifile facades //TODO: support multifile facades
//TODO: support multifile facades hierarchies //TODO: support multifile facades hierarchies
if (hasBoxMethod(fullFileText)) { if (hasBoxMethod(fullFileText)) {
FilesWriter filesHolder = InTextDirectivesUtils.isDirectiveDefined(fullFileText, "FULL_JDK") || var filesHolder = if (InTextDirectivesUtils.isDirectiveDefined(fullFileText, "FULL_JDK") ||
InTextDirectivesUtils.isDirectiveDefined(fullFileText, "WITH_RUNTIME") || InTextDirectivesUtils.isDirectiveDefined(fullFileText, "WITH_RUNTIME") ||
InTextDirectivesUtils.isDirectiveDefined(fullFileText, "WITH_REFLECT") ? holderFull : holderMock; InTextDirectivesUtils.isDirectiveDefined(fullFileText, "WITH_REFLECT"))
filesHolder = fullFileText.contains("+JVM.INHERIT_MULTIFILE_PARTS") ? holderInheritMFP : filesHolder; holderFull
else
holderMock
filesHolder = if (fullFileText.contains("+JVM.INHERIT_MULTIFILE_PARTS")) holderInheritMFP else filesHolder
FqName classWithBoxMethod = AndroidTestGeneratorKt.genFiles(file, fullFileText, filesHolder); val classWithBoxMethod = genFiles(file, fullFileText, filesHolder) ?: continue
if (classWithBoxMethod == null)
continue;
String generatedTestName = generateTestName(file.getName()); val generatedTestName = generateTestName(file.name)
generateTestMethod(printer, generatedTestName, classWithBoxMethod.asString(), StringUtil.escapeStringCharacters(file.getPath())); generateTestMethod(
printer,
generatedTestName,
classWithBoxMethod.asString(),
StringUtil.escapeStringCharacters(file.path)
)
} }
} }
} }
} }
private fun generateTestName(fileName: String): String {
var result = NameUtils.sanitizeAsJavaIdentifier(FileUtil.getNameWithoutExtension(StringUtil.capitalize(fileName)))
var i = 0
private static boolean hasBoxMethod(String text) {
return text.contains("fun box()");
}
private static void generateTestMethod(Printer p, String testName, String className, String filePath) {
p.println("public void test" + testName + "() throws Exception {");
p.pushIndent();
p.println("invokeBoxMethod(" + className + ".class, \"" + filePath + "\", \"OK\");");
p.popIndent();
p.println("}");
p.println();
}
private String generateTestName(String fileName) {
String result = NameUtils.sanitizeAsJavaIdentifier(FileUtil.getNameWithoutExtension(StringUtil.capitalize(fileName)));
int i = 0;
while (generatedTestNames.contains(result)) { while (generatedTestNames.contains(result)) {
result += "_" + i++; result += "_" + i++
}
generatedTestNames.add(result)
return result
}
companion object {
private const val testClassPackage = "org.jetbrains.kotlin.android.tests"
private const val testClassName = "CodegenTestCaseOnAndroid"
private const val baseTestClassPackage = "org.jetbrains.kotlin.android.tests"
private const val baseTestClassName = "AbstractCodegenTestCaseOnAndroid"
private const val generatorName = "CodegenTestsOnAndroidGenerator"
@JvmStatic
@Throws(Throwable::class)
fun generate(pathManager: PathManager) {
CodegenTestsOnAndroidGenerator(pathManager).generateOutputFiles()
}
private fun hasBoxMethod(text: String): Boolean {
return text.contains("fun box()")
}
private fun generateTestMethod(p: Printer, testName: String, className: String, filePath: String) {
p.println("public void test$testName() throws Exception {")
p.pushIndent()
p.println("invokeBoxMethod($className.class, \"$filePath\", \"OK\");")
p.popIndent()
p.println("}")
p.println()
} }
generatedTestNames.add(result);
return result;
} }
} }