Support test with self imports, kotlin multifile tests

This commit is contained in:
Michael Bogdanov
2016-04-29 12:45:15 +03:00
parent 0f110b049b
commit e2ae2f313c
9 changed files with 195 additions and 76 deletions

View File

@@ -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();

View File

@@ -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<FqName>()
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<CodegenTestCase.TestFile> {
val files = KotlinTestUtils.createTestFiles(file.name, expectedText, object : KotlinTestUtils.TestFileFactoryNoModules<CodegenTestCase.TestFile>() {
override fun create(fileName: String, text: String, directives: Map<String, String>): 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<FqName>): 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
}

View File

@@ -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<String> 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<KtFile> files = new ArrayList<KtFile>();
@@ -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<KtFile> 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<FqName> 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<FqName> 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();

View File

@@ -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() {

View File

@@ -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

View File

@@ -28,5 +28,5 @@ fun box(): String {
foo2()().run()
return test.sideEffects
return sideEffects
}

View File

@@ -17,6 +17,6 @@ import a.foo
import a.inlineOnly
fun box(): String {
if (!a.inlineOnly<String>("OK")) return "fail 1"
if (!inlineOnly<String>("OK")) return "fail 1"
return foo { "OK" }
}

View File

@@ -15,6 +15,6 @@ import a.foo
import a.inlineOnly
fun box(): String {
if (!a.inlineOnly<String>("OK")) return "fail 1"
if (!inlineOnly<String>("OK")) return "fail 1"
return foo { "OK" }
}

View File

@@ -503,7 +503,7 @@ public abstract class CodegenTestCase extends KtUsefulTestCase {
}
@NotNull
private List<TestFile> createTestFiles(File file, String expectedText, final Ref<File> javaFilesDir) {
public static List<TestFile> createTestFiles(File file, String expectedText, final Ref<File> javaFilesDir) {
return KotlinTestUtils.createTestFiles(file.getName(), expectedText, new KotlinTestUtils.TestFileFactoryNoModules<TestFile>() {
@NotNull
@Override