mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-04-03 08:31:29 +00:00
Support test with self imports, kotlin multifile tests
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -28,5 +28,5 @@ fun box(): String {
|
||||
|
||||
foo2()().run()
|
||||
|
||||
return test.sideEffects
|
||||
return sideEffects
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user