From e2ae2f313c97ff22c2823cc5b77b4fac335baa4d Mon Sep 17 00:00:00 2001 From: Michael Bogdanov Date: Fri, 29 Apr 2016 12:45:15 +0300 Subject: [PATCH] Support test with self imports, kotlin multifile tests --- .../android/tests/emulator/Emulator.java | 2 +- .../android/tests/AndroidTestGenerator.kt | 160 ++++++++++++++++++ .../tests/CodegenTestsOnAndroidGenerator.java | 89 +++------- .../kotlin/android/tests/SpecialFiles.java | 9 +- .../syntheticAccessors/protectedFromLambda.kt | 3 +- .../boxInline/innerClasses/innerLambda.kt | 2 +- .../inlineFromOptimizedMultifileClass.kt | 2 +- .../inlineFromOtherPackage.kt | 2 +- .../kotlin/codegen/CodegenTestCase.java | 2 +- 9 files changed, 195 insertions(+), 76 deletions(-) create mode 100644 compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/AndroidTestGenerator.kt diff --git a/compiler/android-tests/src/org/jetbrains/kotlin/android/tests/emulator/Emulator.java b/compiler/android-tests/src/org/jetbrains/kotlin/android/tests/emulator/Emulator.java index 4b9f7ba098b..5e0fe19eba7 100644 --- a/compiler/android-tests/src/org/jetbrains/kotlin/android/tests/emulator/Emulator.java +++ b/compiler/android-tests/src/org/jetbrains/kotlin/android/tests/emulator/Emulator.java @@ -176,7 +176,7 @@ public class Emulator { System.out.println("Stopping emulator..."); try { //added cause of missed test results - Thread.sleep(20000); + Thread.sleep(40000); } catch (InterruptedException e) { e.printStackTrace(); diff --git a/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/AndroidTestGenerator.kt b/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/AndroidTestGenerator.kt new file mode 100644 index 00000000000..6f94e73bee9 --- /dev/null +++ b/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/AndroidTestGenerator.kt @@ -0,0 +1,160 @@ +/* + * Copyright 2010-2016 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.intellij.openapi.util.Ref +import org.jetbrains.kotlin.codegen.CodegenTestCase +import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File +import java.util.regex.Pattern + +private val FILE_NAME_ANNOTATIONS = arrayOf("@file:JvmName", "@file:kotlin.jvm.JvmName") + +private val packagePattern = Pattern.compile("(?m)^\\s*package[ |\t]+([\\w|\\.]*)") + +private val importPattern = Pattern.compile("import[ |\t]([\\w|]*\\.)") + +internal fun genFiles(file: File, fileContent: String, filesHolder: CodegenTestsOnAndroidGenerator.FilesWriter): FqName? { + val testFiles = createTestFiles(file, fileContent) + if (testFiles.filter { it.name.endsWith(".java") }.isNotEmpty()) { + //TODO support java files + return null; + } + val ktFiles = testFiles.filter { it.name.endsWith(".kt") } + if (ktFiles.isEmpty()) return null + + val newPackagePrefix = file.path.replace("\\\\|-|\\.|/".toRegex(), "_") + val oldPackage = Ref() + val isSingle = testFiles.size == 1 + val resultFiles = testFiles.map { + val fileName = if (isSingle) it.name else file.name.substringBeforeLast(".kt") + "/" + it.name + TestClassInfo( + fileName, + changePackage(newPackagePrefix, it.content, oldPackage), + oldPackage.get(), + getGeneratedClassName(File(fileName), it.content, newPackagePrefix, oldPackage.get()) + ) + } + + /*replace all Class.forName*/ + resultFiles.forEach { + file -> + file.content = resultFiles.fold(file.content) { r, param -> + patchClassForName(param.newClassId, param.oldPackage, r) + } + } + + /*patch imports and self imports*/ + resultFiles.forEach { + file -> + file.content = resultFiles.fold(file.content) { r, param -> + r.patchImports(param.oldPackage, param.newPackage) + }.patchSelfImports(file.newPackage) + } + + resultFiles.forEach { resultFile -> filesHolder.addFile(resultFile.name, resultFile.content) } + + val boxFiles = resultFiles.filter { hasBoxMethod(it.content) } + if (boxFiles.size != 1) { + println("Several box methods in $file") + } + return boxFiles.last().newClassId +} + + +private fun createTestFiles(file: File, expectedText: String): List { + val files = KotlinTestUtils.createTestFiles(file.name, expectedText, object : KotlinTestUtils.TestFileFactoryNoModules() { + override fun create(fileName: String, text: String, directives: Map): CodegenTestCase.TestFile { + return CodegenTestCase.TestFile(fileName, text) + } + }) + return files +} + +private fun hasBoxMethod(text: String): Boolean { + return text.contains("fun box()") +} + +class TestClassInfo(val name: String, var content: String, val oldPackage: FqName, val newClassId: FqName) { + val newPackage = newClassId.parent() +} + + +private fun changePackage(newPackagePrefix: String, text: String, oldPackage: Ref): String { + val matcher = packagePattern.matcher(text) + if (matcher.find()) { + val oldPackageName = matcher.toMatchResult().group(1) + oldPackage.set(FqName(oldPackageName)) + return matcher.replaceAll("package $newPackagePrefix.$oldPackageName") + } + else { + oldPackage.set(FqName.ROOT) + val packageDirective = "package $newPackagePrefix;\n" + if (text.contains("@file:")) { + val index = text.lastIndexOf("@file:") + val packageDirectiveIndex = text.indexOf("\n", index) + return text.substring(0, packageDirectiveIndex + 1) + packageDirective + text.substring(packageDirectiveIndex + 1) + } + else { + return packageDirective + text + } + } +} + +private fun getGeneratedClassName(file: File, text: String, newPackagePrefix: String, oldPackage: FqName): FqName { + //TODO support multifile facades + var packageFqName = FqName(newPackagePrefix) + if (!oldPackage.isRoot) { + packageFqName = packageFqName.child(Name.identifier(oldPackage.asString())) + } + for (annotation in FILE_NAME_ANNOTATIONS) { + if (text.contains(annotation)) { + val indexOf = text.indexOf(annotation) + val annotationParameter = text.substring(text.indexOf("(\"", indexOf) + 2, text.indexOf("\")", indexOf)) + return packageFqName.child(Name.identifier(annotationParameter)) + } + } + + return PackagePartClassUtils.getPackagePartFqName(packageFqName, file.name) +} + +private fun patchClassForName(className: FqName, oldPackage: FqName, text: String): String { + return text.replace(("Class\\.forName\\(\"" + oldPackage.child(className.shortName()).asString() + "\"\\)").toRegex(), "Class.forName(\"" + className.asString() + "\")") +} + +private fun String.patchImports(oldPackage: FqName, newPackage: FqName): String { + if (oldPackage.isRoot) return this + + return this.replace(("import\\s+" + oldPackage.asString()).toRegex(), "import " + newPackage.asString()) +} + + +private fun String.patchSelfImports(newPackage: FqName): String { + var newText = this; + val matcher = importPattern.matcher(this) + while (matcher.find()) { + val possibleSelfImport = matcher.toMatchResult().group(1) + val classOrObjectPattern = Pattern.compile("[\\s|^](class|object)\\s$possibleSelfImport[\\s|\\(|{|;|:]") + if (classOrObjectPattern.matcher(newText).find()) { + newText = newText.replace("import " + possibleSelfImport, "import " + newPackage.child(Name.identifier(possibleSelfImport)).asString()) + } + } + return newText +} diff --git a/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/CodegenTestsOnAndroidGenerator.java b/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/CodegenTestsOnAndroidGenerator.java index 42faeef21a4..fd91175640d 100644 --- a/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/CodegenTestsOnAndroidGenerator.java +++ b/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/CodegenTestsOnAndroidGenerator.java @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.android.tests; import com.google.common.collect.Lists; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; @@ -32,9 +31,7 @@ import org.jetbrains.kotlin.codegen.GenerationUtils; import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; import org.jetbrains.kotlin.idea.KotlinFileType; import org.jetbrains.kotlin.load.java.JvmAbi; -import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils; import org.jetbrains.kotlin.name.FqName; -import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.test.ConfigurationKind; import org.jetbrains.kotlin.test.InTextDirectivesUtils; @@ -46,10 +43,7 @@ import org.junit.Assert; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class CodegenTestsOnAndroidGenerator extends UsefulTestCase { @@ -60,10 +54,6 @@ public class CodegenTestsOnAndroidGenerator extends UsefulTestCase { private static final String baseTestClassName = "AbstractCodegenTestCaseOnAndroid"; private static final String generatorName = "CodegenTestsOnAndroidGenerator"; - private static final String [] FILE_NAME_ANNOTATIONS = new String [] {"@file:JvmName", "@file:kotlin.jvm.JvmName"}; - - private final Pattern packagePattern = Pattern.compile("package (.*)"); - private final List generatedTestNames = Lists.newArrayList(); public static void generate(PathManager pathManager) throws Throwable { @@ -120,7 +110,7 @@ public class CodegenTestsOnAndroidGenerator extends UsefulTestCase { p.println("public class ", testClassName, " extends ", baseTestClassName, " {"); p.pushIndent(); - generateTestMethodsForDirectories(p, new File("compiler/testData/codegen/box")); + generateTestMethodsForDirectories(p, new File("compiler/testData/codegen/box"), new File("compiler/testData/codegen/boxInline")); p.popIndent(); p.println("}"); @@ -144,7 +134,7 @@ public class CodegenTestsOnAndroidGenerator extends UsefulTestCase { holderMock.writeFilesOnDisk(); } - private class FilesWriter { + class FilesWriter { private final boolean isFullJdkAndRuntime; public List files = new ArrayList(); @@ -179,6 +169,14 @@ public class CodegenTestsOnAndroidGenerator extends UsefulTestCase { environment = createEnvironment(isFullJdkAndRuntime); } + public void addFile(String name, String content) { + try { + files.add(CodegenTestFiles.create(name, content, environment.getProject()).getPsiFile()); + } catch (Throwable e) { + new RuntimeException("Problem during creating file " + name + ": \n" + content, e); + } + } + private void writeFiles(List filesToCompile) { if (filesToCompile.isEmpty()) return; @@ -231,73 +229,32 @@ public class CodegenTestsOnAndroidGenerator extends UsefulTestCase { // skip non kotlin files } else { - String text = FileUtil.loadFile(file, true); - //TODO: support multifile tests - if (text.contains("FILE:")) continue; + String fullFileText = FileUtil.loadFile(file, true); + + //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; + + FqName classWithBoxMethod = AndroidTestGeneratorKt.genFiles(file, fullFileText, filesHolder); + if (classWithBoxMethod == null) + continue; - if (hasBoxMethod(text)) { String generatedTestName = generateTestName(file.getName()); - String packageName = file.getPath().replaceAll("\\\\|-|\\.|/", "_"); - Ref oldPackage = new Ref(); - text = changePackage(packageName, text, oldPackage); - FqName className = getGeneratedClassName(file, text, packageName); - text = patchClassForName(className, oldPackage.get(), text); - - FilesWriter filesHolder = InTextDirectivesUtils.isDirectiveDefined(text, "FULL_JDK") || - InTextDirectivesUtils.isDirectiveDefined(text, "WITH_RUNTIME") || - InTextDirectivesUtils.isDirectiveDefined(text, "WITH_REFLECT") ? holderFull : holderMock; - CodegenTestFiles codegenFile = CodegenTestFiles.create(file.getName(), text, filesHolder.environment.getProject()); - filesHolder.files.add(codegenFile.getPsiFile()); - - - generateTestMethod(printer, generatedTestName, className.asString(), StringUtil.escapeStringCharacters(file.getPath())); + generateTestMethod(printer, generatedTestName, classWithBoxMethod.asString(), StringUtil.escapeStringCharacters(file.getPath())); } } } } - private static FqName getGeneratedClassName(File file, String text, String packageName) { - FqName packageFqName = new FqName(packageName); - for (String annotation : FILE_NAME_ANNOTATIONS) { - if (text.contains(annotation)) { - int indexOf = text.indexOf(annotation); - String annotationParameter = text.substring(text.indexOf("(\"", indexOf) + 2, text.indexOf("\")", indexOf)); - return packageFqName.child(Name.identifier(annotationParameter)); - } - } - return PackagePartClassUtils.getPackagePartFqName(packageFqName, file.getName()); - } private static boolean hasBoxMethod(String text) { return text.contains("fun box()"); } - private String changePackage(String newPackageName, String text, Ref oldPackage) { - if (text.contains("package ")) { - Matcher matcher = packagePattern.matcher(text); - assert matcher.find(); - String oldPackageName = matcher.toMatchResult().group(1); - oldPackage.set(new FqName(oldPackageName)); - return matcher.replaceAll("package " + newPackageName); - } - else { - oldPackage.set(FqName.ROOT); - String packageDirective = "package " + newPackageName + ";\n"; - if (text.contains("@file:")) { - int index = text.lastIndexOf("@file:"); - int packageDirectiveIndex = text.indexOf("\n", index); - return text.substring(0, packageDirectiveIndex + 1) + packageDirective + text.substring(packageDirectiveIndex + 1); - } else { - return packageDirective + text; - } - } - } - - private static String patchClassForName(FqName className, FqName oldPackage, String text) { - return text.replaceAll("Class\\.forName\\(\"" + oldPackage.child(className.shortName()).asString() + "\"\\)", "Class.forName(\"" + className.asString() + "\")"); - } - private static void generateTestMethod(Printer p, String testName, String className, String filePath) { p.println("public void test" + testName + "() throws Exception {"); p.pushIndent(); diff --git a/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/SpecialFiles.java b/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/SpecialFiles.java index af393025366..b8ef6b1b7d3 100644 --- a/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/SpecialFiles.java +++ b/compiler/android-tests/tests/org/jetbrains/kotlin/android/tests/SpecialFiles.java @@ -55,6 +55,8 @@ public class SpecialFiles { excludedFiles.add("recursiveInnerAnonymousObject.kt"); // Cannot change package name excludedFiles.add("approximateCapturedTypes.kt"); // Cannot change package name excludedFiles.add("classForEnumEntry.kt"); // Cannot change package name + excludedFiles.add("kt10143.kt"); // Cannot change package name + excludedFiles.add("internalTopLevelOtherPackage.kt"); // Cannot change package name excludedFiles.add("kt684.kt"); // StackOverflow with StringBuilder (escape()) @@ -73,14 +75,13 @@ public class SpecialFiles { excludedFiles.add("smap"); // Line numbers - // TODO: fix import processing - excludedFiles.add("useImportedMemberFromCompanion.kt"); - excludedFiles.add("useImportedMember.kt"); - excludedFiles.add("importStaticMemberFromObject.kt"); //TODO: fix KT-12127 excludedFiles.add("genericProperty.kt"); excludedFiles.add("external"); //native methods + + excludedFiles.add("enclosingInfo"); // Wrong enclosing info after package renaming + excludedFiles.add("signature"); // Wrong signature after package renaming } private SpecialFiles() { diff --git a/compiler/testData/codegen/box/syntheticAccessors/protectedFromLambda.kt b/compiler/testData/codegen/box/syntheticAccessors/protectedFromLambda.kt index 2aec1ad2a0b..a0cb7165308 100644 --- a/compiler/testData/codegen/box/syntheticAccessors/protectedFromLambda.kt +++ b/compiler/testData/codegen/box/syntheticAccessors/protectedFromLambda.kt @@ -1,12 +1,13 @@ // FILE: A.kt package first +import second.C open class A { protected open fun test(): String = "FAIL (A)" } -fun box() = second.C().value() +fun box() = C().value() // FILE: B.kt diff --git a/compiler/testData/codegen/boxInline/innerClasses/innerLambda.kt b/compiler/testData/codegen/boxInline/innerClasses/innerLambda.kt index cdfa4af7a5f..499ff54698f 100644 --- a/compiler/testData/codegen/boxInline/innerClasses/innerLambda.kt +++ b/compiler/testData/codegen/boxInline/innerClasses/innerLambda.kt @@ -28,5 +28,5 @@ fun box(): String { foo2()().run() - return test.sideEffects + return sideEffects } diff --git a/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt b/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt index a64de1c3012..ccd748ffe82 100644 --- a/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt +++ b/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOptimizedMultifileClass.kt @@ -17,6 +17,6 @@ import a.foo import a.inlineOnly fun box(): String { - if (!a.inlineOnly("OK")) return "fail 1" + if (!inlineOnly("OK")) return "fail 1" return foo { "OK" } } diff --git a/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOtherPackage.kt b/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOtherPackage.kt index 366e5445b2c..b8a01ea600e 100644 --- a/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOtherPackage.kt +++ b/compiler/testData/codegen/boxInline/multifileClasses/inlineFromOtherPackage.kt @@ -15,6 +15,6 @@ import a.foo import a.inlineOnly fun box(): String { - if (!a.inlineOnly("OK")) return "fail 1" + if (!inlineOnly("OK")) return "fail 1" return foo { "OK" } } diff --git a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java index 6b5a0ade72a..0205e14e621 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -503,7 +503,7 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { } @NotNull - private List createTestFiles(File file, String expectedText, final Ref javaFilesDir) { + public static List createTestFiles(File file, String expectedText, final Ref javaFilesDir) { return KotlinTestUtils.createTestFiles(file.getName(), expectedText, new KotlinTestUtils.TestFileFactoryNoModules() { @NotNull @Override