/* * Copyright 2010-2015 JetBrains s.r.o. * * 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 org.jetbrains.kotlin.android.tests; import com.google.common.collect.Lists; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.backend.common.output.OutputFileCollection; import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; import org.jetbrains.kotlin.codegen.CodegenTestFiles; import org.jetbrains.kotlin.codegen.GenerationUtils; import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; import org.jetbrains.kotlin.config.CommonConfigurationKeys; import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.config.JVMConfigurationKeys; import org.jetbrains.kotlin.idea.KotlinFileType; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.NameUtils; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.test.*; import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase; import org.jetbrains.kotlin.utils.Printer; import org.junit.Assert; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class CodegenTestsOnAndroidGenerator extends KtUsefulTestCase { private final PathManager pathManager; 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 int WRITED_FILES_COUNT = 0; private final List<String> generatedTestNames = Lists.newArrayList(); public static void generate(PathManager pathManager) throws Throwable { new CodegenTestsOnAndroidGenerator(pathManager).generateOutputFiles(); } private CodegenTestsOnAndroidGenerator(PathManager pathManager) { this.pathManager = pathManager; } private void generateOutputFiles() throws Throwable { prepareAndroidModule(); 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()) { libsFolderInTestedModule.mkdirs(); } } private void copyKotlinRuntimeJars() throws IOException { FileUtil.copy( ForTestCompileRuntime.runtimeJarForTests(), new File(pathManager.getLibsFolderInAndroidTmpFolder() + "/kotlin-runtime.jar") ); FileUtil.copy( ForTestCompileRuntime.reflectJarForTests(), new File(pathManager.getLibsFolderInAndroidTmpFolder() + "/kotlin-reflect.jar") ); FileUtil.copy( ForTestCompileRuntime.kotlinTestJarForTests(), new File(pathManager.getLibsFolderInAndroidTmpFolder() + "/kotlin-test.jar") ); } private void generateAndSave() throws Throwable { System.out.println("Generating test files..."); StringBuilder out = new StringBuilder(); Printer p = new Printer(out); p.print(FileUtil.loadFile(new File("license/LICENSE.txt"))); p.println("package " + testClassPackage + ";"); p.println(); p.println("import ", baseTestClassPackage, ".", baseTestClassName, ";"); p.println(); p.println("/* This class is generated by " + generatorName + ". DO NOT MODIFY MANUALLY */"); p.println("public class ", testClassName, " extends ", baseTestClassName, " {"); p.pushIndent(); generateTestMethodsForDirectories(p, new File("compiler/testData/codegen/box"), new File("compiler/testData/codegen/boxInline")); p.popIndent(); p.println("}"); String testSourceFilePath = pathManager.getSrcFolderInAndroidTmpFolder() + "/" + testClassPackage.replace(".", "/") + "/" + testClassName + ".java"; FileUtil.writeToFile(new File(testSourceFilePath), out.toString()); } private void generateTestMethodsForDirectories(Printer p, File... dirs) throws IOException { FilesWriter holderMock = new FilesWriter(false, false); FilesWriter holderFull = new FilesWriter(true, false); FilesWriter holderInheritMFP = new FilesWriter(true, true); for (File dir : dirs) { File[] files = dir.listFiles(); Assert.assertNotNull("Folder with testData is empty: " + dir.getAbsolutePath(), files); processFiles(p, files, holderFull, holderMock, holderInheritMFP); } holderFull.writeFilesOnDisk(); holderMock.writeFilesOnDisk(); holderInheritMFP.writeFilesOnDisk(); } class FilesWriter { private final boolean isFullJdkAndRuntime; private final boolean inheritMultifileParts; public List<KtFile> files = new ArrayList<>(); private KotlinCoreEnvironment environment; private FilesWriter(boolean isFullJdkAndRuntime, boolean inheritMultifileParts) { this.isFullJdkAndRuntime = isFullJdkAndRuntime; this.inheritMultifileParts = inheritMultifileParts; this.environment = createEnvironment(isFullJdkAndRuntime); } private KotlinCoreEnvironment createEnvironment(boolean isFullJdkAndRuntime) { ConfigurationKind configurationKind = isFullJdkAndRuntime ? ConfigurationKind.ALL : ConfigurationKind.NO_KOTLIN_REFLECT; TestJdkKind testJdkKind = isFullJdkAndRuntime ? TestJdkKind.FULL_JDK : TestJdkKind.MOCK_JDK; CompilerConfiguration configuration = KotlinTestUtils.newConfiguration(configurationKind, testJdkKind, KotlinTestUtils.getAnnotationsJar()); configuration.put(CommonConfigurationKeys.MODULE_NAME, "android-module-" + MODULE_INDEX++); if (inheritMultifileParts) { configuration.put(JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS, true); } return KotlinCoreEnvironment.createForTests(myTestRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES); } public boolean shouldWriteFilesOnDisk() { return files.size() > 300; } public void writeFilesOnDiskIfNeeded() { if (shouldWriteFilesOnDisk()) { writeFilesOnDisk(); } } public void writeFilesOnDisk() { writeFiles(files); files = new ArrayList<>(); environment = createEnvironment(isFullJdkAndRuntime); } public void addFile(String name, String content) { try { files.add(CodegenTestFiles.create(name, content, environment.getProject()).getPsiFile()); } catch (Throwable e) { throw new RuntimeException("Problem during creating file " + name + ": \n" + content, e); } } private void writeFiles(List<KtFile> filesToCompile) { if (filesToCompile.isEmpty()) return; //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 WRITED_FILES_COUNT += filesToCompile.size(); File outputDir = new File(pathManager.getOutputForCompiledFiles(WRITED_FILES_COUNT / 1000)); System.out.println("Generating " + filesToCompile.size() + " files" + (inheritMultifileParts ? " (JVM.INHERIT_MULTIFILE_PARTS)" : isFullJdkAndRuntime ? " (full jdk and runtime)" : "") + " into " + outputDir.getName() + "..."); OutputFileCollection outputFiles; try { outputFiles = GenerationUtils.compileFiles(filesToCompile, environment).getFactory(); } catch (Throwable e) { throw new RuntimeException(e); } if (!outputDir.exists()) { outputDir.mkdirs(); } Assert.assertTrue("Cannot create directory for compiled files", outputDir.exists()); OutputUtilsKt.writeAllTo(outputFiles, outputDir); } } private void processFiles( @NotNull Printer printer, @NotNull File[] files, @NotNull FilesWriter holderFull, @NotNull FilesWriter holderMock, @NotNull FilesWriter holderInheritMFP ) throws IOException { holderFull.writeFilesOnDiskIfNeeded(); holderMock.writeFilesOnDiskIfNeeded(); holderInheritMFP.writeFilesOnDiskIfNeeded(); for (File file : files) { if (SpecialFiles.getExcludedFiles().contains(file.getName())) { continue; } if (file.isDirectory()) { File[] listFiles = file.listFiles(); if (listFiles != null) { processFiles(printer, listFiles, holderFull, holderMock, holderInheritMFP); } } else if (!FileUtilRt.getExtension(file.getName()).equals(KotlinFileType.INSTANCE.getDefaultExtension())) { // skip non kotlin files } else { String fullFileText = FileUtil.loadFile(file, true); if (!InTextDirectivesUtils.isPassingTarget(TargetBackend.JVM, file)) { continue; } //TODO: support LANGUAGE_VERSION if (InTextDirectivesUtils.isDirectiveDefined(fullFileText, "LANGUAGE_VERSION:")) { continue; } //TODO: support multifile facades //TODO: support multifile facades hierarchies if (hasBoxMethod(fullFileText)) { FilesWriter filesHolder = InTextDirectivesUtils.isDirectiveDefined(fullFileText, "FULL_JDK") || InTextDirectivesUtils.isDirectiveDefined(fullFileText, "WITH_RUNTIME") || InTextDirectivesUtils.isDirectiveDefined(fullFileText, "WITH_REFLECT") ? holderFull : holderMock; filesHolder = fullFileText.contains("+JVM.INHERIT_MULTIFILE_PARTS") ? holderInheritMFP : filesHolder; FqName classWithBoxMethod = AndroidTestGeneratorKt.genFiles(file, fullFileText, filesHolder); if (classWithBoxMethod == null) continue; String generatedTestName = generateTestName(file.getName()); generateTestMethod(printer, generatedTestName, classWithBoxMethod.asString(), StringUtil.escapeStringCharacters(file.getPath())); } } } } 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)) { result += "_" + i++; } generatedTestNames.add(result); return result; } }