/*
* 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;
}
}