Extract tests-common module without any actual tests

The main reasoning for the module is to avoid running any compiler tests while executing run configuration that searches tests across module dependencies.
This commit is contained in:
Nikolay Krasko
2016-04-07 09:45:18 +03:00
parent c45bdef0c2
commit 47c7181f2a
122 changed files with 253 additions and 99 deletions
@@ -0,0 +1,29 @@
/*
* Copyright 2000-2010 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 com.intellij.testFramework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks the annotated parameter as referencing a file in the testdata directory.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER})
public @interface TestDataFile {
}
@@ -0,0 +1,33 @@
/*
* 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 com.intellij.testFramework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the path to testdata for the current test case class.
* May use the variable $CONTENT_ROOT to specify the module content root or
* $PROJECT_ROOT to use the project base directory.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface TestDataPath {
String value();
}
@@ -0,0 +1,66 @@
/*
* 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.asJava
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiClass
import com.intellij.psi.impl.compiled.ClsElementImpl
import junit.framework.TestCase
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
import java.util.regex.Pattern
object LightClassTestCommon {
private val SUBJECT_FQ_NAME_PATTERN = Pattern.compile("^//\\s*(.*)$", Pattern.MULTILINE)
@JvmOverloads
fun testLightClass(
testDataFile: File,
findLightClass: (String) -> PsiClass?,
normalizeText: (String) -> String = { it }
) {
val text = FileUtil.loadFile(testDataFile, true)
val matcher = SUBJECT_FQ_NAME_PATTERN.matcher(text)
TestCase.assertTrue("No FqName specified. First line of the form '// f.q.Name' expected", matcher.find())
val fqName = matcher.group(1)
val lightClass = findLightClass(fqName)
val actual = actualText(fqName, lightClass, normalizeText)
KotlinTestUtils.assertEqualsToFile(KotlinTestUtils.replaceExtension(testDataFile, "java"), actual)
}
private fun actualText(fqName: String?, lightClass: PsiClass?, normalizeText: (String) -> String): String {
if (lightClass == null) {
return "<not generated>"
}
TestCase.assertTrue("Not a light class: $lightClass ($fqName)", lightClass is KtLightClass)
val delegate = (lightClass as KtLightClass).clsDelegate
TestCase.assertTrue("Not a CLS element: $delegate", delegate is ClsElementImpl)
val buffer = StringBuilder()
(delegate as ClsElementImpl).appendMirrorText(0, buffer)
val actual = normalizeText(buffer.toString())
return actual
}
// Actual text for light class is generated with ClsElementImpl.appendMirrorText() that can find empty DefaultImpl inner class in stubs
// for all interfaces. This inner class can't be used in Java as it generally is not seen from light classes built from Kotlin sources.
// It is also omitted during classes generation in backend so it also absent in light classes built from compiled code.
fun removeEmptyDefaultImpls(text: String) : String = text.replace("\n final class DefaultImpls {\n }\n", "")
}
@@ -0,0 +1,52 @@
/*
* 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.checkers;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.LightVirtualFile;
import junit.framework.TestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.KotlinTestUtils;
public class TestCheckerUtil {
@NotNull
public static KtFile createCheckAndReturnPsiFile(@NotNull String fileName, @NotNull String text, @NotNull Project project) {
KtFile myFile = KotlinTestUtils.createFile(fileName, text, project);
ensureParsed(myFile);
TestCase.assertEquals("light virtual file text mismatch", text, ((LightVirtualFile) myFile.getVirtualFile()).getContent().toString());
TestCase.assertEquals("virtual file text mismatch", text, LoadTextUtil.loadText(myFile.getVirtualFile()));
//noinspection ConstantConditions
TestCase.assertEquals("doc text mismatch", text, myFile.getViewProvider().getDocument().getText());
TestCase.assertEquals("psi text mismatch", text, myFile.getText());
return myFile;
}
private static void ensureParsed(PsiFile file) {
file.accept(new PsiElementVisitor() {
@Override
public void visitElement(@NotNull PsiElement element) {
element.acceptChildren(this);
}
});
}
}
@@ -0,0 +1,194 @@
/*
* 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.codegen;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.Processor;
import kotlin.collections.ArraysKt;
import kotlin.io.FilesKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtNamedFunction;
import org.jetbrains.kotlin.psi.KtProperty;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.test.KotlinTestUtils.getAnnotationsJar;
public abstract class AbstractBlackBoxCodegenTest extends CodegenTestCase {
private boolean addRuntime = false;
private boolean addReflect = false;
@Override
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
TestJdkKind jdkKind = getJdkKind(files);
List<String> javacOptions = new ArrayList<String>(0);
for (TestFile file : files) {
if (InTextDirectivesUtils.isDirectiveDefined(file.content, "WITH_RUNTIME")) {
addRuntime = true;
}
if (InTextDirectivesUtils.isDirectiveDefined(file.content, "WITH_REFLECT")) {
addReflect = true;
}
javacOptions.addAll(InTextDirectivesUtils.findListWithPrefixes(file.content, "// JAVAC_OPTIONS:"));
}
configurationKind =
addReflect ? ConfigurationKind.ALL :
addRuntime ? ConfigurationKind.NO_KOTLIN_REFLECT :
ConfigurationKind.JDK_ONLY;
compileAndRun(files, javaFilesDir, jdkKind, javacOptions);
}
@SuppressWarnings("WeakerAccess")
protected void compileAndRun(
@NotNull List<TestFile> files,
@Nullable File javaSourceDir,
@NotNull TestJdkKind jdkKind,
@NotNull List<String> javacOptions
) {
CompilerConfiguration configuration = createCompilerConfigurationForTests(
configurationKind, jdkKind,
Collections.singletonList(getAnnotationsJar()),
ArraysKt.filterNotNull(new File[] {javaSourceDir}),
files
);
myEnvironment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
);
loadMultiFiles(files);
classFileFactory = GenerationUtils.compileManyFilesGetGenerationStateForTest(
myEnvironment.getProject(), myFiles.getPsiFiles(), new JvmPackagePartProvider(myEnvironment),
myEnvironment.getConfiguration()
).getFactory();
if (javaSourceDir != null) {
// If there are Java files, they should be compiled against the class files produced by Kotlin, so we dump them to the disk
File kotlinOut;
try {
kotlinOut = KotlinTestUtils.tmpDir(toString());
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
OutputUtilsKt.writeAllTo(classFileFactory, kotlinOut);
File output = CodegenTestUtil.compileJava(
findJavaSourcesInDirectory(javaSourceDir), Collections.singletonList(kotlinOut.getPath()), javacOptions
);
// Add javac output to classpath so that the created class loader can find generated Java classes
JvmContentRootsKt.addJvmClasspathRoot(configuration, output);
}
blackBox();
}
@NotNull
protected static List<String> findJavaSourcesInDirectory(@NotNull File directory) {
final List<String> javaFilePaths = new ArrayList<String>(1);
FileUtil.processFilesRecursively(directory, new Processor<File>() {
@Override
public boolean process(File file) {
if (file.isFile() && FilesKt.getExtension(file).equals(JavaFileType.DEFAULT_EXTENSION)) {
javaFilePaths.add(file.getPath());
}
return true;
}
});
return javaFilePaths;
}
protected void blackBox() {
// If there are many files, the first 'box(): String' function will be executed.
GeneratedClassLoader generatedClassLoader = generateAndCreateClassLoader();
for (KtFile firstFile : myFiles.getPsiFiles()) {
String className = getFacadeFqName(firstFile);
if (className == null) continue;
Class<?> aClass = getGeneratedClass(generatedClassLoader, className);
try {
Method method = getBoxMethodOrNull(aClass);
if (method != null) {
String r = (String) method.invoke(null);
assertEquals("OK", r);
return;
}
}
catch (Throwable e) {
System.out.println(generateToText());
throw ExceptionUtilsKt.rethrow(e);
}
}
}
@Nullable
private static String getFacadeFqName(@NotNull KtFile firstFile) {
for (KtDeclaration declaration : firstFile.getDeclarations()) {
if (declaration instanceof KtProperty || declaration instanceof KtNamedFunction) {
return JvmFileClassUtil.getFileClassInfoNoResolve(firstFile).getFacadeClassFqName().asString();
}
}
return null;
}
private static Class<?> getGeneratedClass(GeneratedClassLoader generatedClassLoader, String className) {
try {
return generatedClassLoader.loadClass(className);
}
catch (ClassNotFoundException e) {
fail("No class file was generated for: " + className);
}
return null;
}
private static Method getBoxMethodOrNull(Class<?> aClass) {
try {
return aClass.getMethod("box");
}
catch (NoSuchMethodException e){
return null;
}
}
}
@@ -0,0 +1,161 @@
/*
* 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.codegen
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.org.objectweb.asm.*
import java.io.File
abstract class AbstractBytecodeListingTest : CodegenTestCase() {
override fun doTest(filename: String) {
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL)
loadFileByFullPath(filename)
val ktFile = File(filename)
val txtFile = File(ktFile.parentFile, ktFile.nameWithoutExtension + ".txt")
val generatedFiles = CodegenTestUtil.generateFiles(myEnvironment, myFiles)
.getClassFiles()
.sortedBy { it.relativePath }
.map {
val cr = ClassReader(it.asByteArray())
val visitor = TextCollectingVisitor()
cr.accept(visitor, ClassReader.SKIP_CODE)
KotlinTestUtils.replaceHash(visitor.text, "HASH")
}.joinToString("\n\n")
KotlinTestUtils.assertEqualsToFile(txtFile, generatedFiles)
}
private class TextCollectingVisitor : ClassVisitor(Opcodes.ASM5) {
private class Declaration(val text: String, val annotations: MutableList<String> = arrayListOf())
private val declarationsInsideClass = arrayListOf<Declaration>()
private val classAnnotations = arrayListOf<String>()
private var className = ""
private var classAccess = 0
private fun addAnnotation(desc: String, list: MutableList<String> = declarationsInsideClass.last().annotations) {
val name = Type.getType(desc).className
list.add("@$name ")
}
private fun addModifier(text: String, list: MutableList<String>) {
list.add("$text ")
}
private fun handleModifiers(access: Int, list: MutableList<String> = declarationsInsideClass.last().annotations) {
if ((access and Opcodes.ACC_PUBLIC) != 0) addModifier("public", list)
if ((access and Opcodes.ACC_PROTECTED) != 0) addModifier("protected", list)
if ((access and Opcodes.ACC_PRIVATE) != 0) addModifier("private", list)
if ((access and Opcodes.ACC_SYNTHETIC) != 0) addModifier("synthetic", list)
if ((access and Opcodes.ACC_DEPRECATED) != 0) addModifier("deprecated", list)
if ((access and Opcodes.ACC_FINAL) != 0) addModifier("final", list)
if ((access and Opcodes.ACC_ABSTRACT) != 0) addModifier("abstract", list)
if ((access and Opcodes.ACC_STATIC) != 0) addModifier("static", list)
}
val text: String
get() = StringBuilder().apply {
append(classAnnotations.joinToString("\n", postfix = "\n"))
arrayListOf<String>().apply { handleModifiers(classAccess, this) }.forEach { append(it) }
append("class ")
append(className)
if (declarationsInsideClass.isNotEmpty()) {
append(" {\n")
for (declaration in declarationsInsideClass.sortedBy { it.text }) {
append(" ").append(declaration.annotations.joinToString("")).append(declaration.text).append("\n")
}
append("}")
}
}.toString()
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val returnType = Type.getReturnType(desc).className
val parameterTypes = Type.getArgumentTypes(desc).map { it.className }
val methodAnnotations = arrayListOf<String>()
val parameterAnnotations = hashMapOf<Int, MutableList<String>>()
handleModifiers(access, methodAnnotations)
return object : MethodVisitor(Opcodes.ASM5) {
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
val type = Type.getType(desc).className
methodAnnotations += "@$type "
return super.visitAnnotation(desc, visible)
}
override fun visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor? {
val type = Type.getType(desc).className
parameterAnnotations.getOrPut(parameter, { arrayListOf() }).add("@$type ")
return super.visitParameterAnnotation(parameter, desc, visible)
}
override fun visitEnd() {
val parameterWithAnnotations = parameterTypes.mapIndexed { index, parameter ->
val annotations = parameterAnnotations.getOrElse(index, { emptyList<String>() }).joinToString("")
"${annotations}p$index: $parameter"
}.joinToString()
declarationsInsideClass.add(Declaration("method $name($parameterWithAnnotations): $returnType", methodAnnotations))
super.visitEnd()
}
}
}
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
val type = Type.getType(desc).className
declarationsInsideClass.add(Declaration("field $name: $type"))
handleModifiers(access)
return object : FieldVisitor(Opcodes.ASM5) {
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
addAnnotation(desc)
return super.visitAnnotation(desc, visible)
}
}
}
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
val name = Type.getType(desc).className
classAnnotations.add("@$name")
return super.visitAnnotation(desc, visible)
}
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
className = name
classAccess = access
}
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
declarationsInsideClass.add(Declaration("inner class $name"))
}
}
}
@@ -0,0 +1,174 @@
/*
* 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.codegen;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import kotlin.text.Charsets;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AbstractBytecodeTextTest extends CodegenTestCase {
private static final Pattern AT_OUTPUT_FILE_PATTERN = Pattern.compile("^\\s*//\\s*@(.*):$");
private static final Pattern EXPECTED_OCCURRENCES_PATTERN = Pattern.compile("^\\s*//\\s*(\\d+)\\s*(.*)$");
@Override
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL, files, javaFilesDir);
loadMultiFiles(files);
if (isMultiFileTest(files)) {
doTestMultiFile(files);
}
else {
List<OccurrenceInfo> expected = readExpectedOccurrences(wholeFile.getPath());
String actual = generateToText();
checkGeneratedTextAgainstExpectedOccurrences(actual, expected);
}
}
private static boolean isMultiFileTest(@NotNull List<TestFile> files) {
int kotlinFiles = 0;
for (TestFile file : files) {
if (file.name.endsWith(".kt")) {
kotlinFiles++;
}
}
return kotlinFiles > 1;
}
protected static void checkGeneratedTextAgainstExpectedOccurrences(
@NotNull String text,
@NotNull List<OccurrenceInfo> expectedOccurrences
) {
StringBuilder expected = new StringBuilder();
StringBuilder actual = new StringBuilder();
for (OccurrenceInfo info : expectedOccurrences) {
expected.append(info.numberOfOccurrences).append(" ").append(info.needle).append("\n");
int actualCount = StringUtil.findMatches(text, Pattern.compile("(" + info.needle + ")")).size();
actual.append(actualCount).append(" ").append(info.needle).append("\n");
}
try {
assertEquals(text, expected.toString(), actual.toString());
}
catch (Throwable e) {
System.out.println(text);
throw ExceptionUtilsKt.rethrow(e);
}
}
private void doTestMultiFile(@NotNull List<TestFile> files) throws Exception {
Map<String, List<OccurrenceInfo>> expectedOccurrencesByOutputFile = new LinkedHashMap<String, List<OccurrenceInfo>>();
for (TestFile file : files) {
readExpectedOccurrencesForMultiFileTest(file, expectedOccurrencesByOutputFile);
}
Map<String, String> generated = generateEachFileToText();
for (String expectedOutputFile : expectedOccurrencesByOutputFile.keySet()) {
assertTextWasGenerated(expectedOutputFile, generated);
String generatedText = generated.get(expectedOutputFile);
List<OccurrenceInfo> expectedOccurrences = expectedOccurrencesByOutputFile.get(expectedOutputFile);
checkGeneratedTextAgainstExpectedOccurrences(generatedText, expectedOccurrences);
}
}
private static void assertTextWasGenerated(String expectedOutputFile, Map<String, String> generated) {
if (!generated.containsKey(expectedOutputFile)) {
StringBuilder failMessage = new StringBuilder();
failMessage.append("Missing output file ").append(expectedOutputFile).append(", got ").append(generated.size()).append(": ");
for (String generatedFile : generated.keySet()) {
failMessage.append(generatedFile).append(" ");
}
fail(failMessage.toString());
}
}
@NotNull
protected List<OccurrenceInfo> readExpectedOccurrences(@NotNull String filename) throws Exception {
List<OccurrenceInfo> result = new ArrayList<OccurrenceInfo>();
String[] lines = FileUtil.loadFile(new File(filename), Charsets.UTF_8.name(), true).split("\n");
for (String line : lines) {
Matcher matcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line);
if (matcher.matches()) {
result.add(parseOccurrenceInfo(matcher));
}
}
return result;
}
private static void readExpectedOccurrencesForMultiFileTest(
@NotNull TestFile file,
@NotNull Map<String, List<OccurrenceInfo>> occurrenceMap
) {
List<OccurrenceInfo> currentOccurrenceInfos = null;
for (String line : file.content.split("\n")) {
Matcher atOutputFileMatcher = AT_OUTPUT_FILE_PATTERN.matcher(line);
if (atOutputFileMatcher.matches()) {
String outputFileName = atOutputFileMatcher.group(1);
if (occurrenceMap.containsKey(outputFileName)) {
throw new AssertionError(
file.name + ": Expected occurrences for output file " + outputFileName + " were already provided"
);
}
currentOccurrenceInfos = new ArrayList<OccurrenceInfo>();
occurrenceMap.put(outputFileName, currentOccurrenceInfos);
}
Matcher expectedOccurrencesMatcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line);
if (expectedOccurrencesMatcher.matches()) {
if (currentOccurrenceInfos == null) {
throw new AssertionError(
file.name + ": Should specify output file with '// @<OUTPUT_FILE_NAME>:' before expectations"
);
}
OccurrenceInfo occurrenceInfo = parseOccurrenceInfo(expectedOccurrencesMatcher);
currentOccurrenceInfos.add(occurrenceInfo);
}
}
}
@NotNull
private static OccurrenceInfo parseOccurrenceInfo(Matcher matcher) {
int numberOfOccurrences = Integer.parseInt(matcher.group(1));
String needle = matcher.group(2);
return new OccurrenceInfo(numberOfOccurrences, needle);
}
protected static class OccurrenceInfo {
private final int numberOfOccurrences;
private final String needle;
private OccurrenceInfo(int numberOfOccurrences, @NotNull String needle) {
this.numberOfOccurrences = numberOfOccurrences;
this.needle = needle;
}
}
}
@@ -0,0 +1,184 @@
/*
* 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.codegen;
import com.google.common.io.Files;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
import org.jetbrains.org.objectweb.asm.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Test correctness of written local variables in class file for specified method
*/
public abstract class AbstractCheckLocalVariablesTableTest extends TestCaseWithTmpdir {
private File ktFile;
private KotlinCoreEnvironment jetCoreEnvironment;
public AbstractCheckLocalVariablesTableTest() {
}
@Override
protected void setUp() throws Exception {
super.setUp();
jetCoreEnvironment = KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable);
}
@Override
protected void tearDown() throws Exception {
jetCoreEnvironment = null;
super.tearDown();
}
protected void doTest(@NotNull String ktFileName) throws Exception {
ktFile = new File(ktFileName);
String text = FileUtil.loadFile(ktFile, true);
KtFile psiFile = KotlinTestUtils.createFile(ktFile.getName(), text, jetCoreEnvironment.getProject());
OutputFileCollection outputFiles = GenerationUtils.compileFileGetClassFileFactoryForTest(psiFile, jetCoreEnvironment);
String classAndMethod = parseClassAndMethodSignature();
String[] split = classAndMethod.split("\\.");
assert split.length == 2 : "Exactly one dot is expected: " + classAndMethod;
final String classFileRegex = StringUtil.escapeToRegexp(split[0] + ".class").replace("\\*", ".+");
String methodName = split[1];
OutputFile outputFile = ContainerUtil.find(outputFiles.asList(), new Condition<OutputFile>() {
@Override
public boolean value(OutputFile outputFile) {
return outputFile.getRelativePath().matches(classFileRegex);
}
});
String pathsString = StringUtil.join(outputFiles.asList(), new Function<OutputFile, String>() {
@Override
public String fun(OutputFile file) {
return file.getRelativePath();
}
}, ", ");
assertNotNull("Couldn't find class file for pattern " + classFileRegex + " in: " + pathsString, outputFile);
ClassReader cr = new ClassReader(outputFile.asByteArray());
List<LocalVariable> actualLocalVariables = readLocalVariable(cr, methodName);
KotlinTestUtils.assertEqualsToFile(ktFile, text.substring(0, text.indexOf("// VARIABLE : ")) + getActualVariablesAsString(actualLocalVariables));
}
private static String getActualVariablesAsString(List<LocalVariable> list) {
StringBuilder builder = new StringBuilder();
for (LocalVariable variable : list) {
builder.append(variable.toString()).append("\n");
}
return builder.toString();
}
private static class LocalVariable {
private final String name;
private final String type;
private final int index;
private LocalVariable(
@NotNull String name,
@NotNull String type,
int index
) {
this.name = name;
this.type = type;
this.index = index;
}
@Override
public String toString() {
return "// VARIABLE : NAME=" + name + " TYPE=" + type + " INDEX=" + index;
}
}
@NotNull
private static final Pattern methodPattern = Pattern.compile("^// METHOD : *(.*)");
@NotNull
private String parseClassAndMethodSignature() throws IOException {
List<String> lines = Files.readLines(ktFile, Charset.forName("utf-8"));
for (String line : lines) {
Matcher methodMatcher = methodPattern.matcher(line);
if (methodMatcher.matches()) {
return methodMatcher.group(1);
}
}
throw new AssertionError("method instructions not found");
}
@NotNull
private static List<LocalVariable> readLocalVariable(ClassReader cr, final String methodName) throws Exception {
class Visitor extends ClassVisitor {
List<LocalVariable> readVariables = new ArrayList<LocalVariable>();
public Visitor() {
super(Opcodes.ASM5);
}
@Override
public MethodVisitor visitMethod(
int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions
) {
if (methodName.equals(name + desc)) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitLocalVariable(
@NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index
) {
readVariables.add(new LocalVariable(name, desc, index));
}
};
}
else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
}
Visitor visitor = new Visitor();
cr.accept(visitor, ClassReader.SKIP_FRAMES);
assertFalse("method not found: " + methodName, visitor.readVariables.size() == 0);
return visitor.readVariables;
}
}
@@ -0,0 +1,35 @@
/*
* 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.codegen
import java.io.File
abstract class AbstractCompileKotlinAgainstInlineKotlinTest : AbstractCompileKotlinAgainstKotlinTest() {
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val (factory1, factory2) = doTwoFileTest(files.filter { it.name.endsWith(".kt") })
try {
val allGeneratedFiles = factory1.asList() + factory2.asList()
val sourceFiles = factory1.inputFiles + factory2.inputFiles
InlineTestUtil.checkNoCallsToInline(allGeneratedFiles.filterClassFiles(), sourceFiles)
SMAPTestUtil.checkSMAP(files, allGeneratedFiles.filterClassFiles())
}
catch (e: Throwable) {
println("FIRST:\n\n${factory1.createText()}\n\nSECOND:\n\n${factory2.createText()}")
throw e
}
}
}
@@ -0,0 +1,144 @@
/*
* 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.codegen;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.modules.ModuleBuilder;
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.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager;
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
public abstract class AbstractCompileKotlinAgainstKotlinTest extends CodegenTestCase {
private File tmpdir;
private File aDir;
private File bDir;
@Override
protected void setUp() throws Exception {
super.setUp();
tmpdir = KotlinTestUtils.tmpDirForTest(this);
aDir = new File(tmpdir, "a");
bDir = new File(tmpdir, "b");
KotlinTestUtils.mkdirs(aDir);
KotlinTestUtils.mkdirs(bDir);
}
@Override
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
assert javaFilesDir == null : ".java files are not supported yet in this test";
doTwoFileTest(files);
}
@NotNull
protected Pair<ClassFileFactory, ClassFileFactory> doTwoFileTest(@NotNull List<TestFile> files) throws Exception {
// Note that it may be beneficial to improve this test to handle many files, compiling them successively against all previous
assert files.size() == 2 : "There should be exactly two files in this test";
TestFile fileA = files.get(0);
TestFile fileB = files.get(1);
ClassFileFactory factoryA = compileA(fileA.name, fileA.content, files);
ClassFileFactory factoryB = null;
try {
factoryB = compileB(fileB.name, fileB.content, files);
invokeBox(PackagePartClassUtils.getFilePartShortName(new File(fileB.name).getName()));
}
catch (Throwable e) {
String result = "FIRST: \n\n" + factoryA.createText();
if (factoryB != null) {
result += "\n\nSECOND: \n\n" + factoryB.createText();
}
System.out.println(result);
throw ExceptionUtilsKt.rethrow(e);
}
return new Pair<ClassFileFactory, ClassFileFactory>(factoryA, factoryB);
}
protected void invokeBox(@NotNull String className) throws Exception {
Method box = createGeneratedClassLoader().loadClass(className).getMethod("box");
String result = (String) box.invoke(null);
assertEquals("OK", result);
}
@NotNull
private URLClassLoader createGeneratedClassLoader() throws Exception {
return new URLClassLoader(
new URL[]{ bDir.toURI().toURL(), aDir.toURI().toURL() },
ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
);
}
@NotNull
protected ClassFileFactory compileA(@NotNull String fileName, @NotNull String content, List<TestFile> files) throws IOException {
Disposable compileDisposable = createDisposable("compileA");
KotlinCoreEnvironment environment =
KotlinTestUtils.createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(compileDisposable, ConfigurationKind.ALL, getJdkKind(files));
return compileKotlin(fileName, content, aDir, environment, compileDisposable);
}
@NotNull
protected ClassFileFactory compileB(@NotNull String fileName, @NotNull String content, List<TestFile> files) throws IOException {
CompilerConfiguration configurationWithADirInClasspath = KotlinTestUtils
.compilerConfigurationForTests(ConfigurationKind.ALL, getJdkKind(files), KotlinTestUtils.getAnnotationsJar(), aDir);
Disposable compileDisposable = createDisposable("compileB");
KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForTests(
compileDisposable, configurationWithADirInClasspath, EnvironmentConfigFiles.JVM_CONFIG_FILES);
return compileKotlin(fileName, content, bDir, environment, compileDisposable);
}
private Disposable createDisposable(String debugName) {
Disposable disposable = Disposer.newDisposable("CompileDisposable" + debugName);
Disposer.register(getTestRootDisposable(), disposable);
return disposable;
}
@NotNull
private ClassFileFactory compileKotlin(
@NotNull String fileName, @NotNull String content, @NotNull File outputDir, @NotNull KotlinCoreEnvironment environment,
@NotNull Disposable disposable
) throws IOException {
KtFile psiFile = KotlinTestUtils.createFile(fileName, content, environment.getProject());
ModuleVisibilityManager.SERVICE.getInstance(environment.getProject()).addModule(new ModuleBuilder("module for test", tmpdir.getAbsolutePath(), "test"));
ClassFileFactory outputFiles = GenerationUtils.compileFileGetClassFileFactoryForTest(psiFile, environment);
OutputUtilsKt.writeAllTo(outputFiles, outputDir);
Disposer.dispose(disposable);
return outputFiles;
}
}
@@ -0,0 +1,77 @@
/*
* 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.codegen
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.TestJdkKind
import java.io.File
abstract class AbstractDumpDeclarationsTest : CodegenTestCase() {
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val expectedResult = KotlinTestUtils.replaceExtension(wholeFile, "json")
compileAndCompareDump(files, expectedResult)
}
private fun compileAndCompareDump(files: List<TestFile>, expectedResult: File) {
configurationKind = ConfigurationKind.NO_KOTLIN_REFLECT
val configuration = KotlinTestUtils.compilerConfigurationForTests(
configurationKind, TestJdkKind.MOCK_JDK, listOf(KotlinTestUtils.getAnnotationsJar()), emptyList())
myEnvironment = KotlinCoreEnvironment.createForTests(
testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
loadMultiFiles(files)
val declarationsFile = compileManyFilesGetDeclarationsDump(myFiles.psiFiles)
KotlinTestUtils.assertEqualsToFile(expectedResult, declarationsFile.readText())
}
private fun compileManyFilesGetDeclarationsDump(files: List<KtFile>): File {
val project = myEnvironment.project
val packagePartProvider = JvmPackagePartProvider(myEnvironment)
val analysisResult = JvmResolveUtil.analyzeFilesWithJavaIntegrationAndCheckForErrors(
project, files, packagePartProvider)
analysisResult.throwIfError()
val dumpToFile = KotlinTestUtils.tmpDirForTest(this).resolve(this.name + ".json")
val state = GenerationState(
project, ClassBuilderFactories.TEST,
analysisResult.moduleDescriptor, analysisResult.bindingContext,
files,
disableCallAssertions = false,
disableParamAssertions = false,
dumpBinarySignatureMappingTo = dumpToFile)
KotlinCodegenFacade.compileCorrectFiles(state, org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION)
state.destroy()
return dumpToFile
}
}
@@ -0,0 +1,232 @@
/*
* 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.codegen;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import kotlin.Pair;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
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.state.GenerationState;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.org.objectweb.asm.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AbstractLineNumberTest extends TestCaseWithTmpdir {
private static final String LINE_NUMBER_FUN = "lineNumber";
private static final Pattern TEST_LINE_NUMBER_PATTERN = Pattern.compile("^.*test." + LINE_NUMBER_FUN + "\\(\\).*$");
@NotNull
private static String getTestDataPath() {
return KotlinTestUtils.getTestDataPathBase() + "/lineNumber";
}
@NotNull
private KotlinCoreEnvironment createEnvironment() {
return KotlinCoreEnvironment.createForTests(
myTestRootDisposable,
KotlinTestUtils.compilerConfigurationForTests(ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK,
KotlinTestUtils.getAnnotationsJar(), tmpdir),
EnvironmentConfigFiles.JVM_CONFIG_FILES);
}
@Override
public void setUp() throws Exception {
super.setUp();
KotlinCoreEnvironment environment = createEnvironment();
KtFile psiFile = KotlinTestUtils.createFile(LINE_NUMBER_FUN + ".kt",
"package test;\n\npublic fun " + LINE_NUMBER_FUN + "(): Int = 0\n",
environment.getProject());
OutputFileCollection outputFiles =
GenerationUtils.compileFileGetClassFileFactoryForTest(psiFile, environment);
OutputUtilsKt.writeAllTo(outputFiles, tmpdir);
}
@NotNull
private Pair<KtFile, KotlinCoreEnvironment> createPsiFile(@NotNull String filename) {
File file = new File(filename);
KotlinCoreEnvironment environment = createEnvironment();
String text;
try {
text = FileUtil.loadFile(file, true);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
return new Pair(KotlinTestUtils.createFile(file.getName(), text, environment.getProject()), environment);
}
private void doTest(@NotNull String filename, boolean custom) {
Pair<KtFile, KotlinCoreEnvironment> fileAndEnv = createPsiFile(filename);
KtFile psiFile = fileAndEnv.getFirst();
KotlinCoreEnvironment environment = fileAndEnv.getSecond();
GenerationState state = GenerationUtils.compileFileGetGenerationStateForTest(psiFile, environment);
if (custom) {
List<Integer> actualLineNumbers = extractActualLineNumbersFromBytecode(state, false);
String text = psiFile.getText();
String newFileText = text.substring(0, text.indexOf("// ")) + getActualLineNumbersAsString(actualLineNumbers);
KotlinTestUtils.assertEqualsToFile(new File(filename), newFileText);
}
else {
List<Integer> expectedLineNumbers = extractSelectedLineNumbersFromSource(psiFile);
List<Integer> actualLineNumbers = extractActualLineNumbersFromBytecode(state, true);
assertSameElements(actualLineNumbers, expectedLineNumbers);
}
}
private static String getActualLineNumbersAsString(List<Integer> list) {
return CollectionsKt.joinToString(list, " ", "// ", "", -1, "...", new Function1<Integer, CharSequence>() {
@Override
public CharSequence invoke(Integer integer) {
return integer.toString();
}
});
}
@NotNull
private static List<Integer> extractActualLineNumbersFromBytecode(@NotNull GenerationState state, boolean testFunInvoke) {
ClassFileFactory factory = state.getFactory();
List<Integer> actualLineNumbers = Lists.newArrayList();
for (OutputFile outputFile : ClassFileUtilsKt.getClassFiles(factory)) {
ClassReader cr = new ClassReader(outputFile.asByteArray());
try {
List<Integer> lineNumbers = testFunInvoke ? readTestFunLineNumbers(cr) : readAllLineNumbers(cr);
actualLineNumbers.addAll(lineNumbers);
}
catch (Throwable e) {
System.out.println(factory.createText());
throw ExceptionUtilsKt.rethrow(e);
}
}
return actualLineNumbers;
}
protected void doTest(String path) {
doTest(path, false);
}
protected void doTestCustom(String path) {
doTest(path, true);
}
@NotNull
private static List<Integer> extractSelectedLineNumbersFromSource(@NotNull KtFile file) {
String fileContent = file.getText();
List<Integer> lineNumbers = Lists.newArrayList();
String[] lines = StringUtil.convertLineSeparators(fileContent).split("\n");
for (int i = 0; i < lines.length; i++) {
Matcher matcher = TEST_LINE_NUMBER_PATTERN.matcher(lines[i]);
if (matcher.matches()) {
lineNumbers.add(i + 1);
}
}
return lineNumbers;
}
@NotNull
private static List<Integer> readTestFunLineNumbers(@NotNull ClassReader cr) {
final List<Label> labels = Lists.newArrayList();
final Map<Label, Integer> labels2LineNumbers = Maps.newHashMap();
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
private Label lastLabel;
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (LINE_NUMBER_FUN.equals(name)) {
assert lastLabel != null : "A function call with no preceding label";
labels.add(lastLabel);
}
lastLabel = null;
}
@Override
public void visitLabel(@NotNull Label label) {
lastLabel = label;
}
@Override
public void visitLineNumber(int line, @NotNull Label start) {
labels2LineNumbers.put(start, line);
}
};
}
};
cr.accept(visitor, ClassReader.SKIP_FRAMES);
List<Integer> lineNumbers = Lists.newArrayList();
for (Label label : labels) {
Integer lineNumber = labels2LineNumbers.get(label);
assert lineNumber != null : "No line number found for a label";
lineNumbers.add(lineNumber);
}
return lineNumbers;
}
@NotNull
private static List<Integer> readAllLineNumbers(@NotNull ClassReader reader) {
final List<Integer> result = new ArrayList<Integer>();
reader.accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitLineNumber(int line, @NotNull Label label) {
result.add(line);
}
};
}
}, ClassReader.SKIP_FRAMES);
return result;
}
}
@@ -0,0 +1,84 @@
/*
* 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.codegen;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public abstract class AbstractScriptCodegenTest extends CodegenTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.JDK_ONLY);
}
@Override
protected void doTest(@NotNull String filename) {
loadFileByFullPath(filename);
try {
//noinspection ConstantConditions
FqName fqName = myFiles.getPsiFile().getScript().getFqName();
Class<?> scriptClass = generateClass(fqName.asString());
Constructor constructor = getTheOnlyConstructor(scriptClass);
Object scriptInstance = constructor.newInstance(myFiles.getScriptParameterValues().toArray());
assertFalse("expecting at least one expectation", myFiles.getExpectedValues().isEmpty());
for (Pair<String, String> nameValue : myFiles.getExpectedValues()) {
String fieldName = nameValue.first;
String expectedValue = nameValue.second;
if (expectedValue.equals("<nofield>")) {
try {
scriptClass.getDeclaredField(fieldName);
fail("must have no field " + fieldName);
}
catch (NoSuchFieldException e) {
continue;
}
}
Field field = scriptClass.getDeclaredField(fieldName);
field.setAccessible(true);
Object result = field.get(scriptInstance);
String resultString = result != null ? result.toString() : "null";
assertEquals("comparing field " + fieldName, expectedValue, resultString);
}
}
catch (Throwable e) {
System.out.println(generateToText());
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
private static Constructor getTheOnlyConstructor(@NotNull Class<?> clazz) {
Constructor[] constructors = clazz.getConstructors();
if (constructors.length != 1) {
throw new IllegalArgumentException("Script class should have one constructor: " + clazz);
}
return constructors[0];
}
}
@@ -0,0 +1,81 @@
/*
* 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.codegen;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Processor;
import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.MockLibraryUtil;
import org.jetbrains.kotlin.test.TestJdkKind;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class AbstractTopLevelMembersInvocationTest extends AbstractBytecodeTextTest {
private static final String LIBRARY = "library";
@Override
public void doTest(@NotNull String filename) throws Exception {
File root = new File(filename);
final List<String> sourceFiles = new ArrayList<String>(2);
FileUtil.processFilesRecursively(root, new Processor<File>() {
@Override
public boolean process(File file) {
if (file.getName().endsWith(".kt")) {
sourceFiles.add(relativePath(file));
return true;
}
return true;
}
}, new Processor<File>() {
@Override
public boolean process(File file) {
return !LIBRARY.equals(file.getName());
}
});
File library = new File(root, LIBRARY);
List<File> classPath = library.exists() ?
Collections.singletonList(MockLibraryUtil.compileLibraryToJar(library.getPath(), LIBRARY, false, false)) :
Collections.<File>emptyList();
assert !sourceFiles.isEmpty() : getTestName(true) + " should contain at least one .kt file";
Collections.sort(sourceFiles);
myEnvironment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(),
KotlinTestUtils.compilerConfigurationForTests(ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK,
CollectionsKt.plus(classPath, KotlinTestUtils.getAnnotationsJar()), classPath),
EnvironmentConfigFiles.JVM_CONFIG_FILES);
loadFiles(ArrayUtil.toStringArray(sourceFiles));
List<OccurrenceInfo> expected = readExpectedOccurrences(KotlinTestUtils.getTestDataPathBase() + "/codegen/" + sourceFiles.get(0));
String actual = generateToText();
checkGeneratedTextAgainstExpectedOccurrences(actual, expected);
}
}
@@ -0,0 +1,537 @@
/*
* 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.codegen;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.testFramework.TestDataFile;
import kotlin.collections.ArraysKt;
import kotlin.io.FilesKt;
import kotlin.text.Charsets;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.checkers.CheckerTestUtil;
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.config.CompilerConfigurationKey;
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.tree.ClassNode;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
import org.jetbrains.org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.jetbrains.org.objectweb.asm.util.Textifier;
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jetbrains.kotlin.codegen.CodegenTestUtil.*;
import static org.jetbrains.kotlin.test.KotlinTestUtils.compilerConfigurationForTests;
import static org.jetbrains.kotlin.test.KotlinTestUtils.getAnnotationsJar;
public abstract class CodegenTestCase extends KtUsefulTestCase {
private static final String DEFAULT_TEST_FILE_NAME = "a_test";
protected KotlinCoreEnvironment myEnvironment;
protected CodegenTestFiles myFiles;
protected ClassFileFactory classFileFactory;
protected GeneratedClassLoader initializedClassLoader;
protected ConfigurationKind configurationKind = ConfigurationKind.JDK_ONLY;
protected final void createEnvironmentWithMockJdkAndIdeaAnnotations(
@NotNull ConfigurationKind configurationKind,
@Nullable File... javaSourceRoots
) {
createEnvironmentWithMockJdkAndIdeaAnnotations(configurationKind, Collections.<TestFile>emptyList(), javaSourceRoots);
}
@NotNull
protected static TestJdkKind getJdkKind(@NotNull List<TestFile> files) {
for (TestFile file : files) {
if (InTextDirectivesUtils.isDirectiveDefined(file.content, "FULL_JDK")) {
return TestJdkKind.FULL_JDK;
}
}
return TestJdkKind.MOCK_JDK;
}
protected final void createEnvironmentWithMockJdkAndIdeaAnnotations(
@NotNull ConfigurationKind configurationKind,
@NotNull List<TestFile> testFilesWithConfigurationDirectives,
@Nullable File... javaSourceRoots
) {
if (myEnvironment != null) {
throw new IllegalStateException("must not set up myEnvironment twice");
}
CompilerConfiguration configuration = createCompilerConfigurationForTests(
configurationKind,
TestJdkKind.MOCK_JDK,
Collections.singletonList(getAnnotationsJar()),
ArraysKt.filterNotNull(javaSourceRoots),
testFilesWithConfigurationDirectives
);
myEnvironment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
);
}
@NotNull
protected static CompilerConfiguration createCompilerConfigurationForTests(
@NotNull ConfigurationKind kind,
@NotNull TestJdkKind jdkKind,
@NotNull List<File> classpath,
@NotNull List<File> javaSource,
@NotNull List<TestFile> testFilesWithConfigurationDirectives
) {
CompilerConfiguration configuration = compilerConfigurationForTests(kind, jdkKind, classpath, javaSource);
updateConfigurationByDirectivesInTestFiles(testFilesWithConfigurationDirectives, configuration);
return configuration;
}
private static void updateConfigurationByDirectivesInTestFiles(
@NotNull List<TestFile> testFilesWithConfigurationDirectives,
@NotNull CompilerConfiguration configuration
) {
List<String> kotlinConfigurationFlags = new ArrayList<String>(0);
for (TestFile testFile : testFilesWithConfigurationDirectives) {
kotlinConfigurationFlags.addAll(InTextDirectivesUtils.findListWithPrefixes(testFile.content, "// KOTLIN_CONFIGURATION_FLAGS:"));
}
updateConfigurationWithFlags(configuration, kotlinConfigurationFlags);
}
private static final Map<String, Class<?>> FLAG_NAMESPACE_TO_CLASS = ImmutableMap.of(
"CLI", CLIConfigurationKeys.class,
"JVM", JVMConfigurationKeys.class
);
private static final List<Class<?>> FLAG_CLASSES = ImmutableList.of(CLIConfigurationKeys.class, JVMConfigurationKeys.class);
private static final Pattern BOOLEAN_FLAG_PATTERN = Pattern.compile("([+-])(([a-zA-Z_0-9]*)\\.)?([a-zA-Z_0-9]*)");
private static void updateConfigurationWithFlags(@NotNull CompilerConfiguration configuration, @NotNull List<String> flags) {
for (String flag : flags) {
Matcher m = BOOLEAN_FLAG_PATTERN.matcher(flag);
if (m.matches()) {
boolean flagEnabled = !"-".equals(m.group(1));
String flagNamespace = m.group(3);
String flagName = m.group(4);
tryApplyBooleanFlag(configuration, flag, flagEnabled, flagNamespace, flagName);
}
}
}
private static void tryApplyBooleanFlag(
@NotNull CompilerConfiguration configuration,
@NotNull String flag,
boolean flagEnabled,
@Nullable String flagNamespace,
@NotNull String flagName
) {
Class<?> configurationKeysClass;
Field configurationKeyField = null;
if (flagNamespace == null) {
for (Class<?> flagClass : FLAG_CLASSES) {
try {
configurationKeyField = flagClass.getField(flagName);
break;
}
catch (Exception ignored) {
}
}
}
else {
configurationKeysClass = FLAG_NAMESPACE_TO_CLASS.get(flagNamespace);
assert configurationKeysClass != null : "Expected [+|-][namespace.]configurationKey, got: " + flag;
try {
configurationKeyField = configurationKeysClass.getField(flagName);
}
catch (Exception e) {
configurationKeyField = null;
}
}
assert configurationKeyField != null : "Expected [+|-][namespace.]configurationKey, got: " + flag;
try {
//noinspection unchecked
CompilerConfigurationKey<Boolean> configurationKey = (CompilerConfigurationKey<Boolean>) configurationKeyField.get(null);
configuration.put(configurationKey, flagEnabled);
}
catch (Exception e) {
assert false : "Expected [+|-][namespace.]configurationKey, got: " + flag;
}
}
@Override
protected void tearDown() throws Exception {
myFiles = null;
myEnvironment = null;
classFileFactory = null;
if (initializedClassLoader != null) {
initializedClassLoader.dispose();
initializedClassLoader = null;
}
super.tearDown();
}
protected void loadText(@NotNull String text) {
myFiles = CodegenTestFiles.create(DEFAULT_TEST_FILE_NAME + ".kt", text, myEnvironment.getProject());
}
@NotNull
protected String loadFile(@NotNull @TestDataFile String name) {
return loadFileByFullPath(KotlinTestUtils.getTestDataPathBase() + "/codegen/" + name);
}
@NotNull
protected String loadFileByFullPath(@NotNull String fullPath) {
try {
File file = new File(fullPath);
String content = FileUtil.loadFile(file, Charsets.UTF_8.name(), true);
myFiles = CodegenTestFiles.create(file.getName(), content, myEnvironment.getProject());
return content;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void loadFiles(@NotNull String... names) {
myFiles = CodegenTestFiles.create(myEnvironment.getProject(), names);
}
protected void loadFile() {
loadFile(getPrefix() + "/" + getTestName(true) + ".kt");
}
protected void loadMultiFiles(@NotNull List<TestFile> files) {
Collections.sort(files);
List<KtFile> ktFiles = new ArrayList<KtFile>(files.size());
for (TestFile file : files) {
if (file.name.endsWith(".kt")) {
String content = CheckerTestUtil.parseDiagnosedRanges(file.content, new ArrayList<CheckerTestUtil.DiagnosedRange>(0));
ktFiles.add(KotlinTestUtils.createFile(file.name, content, myEnvironment.getProject()));
}
}
myFiles = CodegenTestFiles.create(ktFiles);
}
@NotNull
protected String codegenTestBasePath() {
return "compiler/testData/codegen/";
}
@NotNull
protected String relativePath(@NotNull File file) {
String stringToCut = codegenTestBasePath();
String systemIndependentPath = file.getPath().replace(File.separatorChar, '/');
assert systemIndependentPath.startsWith(stringToCut) : "File path is not absolute: " + file;
return systemIndependentPath.substring(stringToCut.length());
}
@NotNull
protected String getPrefix() {
throw new UnsupportedOperationException();
}
@NotNull
protected GeneratedClassLoader generateAndCreateClassLoader() {
if (initializedClassLoader != null) {
fail("Double initialization of class loader in same test");
}
initializedClassLoader = createClassLoader();
if (!verifyAllFilesWithAsm(generateClassesInFile(), initializedClassLoader)) {
fail("Verification failed: see exceptions above");
}
return initializedClassLoader;
}
@NotNull
protected GeneratedClassLoader createClassLoader() {
return new GeneratedClassLoader(
generateClassesInFile(),
configurationKind.getWithReflection() ? ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
: ForTestCompileRuntime.runtimeJarClassLoader(),
getClassPathURLs()
);
}
@NotNull
private URL[] getClassPathURLs() {
List<URL> urls = Lists.newArrayList();
for (File file : JvmContentRootsKt.getJvmClasspathRoots(myEnvironment.getConfiguration())) {
try {
urls.add(file.toURI().toURL());
}
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return urls.toArray(new URL[urls.size()]);
}
@NotNull
protected String generateToText() {
if (classFileFactory == null) {
classFileFactory = generateFiles(myEnvironment, myFiles);
}
return classFileFactory.createText();
}
@NotNull
protected Map<String, String> generateEachFileToText() {
if (classFileFactory == null) {
classFileFactory = generateFiles(myEnvironment, myFiles);
}
return classFileFactory.createTextForEachFile();
}
@NotNull
protected Class<?> generateFacadeClass() {
FqName facadeClassFqName = JvmFileClassUtil.getFileClassInfoNoResolve(myFiles.getPsiFile()).getFacadeClassFqName();
return generateClass(facadeClassFqName.asString());
}
@NotNull
protected Class<?> generateClass(@NotNull String name) {
try {
return generateAndCreateClassLoader().loadClass(name);
}
catch (ClassNotFoundException e) {
fail("No class file was generated for: " + name);
return null;
}
}
@NotNull
protected ClassFileFactory generateClassesInFile() {
if (classFileFactory == null) {
try {
classFileFactory = generateFiles(myEnvironment, myFiles);
if (DxChecker.RUN_DX_CHECKER) {
DxChecker.check(classFileFactory);
}
}
catch (Throwable e) {
e.printStackTrace();
System.err.println("Generating instructions as text...");
try {
if (classFileFactory == null) {
System.out.println("Cannot generate text: exception was thrown during generation");
}
else {
System.out.println(classFileFactory.createText());
}
}
catch (Throwable e1) {
System.err.println("Exception thrown while trying to generate text, the actual exception follows:");
e1.printStackTrace();
System.err.println("-----------------------------------------------------------------------------");
}
fail("See exceptions above");
}
}
return classFileFactory;
}
private static boolean verifyAllFilesWithAsm(ClassFileFactory factory, ClassLoader loader) {
boolean noErrors = true;
for (OutputFile file : ClassFileUtilsKt.getClassFiles(factory)) {
noErrors &= verifyWithAsm(file, loader);
}
return noErrors;
}
private static boolean verifyWithAsm(@NotNull OutputFile file, ClassLoader loader) {
ClassNode classNode = new ClassNode();
new ClassReader(file.asByteArray()).accept(classNode, 0);
SimpleVerifier verifier = new SimpleVerifier();
verifier.setClassLoader(loader);
Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(verifier);
boolean noErrors = true;
for (MethodNode method : classNode.methods) {
try {
analyzer.analyze(classNode.name, method);
}
catch (Throwable e) {
System.err.println(file.asText());
System.err.println(classNode.name + "::" + method.name + method.desc);
//noinspection InstanceofCatchParameter
if (e instanceof AnalyzerException) {
// Print the erroneous instruction
TraceMethodVisitor tmv = new TraceMethodVisitor(new Textifier());
((AnalyzerException) e).node.accept(tmv);
PrintWriter pw = new PrintWriter(System.err);
tmv.p.print(pw);
pw.flush();
}
e.printStackTrace();
noErrors = false;
}
}
return noErrors;
}
@NotNull
protected Method generateFunction() {
Class<?> aClass = generateFacadeClass();
try {
return findTheOnlyMethod(aClass);
} catch (Error e) {
System.out.println(generateToText());
throw e;
}
}
@NotNull
protected Method generateFunction(@NotNull String name) {
return findDeclaredMethodByName(generateFacadeClass(), name);
}
@NotNull
public Class<? extends Annotation> loadAnnotationClassQuietly(@NotNull String fqName) {
try {
//noinspection unchecked
return (Class<? extends Annotation>) initializedClassLoader.loadClass(fqName);
}
catch (ClassNotFoundException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
public static class TestFile implements Comparable<TestFile> {
public final String name;
public final String content;
public TestFile(@NotNull String name, @NotNull String content) {
this.name = name;
this.content = content;
}
@Override
public int compareTo(@NotNull TestFile o) {
return name.compareTo(o.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof TestFile && ((TestFile) obj).name.equals(name);
}
@Override
public String toString() {
return name;
}
}
protected void doTest(String filePath) throws Exception {
File file = new File(filePath);
String expectedText = KotlinTestUtils.doLoadFile(file);
final Ref<File> javaFilesDir = Ref.create();
List<TestFile> testFiles = createTestFiles(file, expectedText, javaFilesDir);
doMultiFileTest(file, testFiles, javaFilesDir.get());
}
@NotNull
private List<TestFile> createTestFiles(File file, String expectedText, final Ref<File> javaFilesDir) {
return KotlinTestUtils.createTestFiles(file.getName(), expectedText, new KotlinTestUtils.TestFileFactoryNoModules<TestFile>() {
@NotNull
@Override
public TestFile create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
if (fileName.endsWith(".java")) {
if (javaFilesDir.isNull()) {
try {
javaFilesDir.set(KotlinTestUtils.tmpDir("java-files"));
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
writeSourceFile(fileName, text, javaFilesDir.get());
}
return new TestFile(fileName, text);
}
private void writeSourceFile(@NotNull String fileName, @NotNull String content, @NotNull File targetDir) {
File file = new File(targetDir, fileName);
KotlinTestUtils.mkdirs(file.getParentFile());
FilesKt.writeText(file, content, Charsets.UTF_8);
}
});
}
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
throw new UnsupportedOperationException("Multi-file test cases are not supported in this test");
}
}
@@ -0,0 +1,175 @@
/*
* 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.codegen;
import com.google.common.collect.Lists;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiErrorElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.checkers.CheckerTestUtil;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.AnalyzingUtils;
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform;
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider;
import org.jetbrains.kotlin.script.ScriptParameter;
import org.jetbrains.kotlin.scripts.TestScriptDefinition;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.Variance;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CodegenTestFiles {
@NotNull
private final List<KtFile> psiFiles;
@NotNull
private final List<Pair<String, String>> expectedValues;
@NotNull
private final List<Object> scriptParameterValues;
private CodegenTestFiles(
@NotNull List<KtFile> psiFiles,
@NotNull List<Pair<String, String>> expectedValues,
@NotNull List<Object> scriptParameterValues
) {
this.psiFiles = psiFiles;
this.expectedValues = expectedValues;
this.scriptParameterValues = scriptParameterValues;
}
@NotNull
public KtFile getPsiFile() {
assert psiFiles.size() == 1;
return psiFiles.get(0);
}
@NotNull
public List<Pair<String, String>> getExpectedValues() {
return expectedValues;
}
@NotNull
public List<Object> getScriptParameterValues() {
return scriptParameterValues;
}
@NotNull
public List<KtFile> getPsiFiles() {
return psiFiles;
}
@NotNull
public static CodegenTestFiles create(@NotNull List<KtFile> ktFiles) {
assert !ktFiles.isEmpty() : "List should have at least one file";
return new CodegenTestFiles(ktFiles, Collections.<Pair<String, String>>emptyList(), Collections.emptyList());
}
public static CodegenTestFiles create(Project project, String[] names) {
return create(project, names, KotlinTestUtils.getTestDataPathBase());
}
public static CodegenTestFiles create(Project project, String[] names, String testDataPath) {
List<KtFile> files = new ArrayList<KtFile>(names.length);
for (String name : names) {
try {
String content = KotlinTestUtils.doLoadFile(testDataPath + "/codegen/", name);
KtFile file = KotlinTestUtils.createFile(name, content, project);
files.add(file);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
return create(files);
}
@NotNull
public static CodegenTestFiles create(@NotNull String fileName, @NotNull String contentWithDiagnosticMarkup, @NotNull Project project) {
String content = CheckerTestUtil.parseDiagnosedRanges(contentWithDiagnosticMarkup, new ArrayList<CheckerTestUtil.DiagnosedRange>());
KtFile file = KotlinTestUtils.createFile(fileName, content, project);
List<PsiErrorElement> ranges = AnalyzingUtils.getSyntaxErrorRanges(file);
assert ranges.isEmpty() : "Syntax errors found in " + file + ": " + ranges;
List<Pair<String, String>> expectedValues = Lists.newArrayList();
Matcher matcher = Pattern.compile("// expected: (\\S+): (.*)").matcher(content);
while (matcher.find()) {
String fieldName = matcher.group(1);
String expectedValue = matcher.group(2);
expectedValues.add(Pair.create(fieldName, expectedValue));
}
List<ScriptParameter> scriptParameterTypes = Lists.newArrayList();
List<Object> scriptParameterValues = Lists.newArrayList();
if (file.isScript()) {
Pattern scriptParametersPattern = Pattern.compile("param: (\\S+): (\\S+): (\\S.*)");
Matcher scriptParametersMatcher = scriptParametersPattern.matcher(file.getText());
while (scriptParametersMatcher.find()) {
String name = scriptParametersMatcher.group(1);
String type = scriptParametersMatcher.group(2);
String valueString = scriptParametersMatcher.group(3);
Object value;
KotlinType jetType;
KotlinBuiltIns builtIns = JvmPlatform.INSTANCE.getBuiltIns();
if (type.equals("kotlin.String")) {
value = valueString;
jetType = builtIns.getStringType();
}
else if (type.equals("kotlin.Long")) {
value = Long.parseLong(valueString);
jetType = builtIns.getLongType();
}
else if (type.equals("kotlin.Int")) {
value = Integer.parseInt(valueString);
jetType = builtIns.getIntType();
}
else if (type.equals("kotlin.Array<kotlin.String>")) {
value = valueString.split(" ");
jetType = builtIns.getArrayType(Variance.INVARIANT, builtIns.getStringType());
}
else {
throw new AssertionError("TODO: " + type);
}
scriptParameterTypes.add(new ScriptParameter(Name.identifier(name), jetType));
scriptParameterValues.add(value);
}
KotlinScriptDefinitionProvider definitionProvider = KotlinScriptDefinitionProvider.getInstance(project);
definitionProvider.addScriptDefinition(
new TestScriptDefinition(
".kts",
scriptParameterTypes
)
);
}
return new CodegenTestFiles(Collections.singletonList(file), expectedValues, scriptParameterValues);
}
}
@@ -0,0 +1,176 @@
/*
* 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.codegen;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.resolve.AnalyzingUtils;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.kotlin.utils.StringsKt;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertTrue;
public class CodegenTestUtil {
private CodegenTestUtil() {}
@NotNull
public static ClassFileFactory generateFiles(@NotNull KotlinCoreEnvironment environment, @NotNull CodegenTestFiles files) {
AnalysisResult analysisResult = JvmResolveUtil.analyzeFilesWithJavaIntegrationAndCheckForErrors(
environment.getProject(),
files.getPsiFiles(),
new JvmPackagePartProvider(environment)
);
analysisResult.throwIfError();
AnalyzingUtils.throwExceptionOnErrors(analysisResult.getBindingContext());
CompilerConfiguration configuration = environment.getConfiguration();
GenerationState state = new GenerationState(
environment.getProject(),
ClassBuilderFactories.TEST,
analysisResult.getModuleDescriptor(),
analysisResult.getBindingContext(),
files.getPsiFiles(),
configuration.get(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
configuration.get(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
GenerationState.GenerateClassFilter.GENERATE_ALL,
configuration.get(JVMConfigurationKeys.DISABLE_INLINE, false),
configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
/* useTypeTableInSerializer = */ false,
configuration.get(JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS, false)
);
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION);
// For JVM-specific errors
AnalyzingUtils.throwExceptionOnErrors(state.getCollectedExtraJvmDiagnostics());
return state.getFactory();
}
public static void assertThrows(@NotNull Method foo, @NotNull Class<? extends Throwable> exceptionClass,
@Nullable Object instance, @NotNull Object... args) throws IllegalAccessException {
boolean caught = false;
try {
foo.invoke(instance, args);
}
catch (InvocationTargetException ex) {
caught = exceptionClass.isInstance(ex.getTargetException());
}
assertTrue(caught);
}
@NotNull
public static Method findDeclaredMethodByName(@NotNull Class<?> aClass, @NotNull String name) {
Method result = findDeclaredMethodByNameOrNull(aClass, name);
if (result == null) {
throw new AssertionError("Method " + name + " is not found in " + aClass);
}
return result;
}
public static Method findDeclaredMethodByNameOrNull(@NotNull Class<?> aClass, @NotNull String name) {
for (Method method : aClass.getDeclaredMethods()) {
if (method.getName().equals(name)) {
return method;
}
}
return null;
}
@NotNull
public static File compileJava(
@NotNull List<String> fileNames,
@NotNull List<String> additionalClasspath,
@NotNull List<String> additionalOptions
) {
try {
File javaClassesTempDirectory = KotlinTestUtils.tmpDir("java-classes");
List<String> classpath = new ArrayList<String>();
classpath.add(ForTestCompileRuntime.runtimeJarForTests().getPath());
classpath.add(ForTestCompileRuntime.reflectJarForTests().getPath());
classpath.add(KotlinTestUtils.getAnnotationsJar().getPath());
classpath.addAll(additionalClasspath);
List<String> options = new ArrayList<String>(Arrays.asList(
"-classpath", StringsKt.join(classpath, File.pathSeparator),
"-d", javaClassesTempDirectory.getPath()
));
options.addAll(additionalOptions);
List<File> fileList = CollectionsKt.map(fileNames, new Function1<String, File>() {
@Override
public File invoke(String input) {
return new File(input);
}
});
KotlinTestUtils.compileJavaFiles(fileList, options);
return javaClassesTempDirectory;
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
public static Method findTheOnlyMethod(@NotNull Class<?> aClass) {
Method r = null;
for (Method method : aClass.getMethods()) {
if (method.getDeclaringClass().equals(Object.class)) {
continue;
}
if (r != null) {
throw new AssertionError("More than one public method in class " + aClass);
}
r = method;
}
if (r == null) {
throw new AssertionError("No public methods in class " + aClass);
}
return r;
}
@Nullable
public static Object getAnnotationAttribute(@NotNull Object annotation, @NotNull String name) {
try {
return annotation.getClass().getMethod(name).invoke(annotation);
}
catch (Exception e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
}
@@ -0,0 +1,91 @@
/*
* 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.codegen;
import com.android.dx.cf.direct.DirectClassFile;
import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.command.dexer.Main;
import com.android.dx.dex.cf.CfTranslator;
import com.android.dx.dex.file.DexFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.junit.Assert;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DxChecker {
public static final boolean RUN_DX_CHECKER = true;
private static final Pattern STACK_TRACE_PATTERN = Pattern.compile("[\\s]+at .*");
private DxChecker() {
}
public static void check(ClassFileFactory outputFiles) {
Main.Arguments arguments = new Main.Arguments();
String[] array = new String[1];
array[0] = "testArgs";
arguments.parse(array);
for (OutputFile file : ClassFileUtilsKt.getClassFiles(outputFiles)) {
try {
byte[] bytes = file.asByteArray();
checkFileWithDx(bytes, file.getRelativePath(), arguments);
}
catch (Throwable e) {
Assert.fail(generateExceptionMessage(e));
}
}
}
public static void checkFileWithDx(byte[] bytes, @NotNull String relativePath) {
Main.Arguments arguments = new Main.Arguments();
String[] array = new String[1];
array[0] = "testArgs";
arguments.parse(array);
checkFileWithDx(bytes, relativePath, arguments);
}
private static void checkFileWithDx(byte[] bytes, @NotNull String relativePath, @NotNull Main.Arguments arguments) {
DirectClassFile cf = new DirectClassFile(bytes, relativePath, true);
cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
CfTranslator.translate(
cf,
bytes,
arguments.cfOptions,
arguments.dexOptions,
new DexFile(arguments.dexOptions)
);
}
private static String generateExceptionMessage(Throwable e) {
StringWriter writer = new StringWriter();
PrintWriter printWriter = new PrintWriter(writer);
try {
e.printStackTrace(printWriter);
String stackTrace = writer.toString();
Matcher matcher = STACK_TRACE_PATTERN.matcher(stackTrace);
return matcher.replaceAll("");
}
finally {
printWriter.close();
}
}
}
@@ -0,0 +1,119 @@
/*
* 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.codegen;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.config.CompilerConfigurationKey;
import org.jetbrains.kotlin.descriptors.PackagePartProvider;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import java.util.Collections;
import java.util.List;
public class GenerationUtils {
private GenerationUtils() {
}
@NotNull
public static ClassFileFactory compileFileGetClassFileFactoryForTest(
@NotNull KtFile psiFile,
@NotNull KotlinCoreEnvironment environment
) {
return compileFileGetGenerationStateForTest(psiFile, environment).getFactory();
}
@NotNull
public static GenerationState compileFileGetGenerationStateForTest(
@NotNull KtFile psiFile,
@NotNull KotlinCoreEnvironment environment
) {
AnalysisResult analysisResult =
JvmResolveUtil.analyzeOneFileWithJavaIntegrationAndCheckForErrors(psiFile, new JvmPackagePartProvider(environment));
return compileFilesGetGenerationState(psiFile.getProject(), analysisResult, Collections.singletonList(psiFile), false, null);
}
@NotNull
public static GenerationState compileManyFilesGetGenerationStateForTest(@NotNull Project project, @NotNull List<KtFile> files) {
return compileManyFilesGetGenerationStateForTest(project, files, PackagePartProvider.Companion.getEMPTY(), null);
}
@NotNull
public static GenerationState compileManyFilesGetGenerationStateForTest(
@NotNull Project project,
@NotNull List<KtFile> files,
@NotNull PackagePartProvider packagePartProvider,
@Nullable CompilerConfiguration configuration
) {
AnalysisResult analysisResult = JvmResolveUtil.analyzeFilesWithJavaIntegrationAndCheckForErrors(
project, files, packagePartProvider);
return compileFilesGetGenerationState(project, analysisResult, files, false, configuration);
}
@NotNull
public static GenerationState compileFilesGetGenerationState(
@NotNull Project project,
@NotNull AnalysisResult analysisResult,
@NotNull List<KtFile> files,
boolean useTypeTableInSerializer
) {
return compileFilesGetGenerationState(project, analysisResult, files, useTypeTableInSerializer, null);
}
@NotNull
public static GenerationState compileFilesGetGenerationState(
@NotNull Project project,
@NotNull AnalysisResult analysisResult,
@NotNull List<KtFile> files,
boolean useTypeTableInSerializer,
@Nullable CompilerConfiguration configuration
) {
analysisResult.throwIfError();
GenerationState state = new GenerationState(
project, ClassBuilderFactories.TEST,
analysisResult.getModuleDescriptor(), analysisResult.getBindingContext(),
files,
getConfigurationValueOrDefault(configuration, JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, false),
getConfigurationValueOrDefault(configuration, JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, false),
GenerationState.GenerateClassFilter.GENERATE_ALL,
getConfigurationValueOrDefault(configuration, JVMConfigurationKeys.DISABLE_INLINE, false),
getConfigurationValueOrDefault(configuration, JVMConfigurationKeys.DISABLE_OPTIMIZATION, false),
useTypeTableInSerializer,
getConfigurationValueOrDefault(configuration, JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS, false)
);
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION);
return state;
}
private static <T> T getConfigurationValueOrDefault(
@Nullable CompilerConfiguration configuration,
@NotNull CompilerConfigurationKey<T> key,
T defaultValue
) {
if (configuration == null) return defaultValue;
return configuration.get(key, defaultValue);
}
}
@@ -0,0 +1,211 @@
/*
* 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.codegen;
import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
object InlineTestUtil {
fun checkNoCallsToInline(files: Iterable<OutputFile>, sourceFiles: List<KtFile>) {
val inlineInfo = obtainInlineInfo(files)
val inlineMethods = inlineInfo.inlineMethods
assert(!inlineMethods.isEmpty()) { "There are no inline methods" }
val notInlinedCalls = checkInlineMethodNotInvoked(files, inlineMethods)
assert(notInlinedCalls.isEmpty()) { "All inline methods should be inlined but:\n" + notInlinedCalls.joinToString("\n") }
val skipParameterChecking = sourceFiles.any {
InTextDirectivesUtils.isDirectiveDefined(it.text, "NO_CHECK_LAMBDA_INLINING")
}
if (!skipParameterChecking) {
val notInlinedParameters = checkParametersInlined(files, inlineInfo)
assert(notInlinedParameters.isEmpty()) {
"All inline parameters should be inlined but:\n${notInlinedParameters.joinToString("\n")}\n" +
"but if you have not inlined lambdas or anonymous objects enable NO_CHECK_LAMBDA_INLINING directive"
}
}
}
private fun obtainInlineInfo(files: Iterable<OutputFile>): InlineInfo {
val inlineMethods = HashSet<MethodInfo>()
val binaryClasses = hashMapOf<String, KotlinJvmBinaryClass>()
for (file in files) {
val bytes = file.asByteArray()
val cr = ClassReader(bytes)
val inlineFunctions = inlineFunctionsJvmNames(bytes)
val classVisitor = object : ClassVisitorWithName() {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor {
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
override fun visitEnd() {
if (name + desc in inlineFunctions) {
inlineMethods.add(MethodInfo(className, name, this.desc))
}
}
}
}
}
cr.accept(classVisitor, 0)
binaryClasses.put(classVisitor.className, loadBinaryClass(file))
}
return InlineInfo(inlineMethods, binaryClasses)
}
private fun checkInlineMethodNotInvoked(files: Iterable<OutputFile>, inlinedMethods: Set<MethodInfo>): List<NotInlinedCall> {
val notInlined = ArrayList<NotInlinedCall>()
files.forEach { file ->
ClassReader(file.asByteArray()).accept(object : ClassVisitorWithName() {
private var skipMethodsOfThisClass = false
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
if (desc == JvmAnnotationNames.METADATA_DESC) {
return object : AnnotationVisitor(Opcodes.ASM5) {
override fun visit(name: String?, value: Any) {
if (name == JvmAnnotationNames.KIND_FIELD_NAME && value == KotlinClassHeader.Kind.MULTIFILE_CLASS.id) {
skipMethodsOfThisClass = true
}
}
}
}
return null
}
override fun visitMethod(access: Int, name: String, desc: String, signature: String, exceptions: Array<String>): MethodVisitor? {
if (skipMethodsOfThisClass) {
return null
}
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
val methodCall = MethodInfo(owner, name, desc)
if (inlinedMethods.contains(methodCall)) {
val fromCall = MethodInfo(className, this.name, this.desc)
//skip delegation to interface DefaultImpls from child class
if (methodCall.owner.endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX) && fromCall.owner != methodCall.owner) {
return
}
notInlined.add(NotInlinedCall(fromCall, methodCall))
}
}
}
}
}, 0)
}
return notInlined
}
private fun checkParametersInlined(files: Iterable<OutputFile>, inlineInfo: InlineInfo): ArrayList<NotInlinedParameter> {
val inlinedMethods = inlineInfo.inlineMethods
val notInlinedParameters = ArrayList<NotInlinedParameter>()
for (file in files) {
if (!isClassOrPackagePartKind(loadBinaryClass(file))) continue
ClassReader(file.asByteArray()).accept(object : ClassVisitorWithName() {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
val declaration = MethodInfo(className, name, desc)
//do not check anonymous object creation in inline functions and in package facades
if (declaration in inlinedMethods) {
return null
}
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
private fun isInlineParameterLikeOwner(owner: String) =
"$" in owner && !isTopLevelOrInnerOrPackageClass(owner, inlineInfo)
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
if ("<init>".equals(name) && isInlineParameterLikeOwner(owner)) {
val fromCall = MethodInfo(className, this.name, this.desc)
notInlinedParameters.add(NotInlinedParameter(owner, fromCall))
}
}
override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
if (opcode == Opcodes.GETSTATIC && isInlineParameterLikeOwner(owner)) {
val fromCall = MethodInfo(className, this.name, this.desc)
notInlinedParameters.add(NotInlinedParameter(owner, fromCall))
}
super.visitFieldInsn(opcode, owner, name, desc)
}
}
}
}, 0)
}
return notInlinedParameters
}
private fun isTopLevelOrInnerOrPackageClass(classInternalName: String, inlineInfo: InlineInfo): Boolean {
if (classInternalName.startsWith("kotlin/jvm/internal/"))
return true
return isClassOrPackagePartKind(inlineInfo.binaryClasses[classInternalName]!!)
}
private fun isClassOrPackagePartKind(klass: KotlinJvmBinaryClass): Boolean {
return klass.classHeader.kind == KotlinClassHeader.Kind.CLASS && !klass.classId.isLocal
}
private fun loadBinaryClass(file: OutputFile): KotlinJvmBinaryClass {
val klass = FileBasedKotlinClass.create(file.asByteArray()) {
className, classHeader, innerClasses ->
object : FileBasedKotlinClass(className, classHeader, innerClasses) {
override fun getLocation(): String = throw UnsupportedOperationException()
override fun getFileContents(): ByteArray = throw UnsupportedOperationException()
override fun hashCode(): Int = throw UnsupportedOperationException()
override fun equals(other: Any?): Boolean = throw UnsupportedOperationException()
override fun toString(): String = throw UnsupportedOperationException()
}
}!!
return klass
}
private class InlineInfo(val inlineMethods: Set<MethodInfo>, val binaryClasses: Map<String, KotlinJvmBinaryClass>)
private data class NotInlinedCall(val fromCall: MethodInfo, val inlineMethod: MethodInfo)
private data class NotInlinedParameter(val parameterClassName: String, val fromCall: MethodInfo)
private data class MethodInfo(val owner: String, val name: String, val desc: String)
private open class ClassVisitorWithName : ClassVisitor(Opcodes.ASM5) {
lateinit var className: String
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<String>?) {
className = name
super.visit(version, access, name, signature, superName, interfaces)
}
}
}
@@ -0,0 +1,94 @@
/*
* 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.codegen
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.junit.Assert
import java.io.File
import java.io.StringReader
object SMAPTestUtil {
private fun extractSMAPFromClasses(outputFiles: Iterable<OutputFile>): List<SMAPAndFile> {
return outputFiles.mapNotNull { outputFile ->
var debugInfo: String? = null
ClassReader(outputFile.asByteArray()).accept(object : ClassVisitor(Opcodes.ASM5) {
override fun visitSource(source: String?, debug: String?) {
debugInfo = debug
}
}, 0)
SMAPAndFile.SMAPAndFile(debugInfo, outputFile.sourceFiles.single())
}
}
private fun extractSmapFromTestDataFile(file: CodegenTestCase.TestFile): SMAPAndFile? {
if (!file.name.endsWith(".smap")) return null
val content = buildString {
StringReader(file.content).forEachLine { line ->
// Strip comments
if (!line.startsWith("//")) {
appendln(line.trim())
}
}
}.trim()
return SMAPAndFile(if (content.isNotEmpty()) content else null, SMAPAndFile.getPath(file.name))
}
fun checkSMAP(inputFiles: List<CodegenTestCase.TestFile>, outputFiles: Iterable<OutputFile>) {
if (!InlineCodegenUtil.GENERATE_SMAP) return
val sourceData = inputFiles.mapNotNull { extractSmapFromTestDataFile(it) }
val compiledData = extractSMAPFromClasses(outputFiles).groupBy {
it.sourceFile
}.map {
val smap = it.value.mapNotNull { it.smap }.joinToString("\n")
SMAPAndFile(if (smap.isNotEmpty()) smap else null, it.key)
}.associateBy { it.sourceFile }
for (source in sourceData) {
val ktFileName = "/" + source.sourceFile.replace(".smap", ".kt")
val data = compiledData[ktFileName]
Assert.assertEquals("Smap data differs for $ktFileName", normalize(source.smap), normalize(data?.smap))
}
}
private fun normalize(text: String?) =
text?.let { StringUtil.convertLineSeparators(it.trim()) }
private class SMAPAndFile(val smap: String?, val sourceFile: String) {
companion object {
fun SMAPAndFile(smap: String?, sourceFile: File) = SMAPAndFile(smap, getPath(sourceFile))
fun getPath(file: File): String {
return getPath(file.canonicalPath)
}
fun getPath(canonicalPath: String): String {
//There are some problems with disk name on windows cause LightVirtualFile return it without disk name
return FileUtil.toSystemIndependentName(canonicalPath).substringAfter(":")
}
}
}
}
@@ -0,0 +1,72 @@
/*
* 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.codegen.defaultConstructor;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.kotlin.codegen.CodegenTestCase;
import org.jetbrains.kotlin.test.ConfigurationKind;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.List;
import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findListWithPrefixes;
public abstract class AbstractDefaultArgumentsReflectionTest extends CodegenTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.JDK_ONLY);
}
@Override
protected void doTest(String path) throws IOException {
loadFileByFullPath(path);
String fileText = FileUtil.loadFile(new File(path), true);
String className = loadInstructionValue(fileText, "CLASS");
boolean hasDefaultConstructor = loadInstructionValue(fileText, "HAS_DEFAULT_CONSTRUCTOR").equals("true");
Class<?> aClass = generateClass(className);
assertNotNull("Cannot find class with name " + className, aClass);
try {
Constructor constructor = aClass.getDeclaredConstructor();
if (!hasDefaultConstructor) {
System.out.println(generateToText());
throw new AssertionError("Default constructor was found but it wasn't expected: " + constructor);
}
}
catch (NoSuchMethodException e) {
if (hasDefaultConstructor) {
System.out.println(generateToText());
throw new AssertionError("Cannot find default constructor");
}
}
catch (Throwable e) {
System.out.println(generateToText());
throw new RuntimeException(e);
}
}
private static String loadInstructionValue(String fileContent, String instructionName) {
List<String> testedClass = findListWithPrefixes(fileContent, "// " + instructionName + ": ");
assertTrue("Cannot find " + instructionName + " instruction", !testedClass.isEmpty());
assertTrue(instructionName + " instruction must have only one element", testedClass.size() == 1);
return testedClass.get(0);
}
}
@@ -0,0 +1,302 @@
/*
* 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.codegen.flags;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.GenerationUtils;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
import org.jetbrains.org.objectweb.asm.*;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findListWithPrefixes;
import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes;
/*
* Test correctness of written flags in class file
*
* TESTED_OBJECT_KIND - maybe class, function or property
* TESTED_OBJECTS - className, [function/property name]
* FLAGS - only flags which must be true (could be skipped if ABSENT is TRUE)
* ABSENT - true or false, optional (false by default)
*
* There is could be specified several tested objects separated by empty line, e.g:
* TESTED_OBJECT_KIND: property
* TESTED_OBJECTS: Test$object, prop
* ABSENT: TRUE
*
* TESTED_OBJECT_KIND: property
* TESTED_OBJECTS: Test, prop$delegate
* FLAGS: ACC_STATIC, ACC_FINAL, ACC_PRIVATE
*/
public abstract class AbstractWriteFlagsTest extends KtUsefulTestCase {
private KotlinCoreEnvironment jetCoreEnvironment;
@Override
public void setUp() throws Exception {
super.setUp();
jetCoreEnvironment = KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable, ConfigurationKind.JDK_ONLY);
}
@Override
protected void tearDown() throws Exception {
jetCoreEnvironment = null;
super.tearDown();
}
protected void doTest(String path) throws Exception {
File ktFile = new File(path);
assertTrue("Cannot find a file " + ktFile.getAbsolutePath(), ktFile.exists());
String fileText = FileUtil.loadFile(ktFile, true);
KtFile psiFile = KotlinTestUtils.createFile(ktFile.getName(), fileText, jetCoreEnvironment.getProject());
OutputFileCollection outputFiles = GenerationUtils.compileFileGetClassFileFactoryForTest(psiFile, jetCoreEnvironment);
List<TestedObject> testedObjects = parseExpectedTestedObject(fileText);
for (TestedObject testedObject : testedObjects) {
String className = null;
for (OutputFile outputFile : outputFiles.asList()) {
String filePath = outputFile.getRelativePath();
if (testedObject.isFullContainingClassName && filePath.equals(testedObject.containingClass + ".class")) {
className = filePath;
}
else if (!testedObject.isFullContainingClassName && filePath.startsWith(testedObject.containingClass)) {
className = filePath;
}
}
assertNotNull("Couldn't find a class file with name " + testedObject.containingClass, className);
OutputFile outputFile = outputFiles.get(className);
assertNotNull(outputFile);
ClassReader cr = new ClassReader(outputFile.asByteArray());
TestClassVisitor classVisitor = getClassVisitor(testedObject.kind, testedObject.name, false);
cr.accept(classVisitor, ClassReader.SKIP_CODE);
if (!classVisitor.isExists()) {
classVisitor = getClassVisitor(testedObject.kind, testedObject.name, true);
cr.accept(classVisitor, ClassReader.SKIP_CODE);
}
boolean isObjectExists = !Boolean.valueOf(findStringWithPrefixes(testedObject.textData, "// ABSENT: "));
assertEquals("Wrong object existence state: " + testedObject, isObjectExists, classVisitor.isExists());
if (isObjectExists) {
assertEquals("Wrong access flag for " + testedObject + " \n" + outputFile.asText(),
getExpectedFlags(testedObject.textData), classVisitor.getAccess());
}
}
}
private static List<TestedObject> parseExpectedTestedObject(String testDescription) {
String[] testObjectData = testDescription.substring(testDescription.indexOf("// TESTED_OBJECT_KIND")).split("\n\n");
ArrayList<TestedObject> objects = new ArrayList<TestedObject>();
for (String testData : testObjectData) {
if (testData.isEmpty()) continue;
TestedObject testObject = new TestedObject();
testObject.textData = testData;
List<String> testedObjects = findListWithPrefixes(testData, "// TESTED_OBJECTS: ");
assertTrue("Cannot find TESTED_OBJECTS instruction", !testedObjects.isEmpty());
testObject.containingClass = testedObjects.get(0);
if (testedObjects.size() == 1) {
testObject.name = testedObjects.get(0);
}
else if (testedObjects.size() == 2) {
testObject.name = testedObjects.get(1);
}
else {
throw new IllegalArgumentException(
"TESTED_OBJECTS instruction must contain one (for class) or two (for function and property) values");
}
testObject.kind = findStringWithPrefixes(testData, "// TESTED_OBJECT_KIND: ");
List<String> isFullName = findListWithPrefixes(testData, "// IS_FULL_CONTAINING_CLASS_NAME: ");
if (isFullName.size() == 1) {
testObject.isFullContainingClassName = Boolean.parseBoolean(isFullName.get(0));
}
objects.add(testObject);
}
assertTrue("Test description not present!", !objects.isEmpty());
return objects;
}
private static class TestedObject {
public String name;
public String containingClass = "";
public boolean isFullContainingClassName = true;
public String kind;
public String textData;
@Override
public String toString() {
return "Class = " + containingClass + ", name = " + name + ", kind = " + kind;
}
}
private static TestClassVisitor getClassVisitor(String visitorKind, String testedObjectName, boolean allowSynthetic) {
if (visitorKind.equals("class")) {
return new ClassFlagsVisitor();
}
else if (visitorKind.equals("function")) {
return new FunctionFlagsVisitor(testedObjectName, allowSynthetic);
}
else if (visitorKind.equals("property")) {
return new PropertyFlagsVisitor(testedObjectName);
}
else if (visitorKind.equals("innerClass")) {
return new InnerClassFlagsVisitor(testedObjectName);
}
throw new IllegalArgumentException("Value of TESTED_OBJECT_KIND is incorrect: " + visitorKind);
}
protected static abstract class TestClassVisitor extends ClassVisitor {
protected boolean isExists;
public TestClassVisitor() {
super(Opcodes.ASM5);
}
abstract public int getAccess();
public boolean isExists() {
return isExists;
}
}
private static int getExpectedFlags(String text) {
int expectedAccess = 0;
Class klass = Opcodes.class;
List<String> flags = findListWithPrefixes(text, "// FLAGS: ");
for (String flag : flags) {
try {
Field field = klass.getDeclaredField(flag);
expectedAccess |= field.getInt(klass);
}
catch (NoSuchFieldException e) {
throw new IllegalArgumentException("Cannot find " + flag + " field in Opcodes class", e);
}
catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot find " + flag + " field in Opcodes class", e);
}
}
return expectedAccess;
}
private static class ClassFlagsVisitor extends TestClassVisitor {
private int access = 0;
@Override
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
this.access = access;
isExists = true;
}
@Override
public int getAccess() {
return access;
}
}
private static class FunctionFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String funName;
private final boolean allowSynthetic;
public FunctionFlagsVisitor(String name, boolean allowSynthetic) {
funName = name;
this.allowSynthetic = allowSynthetic;
}
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
if (name.equals(funName)) {
if (!allowSynthetic && (access & Opcodes.ACC_SYNTHETIC) != 0) return null;
this.access = access;
isExists = true;
}
return null;
}
@Override
public int getAccess() {
return access;
}
}
private static class PropertyFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String propertyName;
public PropertyFlagsVisitor(String name) {
propertyName = name;
}
@Override
public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) {
if (name.equals(propertyName)) {
this.access = access;
isExists = true;
}
return null;
}
@Override
public int getAccess() {
return access;
}
}
private static class InnerClassFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String innerClassName;
public InnerClassFlagsVisitor(String name) {
innerClassName = name;
}
@Override
public void visitInnerClass(@NotNull String innerClassInternalName, String outerClassInternalName, String name, int access) {
if (name.equals(innerClassName)) {
this.access = access;
isExists = true;
}
}
@Override
public int getAccess() {
return access;
}
}
}
@@ -0,0 +1,95 @@
/*
* 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.codegen.forTestCompile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class ForTestCompileRuntime {
private static volatile SoftReference<ClassLoader> reflectJarClassLoader = new SoftReference<ClassLoader>(null);
private static volatile SoftReference<ClassLoader> runtimeJarClassLoader = new SoftReference<ClassLoader>(null);
@NotNull
public static File runtimeJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-runtime.jar"));
}
@NotNull
public static File mockRuntimeJarForTests() {
return assertExists(new File("dist/kotlin-mock-runtime-for-test.jar"));
}
@NotNull
public static File kotlinTestJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-test.jar"));
}
@NotNull
public static File reflectJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-reflect.jar"));
}
@NotNull
private static File assertExists(@NotNull File file) {
if (!file.exists()) {
throw new IllegalStateException(file + " does not exist. Run 'ant dist'");
}
return file;
}
@NotNull
public static synchronized ClassLoader runtimeAndReflectJarClassLoader() {
ClassLoader loader = reflectJarClassLoader.get();
if (loader == null) {
loader = createClassLoader(runtimeJarForTests(), reflectJarForTests(), kotlinTestJarForTests());
reflectJarClassLoader = new SoftReference<ClassLoader>(loader);
}
return loader;
}
@NotNull
public static synchronized ClassLoader runtimeJarClassLoader() {
ClassLoader loader = runtimeJarClassLoader.get();
if (loader == null) {
loader = createClassLoader(runtimeJarForTests());
runtimeJarClassLoader = new SoftReference<ClassLoader>(loader);
}
return loader;
}
@NotNull
private static ClassLoader createClassLoader(@NotNull File... files) {
try {
List<URL> urls = new ArrayList<URL>(2);
for (File file : files) {
urls.add(file.toURI().toURL());
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]), null);
}
catch (MalformedURLException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
}
@@ -0,0 +1,88 @@
/*
* 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.daemon
/**
* holder of a [regex] and optional [matchCheck] for additional checks on match result
*/
class LinePattern(val regex: Regex, val matchCheck: (MatchResult) -> Boolean = { true })
fun LinePattern(regex: String, matchCheck: (MatchResult) -> Boolean = { true }) = LinePattern(regex.toRegex(), matchCheck)
/**
* calls [body] if receiver does not contain complete sequence of lines matched by [patternsIter], separated by any number of other lines
* [body] receives first unmatched pattern and index of last matched line in the sequence
*/
fun Sequence<String>.ifNotContainsSequence(patternsIter: Iterator<LinePattern>,
body: (LinePattern, Int) -> Unit) : Unit {
class Accumulator(it: Iterator<LinePattern>) {
val iter = EndBoundIteratorWithValue(it)
var lineNo = 1
var lastMatchedLineNo = 0
fun nextLineAndPattern(): Accumulator { iter.traverseNext(); lastMatchedLineNo = lineNo; return nextLine() }
fun nextLine(): Accumulator { lineNo++; return this }
}
val res = fold(Accumulator(patternsIter))
{ acc, line ->
when {
!acc.iter.isValid() -> return@fold acc
acc.iter.value.regex.find(line)?.let { acc.iter.value.matchCheck(it) } ?: false -> acc.nextLineAndPattern()
else -> acc.nextLine()
}
}
if (res.iter.isValid()) {
body(res.iter.value, res.lastMatchedLineNo)
}
}
/**
* calls [body] if receiver does not contain complete sequence of lines matched by [patterns], separated by any number of other lines
* [body] receives first unmatched pattern and index of last matched line in the sequence
*/
fun Sequence<String>.ifNotContainsSequence(patterns: List<LinePattern>,
body: (LinePattern, Int) -> Unit): Unit {
ifNotContainsSequence(patterns.iterator(), body)
}
/**
* calls [body] if receiver does not contain complete sequence of lines matched by [patterns], separated by any number of other lines
* [body] receives first unmatched pattern and index of last matched line in the sequence
*/
fun Sequence<String>.ifNotContainsSequence(vararg patterns: LinePattern,
body: (LinePattern, Int) -> Unit): Unit {
ifNotContainsSequence(patterns.iterator(), body)
}
// emulates Stepanov's / STL iterator, but with "embedded" end check via isValid:
// iterator points to a current value and upon init points to the first element or is invalid
// allows to express some algorithms more concisely
private class EndBoundIteratorWithValue<T: Any, Iter: Iterator<T>>(val base: Iter) {
private var _value: T? = base.nextOrNull()
val value: T get() = _value ?: throw Exception("Dereferencing invalid iterator")
fun isValid(): Boolean = _value != null
fun traverseNext(): EndBoundIteratorWithValue<T, Iter> {
_value = base.nextOrNull()
return this
}
}
private fun<T: Any> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null
@@ -0,0 +1,52 @@
/*
* 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.integration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import java.io.File;
public abstract class AbstractAntTaskTest extends KotlinIntegrationTestBase {
protected void doTest(String testFile) throws Exception {
String testDataDir = new File(testFile).getAbsolutePath();
runJava(
testDataDir,
"build.log",
"-jar", getAntHome() + File.separator + "lib" + File.separator + "ant-launcher.jar",
"-Dkotlin.lib=" + KotlinIntegrationTestBase.getCompilerLib(),
"-Dkotlin.runtime.jar=" + ForTestCompileRuntime.runtimeJarForTests().getAbsolutePath(),
"-Dkotlin.reflect.jar=" + ForTestCompileRuntime.reflectJarForTests().getAbsolutePath(),
"-Dtest.data=" + testDataDir,
"-Dtemp=" + tmpdir,
"-f", "build.xml"
);
}
@Override
@NotNull
protected String normalizeOutput(@NotNull File testDataDir, @NotNull String content) {
return super.normalizeOutput(testDataDir, content)
.replaceAll("Total time: .+\n", "Total time: [time]\n");
}
@NotNull
private static String getAntHome() {
return KotlinIntegrationTestBase.getKotlinProjectHome().getAbsolutePath() + File.separator + "dependencies" + File.separator + "ant-1.8";
}
}
@@ -0,0 +1,173 @@
/*
* 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.integration;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import kotlin.jvm.functions.Function1;
import kotlin.text.MatchResult;
import kotlin.text.Regex;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.KotlinVersion;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
import org.jetbrains.kotlin.utils.PathUtil;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
public abstract class KotlinIntegrationTestBase extends TestCaseWithTmpdir {
static {
System.setProperty("java.awt.headless", "true");
}
protected int runJava(@NotNull String testDataDir, @Nullable String logName, @NotNull String... arguments) throws Exception {
GeneralCommandLine commandLine = new GeneralCommandLine().withWorkDirectory(testDataDir);
commandLine.setExePath(getJavaRuntime().getAbsolutePath());
commandLine.addParameters(arguments);
StringBuilder executionLog = new StringBuilder();
int exitCode = runProcess(commandLine, executionLog);
if (logName == null) {
assertEquals("Non-zero exit code", 0, exitCode);
}
else {
check(testDataDir, logName, executionLog.toString());
}
return exitCode;
}
private static String normalizePath(String content, File baseDir, String pathId) {
String contentWithRelativePaths = content.replace(baseDir.getAbsolutePath(), pathId);
@Language("RegExp")
String RELATIVE_PATH_WITH_MIXED_SEPARATOR = Regex.Companion.escape(pathId) + "[-.\\w/\\\\]*";
return new Regex(RELATIVE_PATH_WITH_MIXED_SEPARATOR).replace(contentWithRelativePaths, new Function1<MatchResult, String>() {
@Override
public String invoke(MatchResult mr) {
return FileUtil.toSystemIndependentName(mr.getValue());
}
});
}
@NotNull
protected String normalizeOutput(@NotNull File testDataDir, @NotNull String content) {
content = normalizePath(content, testDataDir, "[TestData]");
content = normalizePath(content, tmpdir, "[Temp]");
content = normalizePath(content, getCompilerLib(), "[CompilerLib]");
content = normalizePath(content, getKotlinProjectHome(), "[KotlinProjectHome]");
content = content.replaceAll(Pattern.quote(KotlinVersion.VERSION), "[KotlinVersion]");
content = StringUtil.convertLineSeparators(content);
return content;
}
private void check(String testDataDir, String baseName, String content) throws IOException {
File expectedFile = new File(testDataDir, baseName + ".expected");
String normalizedContent = normalizeOutput(new File(testDataDir), content);
KotlinTestUtils.assertEqualsToFile(expectedFile, normalizedContent);
}
private static int runProcess(GeneralCommandLine commandLine, StringBuilder executionLog) throws ExecutionException {
OSProcessHandler handler =
new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString(), commandLine.getCharset());
StringBuilder outContent = new StringBuilder();
StringBuilder errContent = new StringBuilder();
handler.addProcessListener(new OutputListener(outContent, errContent));
handler.startNotify();
handler.waitFor();
int exitCode = handler.getProcess().exitValue();
appendIfNotEmpty(executionLog, "OUT:\n", outContent.toString());
appendIfNotEmpty(executionLog, "\nERR:\n", errContent.toString());
executionLog.append("\nReturn code: ").append(exitCode).append("\n");
return exitCode;
}
private static void appendIfNotEmpty(StringBuilder executionLog, String prefix, String content) {
if (content.length() > 0) {
executionLog.append(prefix);
executionLog.append(content);
}
}
private static File getJavaRuntime() {
File javaHome = new File(System.getProperty("java.home"));
String javaExe = SystemInfo.isWindows ? "java.exe" : "java";
File runtime = new File(javaHome, "bin" + File.separator + javaExe);
assertTrue("No java runtime at " + runtime, runtime.isFile());
return runtime;
}
public static File getCompilerLib() {
File file = PathUtil.getKotlinPathsForDistDirectory().getLibPath().getAbsoluteFile();
assertTrue("Lib directory doesn't exist. Run 'ant dist'", file.isDirectory());
return file;
}
protected static File getKotlinProjectHome() {
return new File(PathManager.getHomePath()).getParentFile();
}
private static class OutputListener extends ProcessAdapter {
private final StringBuilder out;
private final StringBuilder err;
public OutputListener(@NotNull StringBuilder out, @NotNull StringBuilder err) {
this.out = out;
this.err = err;
}
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
if (outputType == ProcessOutputTypes.STDERR) {
err.append(event.getText());
}
else if (outputType == ProcessOutputTypes.SYSTEM) {
// skip
}
else {
out.append(event.getText());
}
}
@Override
public void processTerminated(ProcessEvent event) {}
}
}
@@ -0,0 +1,299 @@
/*
* 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.jvm.compiler;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import junit.framework.ComparisonFailure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
import org.jetbrains.kotlin.cli.jvm.config.ModuleNameKt;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.config.ContentRootsKt;
import org.jetbrains.kotlin.context.ModuleContext;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode;
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.test.util.DescriptorValidator;
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator;
import org.junit.Assert;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil.*;
import static org.jetbrains.kotlin.test.KotlinTestUtils.*;
import static org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesAllowed;
import static org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesForbidden;
import static org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.*;
/*
The generated test compares package descriptors loaded from kotlin sources and read from compiled java.
*/
public abstract class AbstractLoadJavaTest extends TestCaseWithTmpdir {
protected void doTestCompiledJava(@NotNull String javaFileName) throws Exception {
doTestCompiledJava(javaFileName, DONT_INCLUDE_METHODS_OF_OBJECT);
}
// Java-Kotlin dependencies are not supported in this method for simplicity
protected void doTestCompiledJavaAndKotlin(@NotNull String expectedFileName) throws Exception {
File expectedFile = new File(expectedFileName);
File sourcesDir = new File(expectedFileName.replaceFirst("\\.txt$", ""));
List<File> kotlinSources = FileUtil.findFilesByMask(Pattern.compile(".+\\.kt"), sourcesDir);
compileKotlinToDirAndGetAnalysisResult(kotlinSources, tmpdir, myTestRootDisposable, ConfigurationKind.JDK_ONLY, false);
List<File> javaSources = FileUtil.findFilesByMask(Pattern.compile(".+\\.java"), sourcesDir);
Pair<PackageViewDescriptor, BindingContext> binaryPackageAndContext = compileJavaAndLoadTestPackageAndBindingContextFromBinary(
javaSources, tmpdir, ConfigurationKind.JDK_ONLY
);
checkJavaPackage(expectedFile, binaryPackageAndContext.first, binaryPackageAndContext.second, DONT_INCLUDE_METHODS_OF_OBJECT);
}
protected void doTestCompiledJavaIncludeObjectMethods(@NotNull String javaFileName) throws Exception {
doTestCompiledJava(javaFileName, RECURSIVE);
}
protected void doTestCompiledKotlin(@NotNull String ktFileName) throws Exception {
doTestCompiledKotlin(ktFileName, ConfigurationKind.JDK_ONLY, false);
}
protected void doTestCompiledKotlinWithTypeTable(@NotNull String ktFileName) throws Exception {
doTestCompiledKotlin(ktFileName, ConfigurationKind.JDK_ONLY, true);
}
protected void doTestCompiledKotlinWithStdlib(@NotNull String ktFileName) throws Exception {
doTestCompiledKotlin(ktFileName, ConfigurationKind.ALL, false);
}
private void doTestCompiledKotlin(
@NotNull String ktFileName, @NotNull ConfigurationKind configurationKind, boolean useTypeTableInSerializer
) throws Exception {
File ktFile = new File(ktFileName);
File txtFile = new File(ktFileName.replaceFirst("\\.kt$", ".txt"));
AnalysisResult result = compileKotlinToDirAndGetAnalysisResult(
Collections.singletonList(ktFile), tmpdir, getTestRootDisposable(), configurationKind, useTypeTableInSerializer
);
PackageViewDescriptor packageFromSource = result.getModuleDescriptor().getPackage(TEST_PACKAGE_FQNAME);
Assert.assertEquals("test", packageFromSource.getName().asString());
PackageViewDescriptor packageFromBinary = LoadDescriptorUtil.loadTestPackageAndBindingContextFromJavaRoot(
tmpdir, getTestRootDisposable(), getJdkKind(), configurationKind, true
).first;
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(packageFromBinary.getMemberScope())) {
if (descriptor instanceof ClassDescriptor) {
assert descriptor instanceof DeserializedClassDescriptor : DescriptorUtils.getFqName(descriptor) + " is loaded as " + descriptor.getClass();
}
}
DescriptorValidator.validate(errorTypesForbidden(), packageFromSource);
DescriptorValidator.validate(new DeserializedScopeValidationVisitor(), packageFromBinary);
Configuration configuration = RecursiveDescriptorComparator.DONT_INCLUDE_METHODS_OF_OBJECT
.checkPrimaryConstructors(true)
.checkPropertyAccessors(true);
compareDescriptors(packageFromSource, packageFromBinary, configuration, txtFile);
}
protected void doTestJavaAgainstKotlin(String expectedFileName) throws Exception {
File expectedFile = new File(expectedFileName);
File sourcesDir = new File(expectedFileName.replaceFirst("\\.txt$", ""));
FileUtil.copyDir(sourcesDir, new File(tmpdir, "test"), new FileFilter() {
@Override
public boolean accept(@NotNull File pathname) {
return pathname.getName().endsWith(".java");
}
});
CompilerConfiguration configuration = KotlinTestUtils.compilerConfigurationForTests(
ConfigurationKind.JDK_ONLY, getJdkKind());
ContentRootsKt.addKotlinSourceRoot(configuration, sourcesDir.getAbsolutePath());
JvmContentRootsKt.addJavaSourceRoot(configuration, new File("compiler/testData/loadJava/include"));
JvmContentRootsKt.addJavaSourceRoot(configuration, tmpdir);
KotlinCoreEnvironment environment =
KotlinCoreEnvironment.createForTests(getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
BindingTrace trace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.getProject(),
ModuleNameKt.getModuleName(environment));
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationNoIncremental(
moduleContext,
environment.getSourceFiles(),
trace,
TopDownAnalysisMode.TopLevelDeclarations,
new JvmPackagePartProvider(environment)
);
PackageViewDescriptor packageView = moduleContext.getModule().getPackage(TEST_PACKAGE_FQNAME);
checkJavaPackage(expectedFile, packageView, trace.getBindingContext(), DONT_INCLUDE_METHODS_OF_OBJECT);
}
// TODO: add more tests on inherited parameter names, but currently impossible because of KT-4509
protected void doTestKotlinAgainstCompiledJavaWithKotlin(@NotNull String expectedFileName) throws Exception {
File kotlinSrc = new File(expectedFileName);
File librarySrc = new File(expectedFileName.replaceFirst("\\.kt$", ""));
File expectedFile = new File(expectedFileName.replaceFirst("\\.kt$", ".txt"));
File libraryOut = new File(tmpdir, "libraryOut");
compileKotlinWithJava(
FileUtil.findFilesByMask(Pattern.compile(".+\\.java$"), librarySrc),
FileUtil.findFilesByMask(Pattern.compile(".+\\.kt$"), librarySrc),
libraryOut,
getTestRootDisposable(),
null
);
KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(),
compilerConfigurationForTests(ConfigurationKind.JDK_ONLY, getJdkKind(), getAnnotationsJar(), libraryOut),
EnvironmentConfigFiles.JVM_CONFIG_FILES);
KtFile jetFile = KotlinTestUtils.createFile(kotlinSrc.getPath(), FileUtil.loadFile(kotlinSrc, true), environment.getProject());
AnalysisResult result = JvmResolveUtil.analyzeFilesWithJavaIntegrationAndCheckForErrors(
environment.getProject(), Collections.singleton(jetFile)
);
PackageViewDescriptor packageView = result.getModuleDescriptor().getPackage(TEST_PACKAGE_FQNAME);
assertFalse(packageView.isEmpty());
validateAndCompareDescriptorWithFile(packageView, DONT_INCLUDE_METHODS_OF_OBJECT.withValidationStrategy(
new DeserializedScopeValidationVisitor()
), expectedFile);
}
@NotNull
protected TestJdkKind getJdkKind() {
return TestJdkKind.MOCK_JDK;
}
protected void doTestSourceJava(@NotNull String javaFileName) throws Exception {
File originalJavaFile = new File(javaFileName);
File expectedFile = getTxtFile(javaFileName);
File testPackageDir = new File(tmpdir, "test");
assertTrue(testPackageDir.mkdir());
FileUtil.copy(originalJavaFile, new File(testPackageDir, originalJavaFile.getName()));
Pair<PackageViewDescriptor, BindingContext> javaPackageAndContext = loadTestPackageAndBindingContextFromJavaRoot(
tmpdir, getTestRootDisposable(), getJdkKind(), ConfigurationKind.JDK_ONLY, false
);
checkJavaPackage(expectedFile, javaPackageAndContext.first, javaPackageAndContext.second,
DONT_INCLUDE_METHODS_OF_OBJECT.withValidationStrategy(errorTypesAllowed()));
}
private void doTestCompiledJava(@NotNull String javaFileName, Configuration configuration) throws Exception {
final File srcDir = new File(tmpdir, "src");
File compiledDir = new File(tmpdir, "compiled");
assertTrue(srcDir.mkdir());
assertTrue(compiledDir.mkdir());
List<File> srcFiles = KotlinTestUtils.createTestFiles(
new File(javaFileName).getName(), FileUtil.loadFile(new File(javaFileName), true),
new TestFileFactoryNoModules<File>() {
@NotNull
@Override
public File create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
File targetFile = new File(srcDir, fileName);
try {
FileUtil.writeToFile(targetFile, text);
}
catch (IOException e) {
throw new AssertionError(e);
}
return targetFile;
}
});
Pair<PackageViewDescriptor, BindingContext> javaPackageAndContext = compileJavaAndLoadTestPackageAndBindingContextFromBinary(
srcFiles, compiledDir, ConfigurationKind.ALL
);
checkJavaPackage(getTxtFile(javaFileName), javaPackageAndContext.first, javaPackageAndContext.second, configuration);
}
@NotNull
private Pair<PackageViewDescriptor, BindingContext> compileJavaAndLoadTestPackageAndBindingContextFromBinary(
@NotNull Collection<File> javaFiles,
@NotNull File outDir,
@NotNull ConfigurationKind configurationKind
) throws IOException {
compileJavaWithAnnotationsJar(javaFiles, outDir);
return loadTestPackageAndBindingContextFromJavaRoot(outDir, myTestRootDisposable, getJdkKind(), configurationKind, true);
}
private static void checkJavaPackage(
File txtFile,
PackageViewDescriptor javaPackage,
BindingContext bindingContext,
Configuration configuration
) {
boolean fail = false;
try {
ExpectedLoadErrorsUtil.checkForLoadErrors(javaPackage, bindingContext);
}
catch (ComparisonFailure e) {
// to let the next check run even if this one failed
System.err.println("Expected: " + e.getExpected());
System.err.println("Actual : " + e.getActual());
e.printStackTrace();
fail = true;
}
catch (AssertionError e) {
e.printStackTrace();
fail = true;
}
validateAndCompareDescriptorWithFile(javaPackage, configuration, txtFile);
if (fail) {
fail("See error above");
}
}
private static File getTxtFile(String javaFileName) {
return new File(javaFileName.replaceFirst("\\.java$", ".txt"));
}
}
@@ -0,0 +1,53 @@
/*
* 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.jvm.compiler
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.resolve.MemberComparator
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberScope
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.kotlin.test.util.DescriptorValidator
import org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor
class DeserializedScopeValidationVisitor : ValidationVisitor() {
override fun validateScope(scopeOwner: DeclarationDescriptor, scope: MemberScope, collector: DescriptorValidator.DiagnosticCollector) {
super.validateScope(scopeOwner, scope, collector)
validateDeserializedScope(scopeOwner, scope)
}
}
private fun validateDeserializedScope(scopeOwner: DeclarationDescriptor, scope: MemberScope) {
val isPackageViewScope = scopeOwner is PackageViewDescriptor
if (scope is DeserializedMemberScope || isPackageViewScope) {
val relevantDescriptors = scope.getContributedDescriptors().filter { member ->
member is CallableMemberDescriptor && member.kind.isReal || (!isPackageViewScope && member is ClassDescriptor)
}
checkSorted(relevantDescriptors, scopeOwner)
}
}
private fun checkSorted(descriptors: Collection<DeclarationDescriptor>, declaration: DeclarationDescriptor) {
KtUsefulTestCase.assertOrderedEquals(
"Members of $declaration should be sorted by serialization.",
descriptors,
descriptors.sortedWith(MemberComparator.INSTANCE)
)
}
@@ -0,0 +1,135 @@
/*
* 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.jvm.compiler;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies;
import org.jetbrains.kotlin.load.java.JavaBindingContext;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import java.util.*;
import static org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase.assertNotNull;
import static org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase.assertSameElements;
public class ExpectedLoadErrorsUtil {
public static final String ANNOTATION_CLASS_NAME = "org.jetbrains.kotlin.jvm.compiler.annotation.ExpectLoadError";
public static void checkForLoadErrors(
@NotNull PackageViewDescriptor packageFromJava,
@NotNull BindingContext bindingContext
) {
Map<SourceElement, List<String>> expectedErrors = getExpectedLoadErrors(packageFromJava);
Map<SourceElement, List<String>> actualErrors = getActualLoadErrors(bindingContext);
for (SourceElement source : ContainerUtil.union(expectedErrors.keySet(), actualErrors.keySet())) {
List<String> actual = actualErrors.get(source);
List<String> expected = expectedErrors.get(source);
assertNotNull("Unexpected load error(s):\n" + actual + "\ncontainer:" + source, expected);
assertNotNull("Missing load error(s):\n" + expected + "\ncontainer:" + source, actual);
assertSameElements("Unexpected/missing load error(s)\ncontainer:" + source, actual, expected);
}
}
private static Map<SourceElement, List<String>> getExpectedLoadErrors(@NotNull PackageViewDescriptor packageFromJava) {
final Map<SourceElement, List<String>> map = new HashMap<SourceElement, List<String>>();
packageFromJava.acceptVoid(new DeclarationDescriptorVisitorEmptyBodies<Void, Void>() {
@Override
public Void visitPackageViewDescriptor(PackageViewDescriptor descriptor, Void data) {
return visitDeclarationRecursively(descriptor, descriptor.getMemberScope());
}
@Override
public Void visitClassDescriptor(ClassDescriptor descriptor, Void data) {
return visitDeclarationRecursively(descriptor, descriptor.getDefaultType().getMemberScope());
}
@Override
public Void visitFunctionDescriptor(FunctionDescriptor descriptor, Void data) {
return visitDeclaration(descriptor);
}
@Override
public Void visitPropertyDescriptor(PropertyDescriptor descriptor, Void data) {
return visitDeclaration(descriptor);
}
private Void visitDeclaration(@NotNull DeclarationDescriptor descriptor) {
AnnotationDescriptor annotation = descriptor.getAnnotations().findAnnotation(new FqName(ANNOTATION_CLASS_NAME));
if (annotation == null) return null;
// we expect exactly one annotation argument
ConstantValue<?> argument = annotation.getAllValueArguments().values().iterator().next();
String error = (String) argument.getValue();
//noinspection ConstantConditions
List<String> errors = Arrays.asList(error.split("\\|"));
putError(map, descriptor, errors);
return null;
}
private Void visitDeclarationRecursively(@NotNull DeclarationDescriptor descriptor, @NotNull MemberScope memberScope) {
for (DeclarationDescriptor member : DescriptorUtils.getAllDescriptors(memberScope)) {
member.acceptVoid(this);
}
return visitDeclaration(descriptor);
}
});
return map;
}
private static Map<SourceElement, List<String>> getActualLoadErrors(@NotNull BindingContext bindingContext) {
Map<SourceElement, List<String>> result = new HashMap<SourceElement, List<String>>();
Collection<DeclarationDescriptor> descriptors = bindingContext.getKeys(JavaBindingContext.LOAD_FROM_JAVA_SIGNATURE_ERRORS);
for (DeclarationDescriptor descriptor : descriptors) {
List<String> errors = bindingContext.get(JavaBindingContext.LOAD_FROM_JAVA_SIGNATURE_ERRORS, descriptor);
if (errors == null) continue;
putError(result, descriptor, errors);
}
return result;
}
private static void putError(
@NotNull Map<SourceElement, List<String>> result,
@NotNull DeclarationDescriptor descriptor,
@NotNull List<String> errors
) {
assert descriptor.getOriginal() instanceof DeclarationDescriptorWithSource
: "Signature errors should be reported only on declarations with source, but " + descriptor + " found";
result.put(((DeclarationDescriptorWithSource) descriptor.getOriginal()).getSource(), errors);
}
private ExpectedLoadErrorsUtil() {
}
}
@@ -0,0 +1,170 @@
/*
* 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.jvm.compiler;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.GenerationUtils;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.resolve.lazy.LazyResolveTestUtil;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static org.jetbrains.kotlin.test.KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations;
public final class LoadDescriptorUtil {
@NotNull
public static final FqName TEST_PACKAGE_FQNAME = FqName.topLevel(Name.identifier("test"));
private LoadDescriptorUtil() {
}
@NotNull
public static AnalysisResult compileKotlinToDirAndGetAnalysisResult(
@NotNull List<File> kotlinFiles,
@NotNull File outDir,
@NotNull Disposable disposable,
@NotNull ConfigurationKind configurationKind,
boolean useTypeTableInSerializer
) {
KtFilesAndAnalysisResult
filesAndResult = KtFilesAndAnalysisResult.createJetFilesAndAnalyze(kotlinFiles, disposable, configurationKind);
AnalysisResult result = filesAndResult.getAnalysisResult();
List<KtFile> files = filesAndResult.getKtFiles();
GenerationState state = GenerationUtils.compileFilesGetGenerationState(
files.get(0).getProject(), result, files, useTypeTableInSerializer
);
OutputUtilsKt.writeAllTo(state.getFactory(), outDir);
return result;
}
@NotNull
public static Pair<PackageViewDescriptor, BindingContext> loadTestPackageAndBindingContextFromJavaRoot(
@NotNull File javaRoot,
@NotNull Disposable disposable,
@NotNull TestJdkKind testJdkKind,
@NotNull ConfigurationKind configurationKind,
boolean isBinaryRoot
) {
List<File> javaBinaryRoots = new ArrayList<File>();
javaBinaryRoots.add(KotlinTestUtils.getAnnotationsJar());
List<File> javaSourceRoots = new ArrayList<File>();
javaSourceRoots.add(new File("compiler/testData/loadJava/include"));
if (isBinaryRoot) {
javaBinaryRoots.add(javaRoot);
}
else {
javaSourceRoots.add(javaRoot);
}
CompilerConfiguration configuration = KotlinTestUtils.compilerConfigurationForTests(
configurationKind,
testJdkKind,
javaBinaryRoots,
javaSourceRoots
);
KotlinCoreEnvironment environment =
KotlinCoreEnvironment.createForTests(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
BindingTrace trace = new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace();
ModuleDescriptor module = LazyResolveTestUtil
.resolve(environment.getProject(), trace, Collections.<KtFile>emptyList(), environment);
PackageViewDescriptor packageView = module.getPackage(TEST_PACKAGE_FQNAME);
return Pair.create(packageView, trace.getBindingContext());
}
public static void compileJavaWithAnnotationsJar(@NotNull Collection<File> javaFiles, @NotNull File outDir) throws IOException {
String classPath = ForTestCompileRuntime.runtimeJarForTests() + File.pathSeparator +
KotlinTestUtils.getAnnotationsJar().getPath();
KotlinTestUtils.compileJavaFiles(javaFiles, Arrays.asList(
"-classpath", classPath,
"-sourcepath", "compiler/testData/loadJava/include",
"-d", outDir.getPath()
));
}
private static class KtFilesAndAnalysisResult {
@NotNull
public static KtFilesAndAnalysisResult createJetFilesAndAnalyze(
@NotNull List<File> kotlinFiles,
@NotNull Disposable disposable,
@NotNull ConfigurationKind configurationKind
) {
final KotlinCoreEnvironment jetCoreEnvironment = createEnvironmentWithMockJdkAndIdeaAnnotations(disposable, configurationKind);
List<KtFile> jetFiles = ContainerUtil.map(kotlinFiles, new Function<File, KtFile>() {
@Override
public KtFile fun(File kotlinFile) {
try {
return KotlinTestUtils.createFile(
kotlinFile.getName(), FileUtil.loadFile(kotlinFile, true), jetCoreEnvironment.getProject());
}
catch (IOException e) {
throw new AssertionError(e);
}
}
});
AnalysisResult result = JvmResolveUtil.analyzeFilesWithJavaIntegrationAndCheckForErrors(
jetCoreEnvironment.getProject(), jetFiles, new JvmPackagePartProvider(jetCoreEnvironment));
return new KtFilesAndAnalysisResult(jetFiles, result);
}
private final List<KtFile> ktFiles;
private final AnalysisResult result;
private KtFilesAndAnalysisResult(@NotNull List<KtFile> ktFiles, @NotNull AnalysisResult result) {
this.ktFiles = ktFiles;
this.result = result;
}
@NotNull
public List<KtFile> getKtFiles() {
return ktFiles;
}
@NotNull
public AnalysisResult getAnalysisResult() {
return result;
}
}
}
@@ -0,0 +1,245 @@
/*
* 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.jvm.runtime
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAllTo
import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.jvm.compiler.ExpectedLoadErrorsUtil
import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil
import org.jetbrains.kotlin.load.java.ANNOTATIONS_COPIED_TO_TYPES
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.structure.reflect.classId
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.load.kotlin.reflect.ReflectKotlinClass
import org.jetbrains.kotlin.load.kotlin.reflect.RuntimeModuleData
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
import org.jetbrains.kotlin.renderer.OverrideRenderingPolicy
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.test.*
import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactoryNoModules
import org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesForbidden
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.Configuration
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.kotlin.utils.sure
import java.io.File
import java.net.URLClassLoader
import java.util.*
import java.util.regex.Pattern
abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdir() {
companion object {
private val renderer = DescriptorRenderer.withOptions {
withDefinedIn = false
excludedAnnotationClasses = (listOf(
FqName(ExpectedLoadErrorsUtil.ANNOTATION_CLASS_NAME)
) + ANNOTATIONS_COPIED_TO_TYPES).toSet()
overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OPEN_OVERRIDE
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
includePropertyConstant = false
verbose = true
renderDefaultAnnotationArguments = true
modifiers = DescriptorRendererModifier.ALL
}
}
protected open val defaultJdkKind: TestJdkKind = TestJdkKind.MOCK_JDK
// NOTE: this test does a dirty hack of text substitution to make all annotations defined in source code retain at runtime.
// Specifically each @interface in Java sources is extended by @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
// Also type related annotations are removed from Java because they are invisible at runtime
protected fun doTest(fileName: String) {
val file = File(fileName)
val text = FileUtil.loadFile(file, true)
if (InTextDirectivesUtils.isDirectiveDefined(text, "SKIP_IN_RUNTIME_TEST")) return
val jdkKind =
if (InTextDirectivesUtils.isDirectiveDefined(text, "FULL_JDK")) TestJdkKind.FULL_JDK
else defaultJdkKind
compileFile(file, text, jdkKind)
val classLoader = URLClassLoader(arrayOf(tmpdir.toURI().toURL()), ForTestCompileRuntime.runtimeAndReflectJarClassLoader())
val actual = createReflectedPackageView(classLoader, JvmResolveUtil.TEST_MODULE_NAME)
val comparatorConfiguration = Configuration(
/* checkPrimaryConstructors = */ fileName.endsWith(".kt"),
/* checkPropertyAccessors = */ true,
/* includeMethodsOfKotlinAny = */ false,
// Skip Java annotation constructors because order of their parameters is not retained at runtime
{ descriptor -> !descriptor!!.isJavaAnnotationConstructor() },
errorTypesForbidden(), renderer
)
val differentResultFile = KotlinTestUtils.replaceExtension(file, "runtime.txt")
if (differentResultFile.exists()) {
RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile(actual, comparatorConfiguration, differentResultFile)
return
}
val expected = LoadDescriptorUtil.loadTestPackageAndBindingContextFromJavaRoot(
tmpdir, testRootDisposable, jdkKind, ConfigurationKind.ALL, true
).first
RecursiveDescriptorComparator.validateAndCompareDescriptors(expected, actual, comparatorConfiguration, null)
}
private fun DeclarationDescriptor.isJavaAnnotationConstructor() =
this is ConstructorDescriptor &&
containingDeclaration is JavaClassDescriptor &&
containingDeclaration.kind == ClassKind.ANNOTATION_CLASS
private fun compileFile(file: File, text: String, jdkKind: TestJdkKind) {
val fileName = file.name
when {
fileName.endsWith(".java") -> {
val sources = KotlinTestUtils.createTestFiles(fileName, text, object : TestFileFactoryNoModules<File>() {
override fun create(fileName: String, text: String, directives: Map<String, String>): File {
val targetFile = File(tmpdir, fileName)
targetFile.writeText(adaptJavaSource(text))
return targetFile
}
})
LoadDescriptorUtil.compileJavaWithAnnotationsJar(sources, tmpdir)
}
fileName.endsWith(".kt") -> {
val environment = KotlinTestUtils.createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(
myTestRootDisposable, ConfigurationKind.ALL, jdkKind
)
val jetFile = KotlinTestUtils.createFile(file.path, text, environment.project)
GenerationUtils.compileFileGetClassFileFactoryForTest(jetFile, environment).writeAllTo(tmpdir)
}
}
}
private fun createReflectedPackageView(classLoader: URLClassLoader, moduleName: String): SyntheticPackageViewForTest {
val moduleData = RuntimeModuleData.create(classLoader)
moduleData.packageFacadeProvider.registerModule(moduleName)
val module = moduleData.module
val generatedPackageDir = File(tmpdir, LoadDescriptorUtil.TEST_PACKAGE_FQNAME.pathSegments().single().asString())
val allClassFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.class"), generatedPackageDir)
val packageScopes = arrayListOf<MemberScope>()
val classes = arrayListOf<ClassDescriptor>()
for (classFile in allClassFiles) {
val className = classFile.toRelativeString(tmpdir).substringBeforeLast(".class").replace('/', '.').replace('\\', '.')
val klass = classLoader.loadClass(className).sure { "Couldn't load class $className" }
val binaryClass = ReflectKotlinClass.create(klass)
val header = binaryClass?.classHeader
if (header?.kind == KotlinClassHeader.Kind.FILE_FACADE || header?.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS) {
val packageView = module.getPackage(LoadDescriptorUtil.TEST_PACKAGE_FQNAME)
if (!packageScopes.contains(packageView.memberScope)) {
packageScopes.add(packageView.memberScope)
}
}
else if (header == null || header.kind == KotlinClassHeader.Kind.CLASS) {
// Either a normal Kotlin class or a Java class
val classId = klass.classId
if (!classId.isLocal) {
val classDescriptor = module.findClassAcrossModuleDependencies(classId).sure { "Couldn't resolve class $className" }
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
classes.add(classDescriptor)
}
}
}
}
// Since runtime package view descriptor doesn't support getAllDescriptors(), we construct a synthetic package view here.
// It has in its scope descriptors for all the classes and top level members generated by the compiler
return SyntheticPackageViewForTest(module, packageScopes, classes)
}
private fun adaptJavaSource(text: String): String {
val typeAnnotations = arrayOf("NotNull", "Nullable", "ReadOnly", "Mutable")
return typeAnnotations.fold(text) { text, annotation -> text.replace("@$annotation", "") }.replace(
"@interface",
"@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @interface"
)
}
private class SyntheticPackageViewForTest(override val module: ModuleDescriptor,
packageScopes: List<MemberScope>,
classes: List<ClassifierDescriptor>) : PackageViewDescriptor {
private val scope: MemberScope
init {
val list = ArrayList<MemberScope>(packageScopes.size + 1)
list.add(ScopeWithClassifiers(classes))
list.addAll(packageScopes)
scope = ChainedMemberScope("synthetic package view for test", list)
}
override val fqName: FqName
get() = LoadDescriptorUtil.TEST_PACKAGE_FQNAME
override val memberScope: MemberScope
get() = scope
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R =
visitor.visitPackageViewDescriptor(this, data)
override fun getContainingDeclaration() = null
override fun getOriginal() = throw UnsupportedOperationException()
override fun substitute(substitutor: TypeSubstitutor) = throw UnsupportedOperationException()
override fun acceptVoid(visitor: DeclarationDescriptorVisitor<Void, Void>?) = throw UnsupportedOperationException()
override fun getAnnotations() = throw UnsupportedOperationException()
override fun getName() = throw UnsupportedOperationException()
override val fragments: Nothing
get() = throw UnsupportedOperationException()
}
private class ScopeWithClassifiers(classifiers: List<ClassifierDescriptor>) : MemberScopeImpl() {
private val classifierMap = HashMap<Name, ClassifierDescriptor>()
init {
for (classifier in classifiers) {
classifierMap.put(classifier.name, classifier)?.let {
throw IllegalStateException(String.format("Redeclaration: %s (%s) and %s (%s) (no line info available)",
DescriptorUtils.getFqName(it), it,
DescriptorUtils.getFqName(classifier), classifier))
}
}
}
override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? = classifierMap[name]
override fun getContributedDescriptors(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean): Collection<DeclarationDescriptor> = classifierMap.values
override fun printScopeStructure(p: Printer) {
p.println("runtime descriptor loader test")
}
}
}
@@ -0,0 +1,158 @@
/*
* 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.renderer
import com.intellij.openapi.editor.impl.DocumentImpl
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.config.getModuleName
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.frontend.di.createContainerForLazyResolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.resolve.TargetEnvironment
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import java.io.File
import java.util.*
abstract class AbstractDescriptorRendererTest : KotlinTestWithEnvironment() {
protected open fun getDescriptor(declaration: KtDeclaration, container: ComponentProvider): DeclarationDescriptor {
return container.get<ResolveSession>().resolveToDescriptor(declaration)
}
protected open val targetEnvironment: TargetEnvironment
get() = CompilerEnvironment
fun doTest(path: String) {
val fileText = FileUtil.loadFile(File(path), true)
val psiFile = KtPsiFactory(project).createFile(fileText)
val context = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(project, environment.getModuleName())
val container = createContainerForLazyResolve(
context,
FileBasedDeclarationProviderFactory(context.storageManager, listOf(psiFile)),
CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(),
JvmPlatform,
targetEnvironment
)
val resolveSession = container.get<ResolveSession>()
context.initializeModuleContents(resolveSession.packageFragmentProvider)
val descriptors = ArrayList<DeclarationDescriptor>()
psiFile.accept(object : KtVisitorVoid() {
override fun visitKtFile(file: KtFile) {
val fqName = file.packageFqName
if (!fqName.isRoot) {
val packageDescriptor = context.module.getPackage(fqName)
descriptors.add(packageDescriptor)
}
file.acceptChildren(this)
}
override fun visitParameter(parameter: KtParameter) {
val declaringElement = parameter.parent.parent
when (declaringElement) {
is KtFunctionType -> return
is KtNamedFunction ->
addCorrespondingParameterDescriptor(getDescriptor(declaringElement, container) as FunctionDescriptor, parameter)
is KtPrimaryConstructor -> {
val ktClassOrObject: KtClassOrObject = declaringElement.getContainingClassOrObject()
val classDescriptor = getDescriptor(ktClassOrObject, container) as ClassDescriptor
addCorrespondingParameterDescriptor(classDescriptor.unsubstitutedPrimaryConstructor!!, parameter)
}
else -> super.visitParameter(parameter)
}
}
override fun visitPropertyAccessor(accessor: KtPropertyAccessor) {
val property = accessor.property
val propertyDescriptor = getDescriptor(property, container) as PropertyDescriptor
if (accessor.isGetter) {
descriptors.add(propertyDescriptor.getter!!)
}
else {
descriptors.add(propertyDescriptor.setter!!)
}
accessor.acceptChildren(this)
}
override fun visitAnonymousInitializer(initializer: KtAnonymousInitializer) {
initializer.acceptChildren(this)
}
override fun visitDeclaration(element: KtDeclaration) {
val descriptor = getDescriptor(element, container)
descriptors.add(descriptor)
if (descriptor is ClassDescriptor) {
// if class has primary constructor then we visit it later, otherwise add it artificially
if (element !is KtClassOrObject || !element.hasExplicitPrimaryConstructor()) {
if (descriptor.unsubstitutedPrimaryConstructor != null) {
descriptors.add(descriptor.unsubstitutedPrimaryConstructor!!)
}
}
}
element.acceptChildren(this)
}
override fun visitKtElement(element: KtElement) {
element.acceptChildren(this)
}
private fun addCorrespondingParameterDescriptor(functionDescriptor: FunctionDescriptor, parameter: KtParameter) {
for (valueParameterDescriptor in functionDescriptor.valueParameters) {
if (valueParameterDescriptor.name == parameter.nameAsName) {
descriptors.add(valueParameterDescriptor)
}
}
parameter.acceptChildren(this)
}
})
val renderer = DescriptorRenderer.withOptions {
classifierNamePolicy = ClassifierNamePolicy.FULLY_QUALIFIED
modifiers = DescriptorRendererModifier.ALL
}
val renderedDescriptors = descriptors.map { renderer.render(it) }.joinToString(separator = "\n")
val document = DocumentImpl(psiFile.text)
KtUsefulTestCase.assertSameLines(KotlinTestUtils.getLastCommentedLines(document), renderedDescriptors.toString())
}
override fun createEnvironment(): KotlinCoreEnvironment {
return createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY)
}
}
@@ -0,0 +1,38 @@
/*
* 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.resolve;
import com.intellij.openapi.project.Project;
import org.jetbrains.kotlin.checkers.TestCheckerUtil;
import org.jetbrains.kotlin.psi.KtFile;
public abstract class AbstractResolveTest extends ExtensibleResolveTestCase {
@Override
protected ExpectedResolveData getExpectedResolveData() {
final Project project = getProject();
return new ExpectedResolveData(
ExpectedResolveDataUtil.prepareDefaultNameToDescriptors(project, getEnvironment()),
ExpectedResolveDataUtil.prepareDefaultNameToDeclaration(project, getEnvironment())
) {
@Override
protected KtFile createKtFile(String fileName, String text) {
return TestCheckerUtil.createCheckAndReturnPsiFile(fileName, text, project);
}
};
}
}
@@ -0,0 +1,376 @@
/*
* 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.resolve;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiQualifiedNamedElement;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.types.ErrorUtils;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jetbrains.kotlin.resolve.BindingContext.AMBIGUOUS_REFERENCE_TARGET;
import static org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET;
import static org.junit.Assert.*;
public abstract class ExpectedResolveData {
protected static final String STANDARD_PREFIX = "kotlin::";
private static class Position {
private final PsiElement element;
private Position(KtFile file, int offset) {
this.element = file.findElementAt(offset);
}
public PsiElement getElement() {
return element;
}
@Override
public String toString() {
return DiagnosticUtils.atLocation(element);
}
}
private final Map<String, Position> declarationToPosition = Maps.newHashMap();
private final Map<Position, String> positionToReference = Maps.newHashMap();
private final Map<Position, String> positionToType = Maps.newHashMap();
private final Map<String, DeclarationDescriptor> nameToDescriptor;
private final Map<String, PsiElement> nameToPsiElement;
public ExpectedResolveData(Map<String, DeclarationDescriptor> nameToDescriptor, Map<String, PsiElement> nameToPsiElement) {
this.nameToDescriptor = nameToDescriptor;
this.nameToPsiElement = nameToPsiElement;
}
public final KtFile createFileFromMarkedUpText(String fileName, String text) {
Map<String, Integer> declarationToIntPosition = Maps.newHashMap();
Map<Integer, String> intPositionToReference = Maps.newHashMap();
Map<Integer, String> intPositionToType = Maps.newHashMap();
Pattern pattern = Pattern.compile("(~[^~]+~)|(`[^`]+`)");
while (true) {
Matcher matcher = pattern.matcher(text);
if (!matcher.find()) break;
String group = matcher.group();
String name = group.substring(1, group.length() - 1);
int start = matcher.start();
if (group.startsWith("~")) {
if (declarationToIntPosition.put(name, start) != null) {
throw new IllegalArgumentException("Redeclaration: " + name);
}
}
else if (group.startsWith("`")) {
if (name.startsWith(":")) {
intPositionToType.put(start - 1, name.substring(1));
}
else {
intPositionToReference.put(start, name);
}
}
else {
throw new IllegalStateException();
}
text = text.substring(0, start) + text.substring(matcher.end());
}
KtFile ktFile = createKtFile(fileName, text);
for (Map.Entry<Integer, String> entry : intPositionToType.entrySet()) {
positionToType.put(new Position(ktFile, entry.getKey()), entry.getValue());
}
for (Map.Entry<String, Integer> entry : declarationToIntPosition.entrySet()) {
declarationToPosition.put(entry.getKey(), new Position(ktFile, entry.getValue()));
}
for (Map.Entry<Integer, String> entry : intPositionToReference.entrySet()) {
positionToReference.put(new Position(ktFile, entry.getKey()), entry.getValue());
}
return ktFile;
}
protected abstract KtFile createKtFile(String fileName, String text);
protected static BindingContext analyze(List<KtFile> files, KotlinCoreEnvironment environment) {
if (files.isEmpty()) {
System.err.println("Suspicious: no files");
return BindingContext.EMPTY;
}
Project project = files.iterator().next().getProject();
AnalysisResult analysisResult = JvmResolveUtil.analyzeFilesWithJavaIntegration(project, files, environment);
return analysisResult.getBindingContext();
}
public final void checkResult(BindingContext bindingContext) {
Set<PsiElement> unresolvedReferences = Sets.newHashSet();
for (Diagnostic diagnostic : bindingContext.getDiagnostics()) {
if (Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS.contains(diagnostic.getFactory())) {
unresolvedReferences.add(diagnostic.getPsiElement());
}
}
Map<String, PsiElement> nameToDeclaration = Maps.newHashMap();
Map<PsiElement, String> declarationToName = Maps.newHashMap();
for (Map.Entry<String, Position> entry : declarationToPosition.entrySet()) {
String name = entry.getKey();
Position position = entry.getValue();
PsiElement element = position.getElement();
PsiElement ancestorOfType;
if (name.equals("file")) {
ancestorOfType = element.getContainingFile();
}
else {
ancestorOfType = getAncestorOfType(KtDeclaration.class, element);
if (ancestorOfType == null) {
KtPackageDirective directive = getAncestorOfType(KtPackageDirective.class, element);
assert directive != null : "Not a declaration: " + name;
ancestorOfType = element;
}
}
nameToDeclaration.put(name, ancestorOfType);
declarationToName.put(ancestorOfType, name);
}
for (Map.Entry<Position, String> entry : positionToReference.entrySet()) {
Position position = entry.getKey();
String name = entry.getValue();
PsiElement element = position.getElement();
KtReferenceExpression referenceExpression = PsiTreeUtil.getParentOfType(element, KtReferenceExpression.class);
DeclarationDescriptor referenceTarget = bindingContext.get(REFERENCE_TARGET, referenceExpression);
if ("!".equals(name)) {
assertTrue(
"Must have been unresolved: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
unresolvedReferences.contains(referenceExpression));
assertTrue(
String.format("Reference =%s= has a reference target =%s= but expected to be unresolved",
renderReferenceInContext(referenceExpression), renderNullableDescriptor(referenceTarget)),
referenceTarget == null);
continue;
}
if ("!!".equals(name)) {
assertTrue(
"Must have been resolved to multiple descriptors: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
bindingContext.get(AMBIGUOUS_REFERENCE_TARGET, referenceExpression) != null);
continue;
}
else if ("!null".equals(name)) {
assertTrue(
"Must have been resolved to null: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
referenceTarget == null
);
continue;
}
else if ("!error".equals(name)) {
assertTrue(
"Must have been resolved to error: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
ErrorUtils.isError(referenceTarget)
);
continue;
}
PsiElement expected = nameToDeclaration.get(name);
if (expected == null) {
expected = nameToPsiElement.get(name);
}
KtReferenceExpression reference = getAncestorOfType(KtReferenceExpression.class, element);
if (expected == null && name.startsWith(STANDARD_PREFIX)) {
DeclarationDescriptor expectedDescriptor = nameToDescriptor.get(name);
KtTypeReference typeReference = getAncestorOfType(KtTypeReference.class, element);
if (expectedDescriptor != null) {
DeclarationDescriptor actual = bindingContext.get(REFERENCE_TARGET, reference);
assertSame("Expected: " + name, expectedDescriptor.getOriginal(), actual == null
? null
: actual.getOriginal());
continue;
}
KotlinType actualType = bindingContext.get(BindingContext.TYPE, typeReference);
assertNotNull("Type " + name + " not resolved for reference " + name, actualType);
ClassifierDescriptor expectedClass = getBuiltinClass(name.substring(STANDARD_PREFIX.length()));
assertSame("Type resolution mismatch: ", expectedClass.getTypeConstructor(), actualType.getConstructor());
continue;
}
assert expected != null : "No declaration for " + name;
if (referenceTarget instanceof PackageViewDescriptor) {
KtPackageDirective expectedDirective = PsiTreeUtil.getParentOfType(expected, KtPackageDirective.class);
FqName expectedFqName;
if (expectedDirective != null) {
expectedFqName = expectedDirective.getFqName();
}
else if (expected instanceof PsiQualifiedNamedElement) {
String qualifiedName = ((PsiQualifiedNamedElement) expected).getQualifiedName();
assert qualifiedName != null : "No qualified name for " + name;
expectedFqName = new FqName(qualifiedName);
}
else {
throw new IllegalStateException(expected.getClass().getName() + " name=" + name);
}
assertEquals(expectedFqName, ((PackageViewDescriptor) referenceTarget).getFqName());
continue;
}
PsiElement actual = referenceTarget == null
? bindingContext.get(BindingContext.LABEL_TARGET, referenceExpression)
: DescriptorToSourceUtils.descriptorToDeclaration(referenceTarget);
if (actual instanceof KtSimpleNameExpression) {
actual = ((KtSimpleNameExpression)actual).getIdentifier();
}
String actualName = null;
if (actual != null) {
actualName = declarationToName.get(actual);
if (actualName == null) {
actualName = actual.toString();
}
}
assertNotNull(element.getText(), reference);
assertEquals(
"Reference `" + name + "`" + renderReferenceInContext(reference) + " is resolved into " + actualName + ".",
expected, actual);
}
for (Map.Entry<Position, String> entry : positionToType.entrySet()) {
Position position = entry.getKey();
String typeName = entry.getValue();
PsiElement element = position.getElement();
KtExpression expression = getAncestorOfType(KtExpression.class, element);
KotlinType expressionType = bindingContext.getType(expression);
TypeConstructor expectedTypeConstructor;
if (typeName.startsWith(STANDARD_PREFIX)) {
String name = typeName.substring(STANDARD_PREFIX.length());
ClassifierDescriptor expectedClass = getBuiltinClass(name);
expectedTypeConstructor = expectedClass.getTypeConstructor();
}
else {
Position declarationPosition = declarationToPosition.get(typeName);
assertNotNull("Undeclared: " + typeName, declarationPosition);
PsiElement declElement = declarationPosition.getElement();
assertNotNull(declarationPosition);
KtDeclaration declaration = getAncestorOfType(KtDeclaration.class, declElement);
assertNotNull(declaration);
if (declaration instanceof KtClass) {
ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, declaration);
expectedTypeConstructor = classDescriptor.getTypeConstructor();
}
else if (declaration instanceof KtTypeParameter) {
TypeParameterDescriptor typeParameterDescriptor = bindingContext.get(BindingContext.TYPE_PARAMETER, (KtTypeParameter) declaration);
expectedTypeConstructor = typeParameterDescriptor.getTypeConstructor();
}
else {
fail("Unsupported declaration: " + declaration);
return;
}
}
assertNotNull(expression.getText() + " type is null", expressionType);
assertSame("At " + position + ": ", expectedTypeConstructor, expressionType.getConstructor());
}
}
@NotNull
public static ClassifierDescriptor getBuiltinClass(String nameOrFqName) {
ClassifierDescriptor expectedClass;
if (nameOrFqName.indexOf('.') >= 0) {
expectedClass = JvmPlatform.INSTANCE.getBuiltIns().getBuiltInClassByFqNameNullable(FqName.fromSegments(Arrays.asList(nameOrFqName.split("\\."))));
}
else {
expectedClass = JvmPlatform.INSTANCE.getBuiltIns().getBuiltInClassByNameNullable(Name.identifier(nameOrFqName));
}
assertNotNull("Expected class not found: " + nameOrFqName, expectedClass);
return expectedClass;
}
private static String renderReferenceInContext(KtReferenceExpression referenceExpression) {
KtExpression statement = referenceExpression;
while (true) {
PsiElement parent = statement.getParent();
if (!(parent instanceof KtExpression)) break;
if (parent instanceof KtBlockExpression) break;
statement = (KtExpression) parent;
}
KtDeclaration declaration = PsiTreeUtil.getParentOfType(referenceExpression, KtDeclaration.class);
return referenceExpression.getText() + " at " + DiagnosticUtils.atLocation(referenceExpression) +
" in " + statement.getText() + (declaration == null ? "" : " in " + declaration.getText());
}
private static <T> T getAncestorOfType(Class<T> type, PsiElement element) {
while (element != null && !type.isInstance(element)) {
element = element.getParent();
}
@SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
T result = (T) element;
return result;
}
@NotNull
private static String renderNullableDescriptor(@Nullable DeclarationDescriptor d) {
return d == null ? "<null>" : DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d);
}
}
@@ -0,0 +1,164 @@
/*
* 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.resolve;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform;
import org.jetbrains.kotlin.resolve.lazy.LazyResolveTestUtil;
import org.jetbrains.kotlin.resolve.scopes.ImportingScope;
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl;
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.tests.di.ContainerForTests;
import org.jetbrains.kotlin.tests.di.InjectionKt;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext;
import org.jetbrains.kotlin.types.expressions.FakeCallKind;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertNotNull;
public class ExpectedResolveDataUtil {
private ExpectedResolveDataUtil() {
}
public static Map<String, DeclarationDescriptor> prepareDefaultNameToDescriptors(Project project, KotlinCoreEnvironment environment) {
KotlinBuiltIns builtIns = JvmPlatform.INSTANCE.getBuiltIns();
Map<String, DeclarationDescriptor> nameToDescriptor = new HashMap<String, DeclarationDescriptor>();
nameToDescriptor.put("kotlin::Int.plus(Int)", standardFunction(builtIns.getInt(), "plus", project, builtIns.getIntType()));
FunctionDescriptor descriptorForGet = standardFunction(builtIns.getArray(), "get", project, builtIns.getIntType());
nameToDescriptor.put("kotlin::Array.get(Int)", descriptorForGet.getOriginal());
nameToDescriptor.put("kotlin::Int.compareTo(Double)", standardFunction(builtIns.getInt(), "compareTo", project, builtIns.getDoubleType()));
@NotNull
FunctionDescriptor descriptorForSet = standardFunction(builtIns.getArray(), "set", project, builtIns.getIntType(), builtIns.getIntType());
nameToDescriptor.put("kotlin::Array.set(Int, Int)", descriptorForSet.getOriginal());
return nameToDescriptor;
}
public static Map<String, PsiElement> prepareDefaultNameToDeclaration(Project project, KotlinCoreEnvironment environment) {
Map<String, PsiElement> nameToDeclaration = new HashMap<String, PsiElement>();
PsiClass java_util_Collections = findClass("java.util.Collections", project, environment);
nameToDeclaration.put("java::java.util.Collections.emptyList()", findMethod(java_util_Collections, "emptyList"));
nameToDeclaration.put("java::java.util.Collections", java_util_Collections);
PsiClass java_util_List = findClass("java.util.ArrayList", project, environment);
nameToDeclaration.put("java::java.util.List", findClass("java.util.List", project, environment));
nameToDeclaration.put("java::java.util.ArrayList", java_util_List);
nameToDeclaration.put("java::java.util.ArrayList.set()", java_util_List.findMethodsByName("set", true)[0]);
nameToDeclaration.put("java::java.util.ArrayList.get()", java_util_List.findMethodsByName("get", true)[0]);
nameToDeclaration.put("java::java", findPackage("java", project));
nameToDeclaration.put("java::java.util", findPackage("java.util", project));
nameToDeclaration.put("java::java.lang", findPackage("java.lang", project));
nameToDeclaration.put("java::java.lang.Object", findClass("java.lang.Object", project, environment));
nameToDeclaration.put("java::java.lang.Comparable", findClass("java.lang.Comparable", project, environment));
PsiClass java_lang_System = findClass("java.lang.System", project, environment);
nameToDeclaration.put("java::java.lang.System", java_lang_System);
PsiMethod[] methods = findClass("java.io.PrintStream", project, environment).findMethodsByName("print", true);
nameToDeclaration.put("java::java.io.PrintStream.print(Object)", methods[8]);
nameToDeclaration.put("java::java.io.PrintStream.print(Int)", methods[2]);
nameToDeclaration.put("java::java.io.PrintStream.print(char[])", methods[6]);
nameToDeclaration.put("java::java.io.PrintStream.print(Double)", methods[5]);
PsiField outField = java_lang_System.findFieldByName("out", true);
assertNotNull("'out' property wasn't found", outField);
nameToDeclaration.put("java::java.lang.System.out", outField);
PsiClass java_lang_Number = findClass("java.lang.Number", project, environment);
nameToDeclaration.put("java::java.lang.Number", java_lang_Number);
nameToDeclaration.put("java::java.lang.Number.intValue()", java_lang_Number.findMethodsByName("intValue", true)[0]);
return nameToDeclaration;
}
@NotNull
private static PsiElement findPackage(String qualifiedName, Project project) {
JavaPsiFacade javaFacade = JavaPsiFacade.getInstance(project);
PsiPackage javaFacadePackage = javaFacade.findPackage(qualifiedName);
assertNotNull("Package wasn't found: " + qualifiedName, javaFacadePackage);
return javaFacadePackage;
}
@NotNull
private static PsiMethod findMethod(PsiClass psiClass, String name) {
PsiMethod[] emptyLists = psiClass.findMethodsByName(name, true);
return emptyLists[0];
}
@NotNull
private static PsiClass findClass(String qualifiedName, Project project, KotlinCoreEnvironment environment) {
ModuleDescriptor module = LazyResolveTestUtil.resolveProject(project, environment);
ClassDescriptor classDescriptor = DescriptorUtilsKt.resolveTopLevelClass(module, new FqName(qualifiedName), NoLookupLocation.FROM_TEST);
assertNotNull("Class descriptor wasn't resolved: " + qualifiedName, classDescriptor);
PsiClass psiClass = (PsiClass) DescriptorToSourceUtils.getSourceFromDescriptor(classDescriptor);
assertNotNull("Class declaration wasn't found: " + classDescriptor, psiClass);
return psiClass;
}
@NotNull
private static FunctionDescriptor standardFunction(
ClassDescriptor classDescriptor,
String name,
Project project,
KotlinType... parameterTypes
) {
ModuleDescriptorImpl emptyModule = KotlinTestUtils.createEmptyModule();
ContainerForTests container = InjectionKt.createContainerForTests(project, emptyModule);
emptyModule.setDependencies(emptyModule);
emptyModule.initialize(PackageFragmentProvider.Empty.INSTANCE);
LexicalScopeImpl lexicalScope = new LexicalScopeImpl(ImportingScope.Empty.INSTANCE, classDescriptor, false,
classDescriptor.getThisAsReceiverParameter(),
LexicalScopeKind.SYNTHETIC);
ExpressionTypingContext context = ExpressionTypingContext.newContext(
new BindingTraceContext(), lexicalScope,
DataFlowInfoFactory.EMPTY, TypeUtils.NO_EXPECTED_TYPE);
OverloadResolutionResults<FunctionDescriptor> functions = container.getFakeCallResolver().resolveFakeCall(
context, null, Name.identifier(name), null, null, FakeCallKind.OTHER, parameterTypes);
for (ResolvedCall<? extends FunctionDescriptor> resolvedCall : functions.getResultingCalls()) {
List<ValueParameterDescriptor> unsubstitutedValueParameters = resolvedCall.getResultingDescriptor().getValueParameters();
for (int i = 0, unsubstitutedValueParametersSize = unsubstitutedValueParameters.size(); i < unsubstitutedValueParametersSize; i++) {
ValueParameterDescriptor unsubstitutedValueParameter = unsubstitutedValueParameters.get(i);
if (unsubstitutedValueParameter.getType().equals(parameterTypes[i])) {
return resolvedCall.getResultingDescriptor();
}
}
}
throw new IllegalArgumentException("Not found: kotlin::" + classDescriptor.getName() + "." + name + "(" +
Arrays.toString(parameterTypes) + ")");
}
}
@@ -0,0 +1,65 @@
/*
* 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.resolve;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment;
import java.io.File;
import java.util.List;
import java.util.Map;
public abstract class ExtensibleResolveTestCase extends KotlinTestWithEnvironment {
private ExpectedResolveData expectedResolveData;
@Override
protected KotlinCoreEnvironment createEnvironment() {
return createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY);
}
@Override
protected void setUp() throws Exception {
super.setUp();
expectedResolveData = getExpectedResolveData();
}
@Override
protected void tearDown() throws Exception {
expectedResolveData = null;
super.tearDown();
}
protected abstract ExpectedResolveData getExpectedResolveData();
protected void doTest(@NonNls String filePath) throws Exception {
File file = new File(filePath);
String text = KotlinTestUtils.doLoadFile(file);
List<KtFile> files = KotlinTestUtils.createTestFiles("file.kt", text, new KotlinTestUtils.TestFileFactoryNoModules<KtFile>() {
@NotNull
@Override
public KtFile create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
return expectedResolveData.createFileFromMarkedUpText(fileName, text);
}
});
expectedResolveData.checkResult(ExpectedResolveData.analyze(files, getEnvironment()));
}
}
@@ -0,0 +1,399 @@
/*
* 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.resolve.annotation;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.Function;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationWithTarget;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtAnnotationEntry;
import org.jetbrains.kotlin.psi.KtAnnotationUseSiteTarget;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier;
import org.jetbrains.kotlin.renderer.DescriptorRendererOptions;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment;
import org.jetbrains.kotlin.types.TypeProjection;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isNonCompanionObject;
public abstract class AbstractAnnotationDescriptorResolveTest extends KotlinTestWithEnvironment {
private static final DescriptorRenderer WITH_ANNOTATION_ARGUMENT_TYPES = DescriptorRenderer.Companion.withOptions(
new Function1<DescriptorRendererOptions, Unit>() {
@Override
public Unit invoke(DescriptorRendererOptions options) {
options.setVerbose(true);
options.setClassifierNamePolicy(ClassifierNamePolicy.SHORT.INSTANCE);
options.setModifiers(DescriptorRendererModifier.ALL);
return Unit.INSTANCE;
}
}
);
private static final String PATH = "compiler/testData/resolveAnnotations/testFile.kt";
private static final FqName PACKAGE = new FqName("test");
protected BindingContext context;
@Override
protected KotlinCoreEnvironment createEnvironment() {
return KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(getTestRootDisposable());
}
protected void doTest(@NotNull String content, @NotNull String expectedAnnotation) {
checkAnnotationOnAllExceptLocalDeclarations(content, expectedAnnotation);
checkAnnotationOnLocalDeclarations(expectedAnnotation);
}
protected void checkAnnotationOnAllExceptLocalDeclarations(String content, String expectedAnnotation) {
KtFile testFile = getFile(content);
PackageFragmentDescriptor testPackage = getPackage(testFile);
checkAnnotationsOnFile(expectedAnnotation, testFile);
ClassDescriptor myClass = getClassDescriptor(testPackage, "MyClass");
checkDescriptor(expectedAnnotation, myClass);
ClassDescriptor companionObjectDescriptor = myClass.getCompanionObjectDescriptor();
assert companionObjectDescriptor != null : "Cannot find companion object for class " + myClass.getName();
checkDescriptor(expectedAnnotation, companionObjectDescriptor);
checkDescriptor(expectedAnnotation, getInnerClassDescriptor(myClass, "InnerClass"));
FunctionDescriptor foo = getFunctionDescriptor(myClass, "foo");
checkAnnotationsOnFunction(expectedAnnotation, foo);
SimpleFunctionDescriptor anonymousFun = getAnonymousFunDescriptor();
if (anonymousFun instanceof AnonymousFunctionDescriptor) {
for (ValueParameterDescriptor descriptor : anonymousFun.getValueParameters()) {
checkDescriptor(expectedAnnotation, descriptor);
}
}
PropertyDescriptor prop = getPropertyDescriptor(myClass, "prop");
checkAnnotationsOnProperty(expectedAnnotation, prop);
FunctionDescriptor topFoo = getFunctionDescriptor(testPackage, "topFoo");
checkAnnotationsOnFunction(expectedAnnotation, topFoo);
PropertyDescriptor topProp = getPropertyDescriptor(testPackage, "topProp", true);
checkAnnotationsOnProperty(expectedAnnotation, topProp);
checkDescriptor(expectedAnnotation, getClassDescriptor(testPackage, "MyObject"));
checkDescriptor(expectedAnnotation, getConstructorParameterDescriptor(myClass, "consProp"));
checkDescriptor(expectedAnnotation, getConstructorParameterDescriptor(myClass, "param"));
}
private void checkAnnotationsOnFile(String expectedAnnotation, KtFile file) {
String actualAnnotation = StringUtil.join(file.getAnnotationEntries(), new Function<KtAnnotationEntry, String>() {
@Override
public String fun(KtAnnotationEntry annotationEntry) {
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
assertNotNull(annotationDescriptor);
KtAnnotationUseSiteTarget target = annotationEntry.getUseSiteTarget();
if (target != null) {
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(
annotationDescriptor, target.getAnnotationUseSiteTarget());
}
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(annotationDescriptor, null);
}
}, " ");
String expectedAnnotationWithTarget = "@" + AnnotationUseSiteTarget.FILE.getRenderName() + ":" + expectedAnnotation.substring(1);
assertEquals(expectedAnnotationWithTarget, actualAnnotation);
}
private void checkAnnotationOnLocalDeclarations(String expectedAnnotation) {
checkDescriptor(expectedAnnotation, getLocalClassDescriptor("LocalClass"));
checkDescriptor(expectedAnnotation, getLocalObjectDescriptor("LocalObject"));
checkDescriptor(expectedAnnotation, getLocalFunDescriptor("localFun"));
checkDescriptor(expectedAnnotation, getLocalVarDescriptor(context, "localVar"));
}
private static void checkAnnotationsOnProperty(String expectedAnnotation, PropertyDescriptor prop) {
checkDescriptorWithTarget(expectedAnnotation, prop, AnnotationUseSiteTarget.FIELD);
checkDescriptor(expectedAnnotation, prop.getGetter());
PropertySetterDescriptor propSetter = prop.getSetter();
assertNotNull(propSetter);
checkAnnotationsOnFunction(expectedAnnotation, propSetter);
}
private static void checkAnnotationsOnFunction(String expectedAnnotation, FunctionDescriptor foo) {
checkDescriptor(expectedAnnotation, foo);
checkDescriptor(expectedAnnotation, getFunctionParameterDescriptor(foo, "param"));
}
@NotNull
protected static FunctionDescriptor getFunctionDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name) {
Name functionName = Name.identifier(name);
MemberScope memberScope = packageView.getMemberScope();
Collection<SimpleFunctionDescriptor> functions = memberScope.getContributedFunctions(functionName, NoLookupLocation.FROM_TEST);
assert functions.size() == 1 : "Failed to find function " + functionName + " in class" + "." + packageView.getName();
return functions.iterator().next();
}
@NotNull
private static FunctionDescriptor getFunctionDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name functionName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.<TypeProjection>emptyList());
Collection<SimpleFunctionDescriptor> functions = memberScope.getContributedFunctions(functionName, NoLookupLocation.FROM_TEST);
assert functions.size() == 1 : "Failed to find function " + functionName + " in class" + "." + classDescriptor.getName();
return functions.iterator().next();
}
@Nullable
protected static PropertyDescriptor getPropertyDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name, boolean failOnMissing) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = packageView.getMemberScope();
Collection<PropertyDescriptor> properties = memberScope.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
if (properties.isEmpty()) {
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(memberScope)) {
if (descriptor instanceof ClassDescriptor) {
Collection<PropertyDescriptor> classProperties =
((ClassDescriptor) descriptor).getMemberScope(Collections.<TypeProjection>emptyList())
.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
if (!classProperties.isEmpty()) {
properties = classProperties;
break;
}
}
}
}
if (failOnMissing) {
assert properties.size() == 1 : "Failed to find property " + propertyName + " in class " + packageView.getName();
}
else if (properties.size() != 1) {
return null;
}
return properties.iterator().next();
}
@NotNull
private static PropertyDescriptor getPropertyDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.<TypeProjection>emptyList());
Collection<PropertyDescriptor> properties = memberScope.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
assert properties.size() == 1 : "Failed to find property " + propertyName + " in class " + classDescriptor.getName();
return properties.iterator().next();
}
@NotNull
protected static ClassDescriptor getClassDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name) {
Name className = Name.identifier(name);
ClassifierDescriptor aClass = packageView.getMemberScope().getContributedClassifier(className, NoLookupLocation.FROM_TEST);
assertNotNull("Failed to find class: " + packageView.getName() + "." + className, aClass);
assert aClass instanceof ClassDescriptor : "Not a class: " + aClass;
return (ClassDescriptor) aClass;
}
@NotNull
private static ClassDescriptor getInnerClassDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.<TypeProjection>emptyList());
ClassifierDescriptor innerClass = memberScope.getContributedClassifier(propertyName, NoLookupLocation.FROM_TEST);
assert innerClass instanceof ClassDescriptor : "Failed to find inner class " +
propertyName +
" in class " +
classDescriptor.getName();
return (ClassDescriptor) innerClass;
}
@NotNull
private ClassDescriptor getLocalClassDescriptor(@NotNull String name) {
for (ClassDescriptor descriptor : context.getSliceContents(BindingContext.CLASS).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local class " + name);
return null;
}
@NotNull
private ClassDescriptor getLocalObjectDescriptor(@NotNull String name) {
ClassDescriptor localClassDescriptor = getLocalClassDescriptor(name);
if (isNonCompanionObject(localClassDescriptor)) {
return localClassDescriptor;
}
fail("Failed to find local object " + name);
return null;
}
@NotNull
private SimpleFunctionDescriptor getLocalFunDescriptor(@NotNull String name) {
for (SimpleFunctionDescriptor descriptor : context.getSliceContents(BindingContext.FUNCTION).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local fun " + name);
return null;
}
@NotNull
protected static VariableDescriptor getLocalVarDescriptor(@NotNull BindingContext context, @NotNull String name) {
for (VariableDescriptor descriptor : context.getSliceContents(BindingContext.VARIABLE).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local variable " + name);
return null;
}
@NotNull
private SimpleFunctionDescriptor getAnonymousFunDescriptor() {
for (SimpleFunctionDescriptor descriptor : context.getSliceContents(BindingContext.FUNCTION).values()) {
if (descriptor instanceof AnonymousFunctionDescriptor) {
return descriptor;
}
}
fail("Failed to find anonymous fun");
return null;
}
@NotNull
private static ValueParameterDescriptor getConstructorParameterDescriptor(
@NotNull ClassDescriptor classDescriptor,
@NotNull String name
) {
ConstructorDescriptor constructorDescriptor = getConstructorDescriptor(classDescriptor);
ValueParameterDescriptor parameter = findValueParameter(constructorDescriptor.getValueParameters(), name);
assertNotNull("Cannot find constructor parameter with name " + name, parameter);
return parameter;
}
@NotNull
private static ConstructorDescriptor getConstructorDescriptor(@NotNull ClassDescriptor classDescriptor) {
Collection<ConstructorDescriptor> constructors = classDescriptor.getConstructors();
assert constructors.size() == 1;
return constructors.iterator().next();
}
private static ValueParameterDescriptor findValueParameter(List<ValueParameterDescriptor> parameters, String name) {
for (ValueParameterDescriptor parameter : parameters) {
if (parameter.getName().asString().equals(name)) {
return parameter;
}
}
return null;
}
@NotNull
private static ValueParameterDescriptor getFunctionParameterDescriptor(
@NotNull FunctionDescriptor functionDescriptor,
@NotNull String name
) {
ValueParameterDescriptor parameter = findValueParameter(functionDescriptor.getValueParameters(), name);
assertNotNull("Cannot find function parameter with name " + name, parameter);
return parameter;
}
@NotNull
protected KtFile getFile(@NotNull String content) {
KtFile ktFile = KotlinTestUtils.createFile("dummy.kt", content, getProject());
AnalysisResult analysisResult = KotlinTestUtils.analyzeFile(ktFile, getEnvironment());
context = analysisResult.getBindingContext();
return ktFile;
}
@NotNull
protected PackageFragmentDescriptor getPackage(@NotNull KtFile ktFile) {
PackageFragmentDescriptor packageFragment = context.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, ktFile);
assertNotNull("Failed to find package: " + PACKAGE, packageFragment);
return packageFragment;
}
@NotNull
protected PackageFragmentDescriptor getPackage(@NotNull String content) {
return getPackage(getFile(content));
}
protected static String getContent(@NotNull String annotationText) throws IOException {
File file = new File(PATH);
return KotlinTestUtils.doLoadFile(file).replaceAll("ANNOTATION", annotationText);
}
private static String renderAnnotations(Annotations annotations, @Nullable final AnnotationUseSiteTarget defaultTarget) {
return StringUtil.join(annotations.getAllAnnotations(), new Function<AnnotationWithTarget, String>() {
@Override
public String fun(AnnotationWithTarget annotationWithTarget) {
AnnotationUseSiteTarget targetToRender = annotationWithTarget.getTarget();
if (targetToRender == defaultTarget) {
targetToRender = null;
}
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(annotationWithTarget.getAnnotation(), targetToRender);
}
}, " ");
}
protected static void checkDescriptor(String expectedAnnotation, DeclarationDescriptor member) {
String actual = getAnnotations(member);
assertEquals("Failed to resolve annotation descriptor for " + member.toString(), expectedAnnotation, actual);
}
private static void checkDescriptorWithTarget(String expectedAnnotation, DeclarationDescriptor member, AnnotationUseSiteTarget target) {
String actual = renderAnnotations(member.getAnnotations(), target);
assertEquals("Failed to resolve annotation descriptor for " + member.toString(), expectedAnnotation, actual);
}
@NotNull
protected static String getAnnotations(DeclarationDescriptor member) {
return renderAnnotations(member.getAnnotations(), null);
}
@Override
public void tearDown() throws Exception {
context = null;
super.tearDown();
}
}
@@ -0,0 +1,35 @@
/*
* 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.resolve.annotation
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractAnnotationParameterTest : AbstractAnnotationDescriptorResolveTest() {
fun doTest(path: String) {
val fileText = FileUtil.loadFile(File(path), true)
val packageView = getPackage(fileText)
val classDescriptor = AbstractAnnotationDescriptorResolveTest.getClassDescriptor(packageView, "MyClass")
val expected = InTextDirectivesUtils.findListWithPrefixes(fileText, "// EXPECTED: ").joinToString(", ")
val actual = AbstractAnnotationDescriptorResolveTest.getAnnotations(classDescriptor)
KotlinTestUtils.assertEqualsToFile(File(path), fileText.replace(expected, actual))
}
}
@@ -0,0 +1,130 @@
/*
* 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.resolve.calls
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMapping
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import java.io.File
abstract class AbstractResolvedCallsTest : KotlinTestWithEnvironment() {
override fun createEnvironment(): KotlinCoreEnvironment = createEnvironmentWithMockJdk(ConfigurationKind.ALL)
fun doTest(filePath: String) {
val text = KotlinTestUtils.doLoadFile(File(filePath))!!
val jetFile = KtPsiFactory(project).createFile(text.replace("<caret>", ""))
val bindingContext = JvmResolveUtil.analyzeOneFileWithJavaIntegration(jetFile, environment).bindingContext
val (element, cachedCall) = buildCachedCall(bindingContext, jetFile, text)
val resolvedCall = if (cachedCall !is VariableAsFunctionResolvedCall) cachedCall
else if ("(" == element?.text) cachedCall.functionCall
else cachedCall.variableCall
val resolvedCallInfoFileName = FileUtil.getNameWithoutExtension(filePath) + ".txt"
KotlinTestUtils.assertEqualsToFile(File(resolvedCallInfoFileName), "$text\n\n\n${resolvedCall?.renderToText()}")
}
open protected fun buildCachedCall(
bindingContext: BindingContext, jetFile: KtFile, text: String
): Pair<PsiElement?, ResolvedCall<out CallableDescriptor>?> {
val element = jetFile.findElementAt(text.indexOf("<caret>"))!!
val expression = element.getStrictParentOfType<KtExpression>()
val cachedCall = expression?.getParentResolvedCall(bindingContext, strict = false)
return Pair(element, cachedCall)
}
}
private fun Receiver?.getText() = when (this) {
is ExpressionReceiver -> "${expression.text} {${type}}"
is ImplicitClassReceiver -> "Class{${type}}"
is ExtensionReceiver -> "${type}Ext{${declarationDescriptor.getText()}}"
null -> "NO_RECEIVER"
else -> toString()
}
private fun ValueArgument.getText() = this.getArgumentExpression()?.text?.replace("\n", " ") ?: ""
private fun ArgumentMapping.getText() = when (this) {
is ArgumentMatch -> {
val parameterType = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(valueParameter.type)
"${status.name} ${valueParameter.name} : ${parameterType} ="
}
else -> "ARGUMENT UNMAPPED: "
}
private fun DeclarationDescriptor.getText(): String = when (this) {
is ReceiverParameterDescriptor -> "${value.getText()}::this"
else -> DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(this)
}
private fun ResolvedCall<*>.renderToText(): String {
return buildString {
appendln("Resolved call:")
appendln()
if (candidateDescriptor != resultingDescriptor) {
appendln("Candidate descriptor: ${candidateDescriptor!!.getText()}")
}
appendln("Resulting descriptor: ${resultingDescriptor!!.getText()}")
appendln()
appendln("Explicit receiver kind = ${explicitReceiverKind}")
appendln("Dispatch receiver = ${dispatchReceiver.getText()}")
appendln("Extension receiver = ${extensionReceiver.getText()}")
val valueArguments = call.valueArguments
if (!valueArguments.isEmpty()) {
appendln()
appendln("Value arguments mapping:")
appendln()
for (valueArgument in valueArguments) {
val argumentText = valueArgument!!.getText()
val argumentMappingText = getArgumentMapping(valueArgument).getText()
appendln("$argumentMappingText $argumentText")
}
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.resolve.calls
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSecondaryConstructor
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
abstract class AbstractResolvedConstructorDelegationCallsTests : AbstractResolvedCallsTest() {
override fun buildCachedCall(
bindingContext: BindingContext, jetFile: KtFile, text: String
): Pair<PsiElement?, ResolvedCall<out CallableDescriptor>?> {
val element = jetFile.findElementAt(text.indexOf("<caret>"))
val constructor = element?.getNonStrictParentOfType<KtSecondaryConstructor>()!!
val delegationCall = constructor.getDelegationCall()
val cachedCall = delegationCall.getParentResolvedCall(bindingContext, strict = false)
return Pair(delegationCall, cachedCall)
}
}
@@ -0,0 +1,117 @@
/*
* 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.resolve.constants.evaluate
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.annotation.AbstractAnnotationDescriptorResolveTest
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
import java.util.regex.Pattern
abstract class AbstractCompileTimeConstantEvaluatorTest : AbstractAnnotationDescriptorResolveTest() {
// Test directives should look like [// val testedPropertyName: expectedValue]
fun doConstantTest(path: String) {
doTest(path) {
property, context ->
val compileTimeConstant = property.compileTimeInitializer
if (compileTimeConstant is StringValue) {
"\\\"${compileTimeConstant.value}\\\""
} else {
"$compileTimeConstant"
}
}
}
// Test directives should look like [// val testedPropertyName: expectedValue]
fun doIsPureTest(path: String) {
doTest(path) {
property, context ->
evaluateInitializer(context, property)?.isPure.toString()
}
}
// Test directives should look like [// val testedPropertyName: expectedValue]
fun doUsesVariableAsConstantTest(path: String) {
doTest(path) {
property, context ->
evaluateInitializer(context, property)?.usesVariableAsConstant.toString()
}
}
private fun evaluateInitializer(context: BindingContext, property: VariableDescriptor): CompileTimeConstant<*>? {
val propertyDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(property) as KtProperty
val compileTimeConstant = ConstantExpressionEvaluator(property.builtIns).evaluateExpression(
propertyDeclaration.initializer!!,
DelegatingBindingTrace(context, "trace for evaluating compile time constant"),
property.type
)
return compileTimeConstant
}
private fun doTest(path: String, getValueToTest: (VariableDescriptor, BindingContext) -> String) {
val myFile = File(path)
val fileText = FileUtil.loadFile(myFile, true)
val packageView = getPackage(fileText)
val propertiesForTest = getObjectsToTest(fileText)
val expectedActual = arrayListOf<Pair<String, String>>()
for (propertyName in propertiesForTest) {
val expectedPropertyPrefix = "// val ${propertyName}: "
val expected = InTextDirectivesUtils.findStringWithPrefixes(fileText, expectedPropertyPrefix)
assertNotNull(expected, "Failed to find expected directive: $expectedPropertyPrefix")
val property = AbstractAnnotationDescriptorResolveTest.getPropertyDescriptor(packageView, propertyName, false)
?: AbstractAnnotationDescriptorResolveTest.getLocalVarDescriptor(context!!, propertyName)
val testedObject = getValueToTest(property, context!!)
expectedActual.add(expectedPropertyPrefix + expected!! to expectedPropertyPrefix + testedObject)
}
var actualFileText = fileText
for ((expected, actual) in expectedActual) {
assert(actualFileText.contains(expected)) { "File text should contain $expected" }
actualFileText = actualFileText.replace(expected, actual)
}
KotlinTestUtils.assertEqualsToFile(myFile, actualFileText)
}
fun getObjectsToTest(fileText: String): List<String> {
return InTextDirectivesUtils.findListWithPrefixes(fileText, "// val").map {
val matcher = pattern.matcher(it)
if (matcher.find()) {
matcher.group(0) ?: "Couldn't match tested object $it"
} else "Couldn't match tested object $it"
}
}
companion object {
val pattern = Pattern.compile(".+(?=:)")
}
}
@@ -0,0 +1,149 @@
/*
* 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.resolve.constraintSystem
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.TypeResolver
import org.jetbrains.kotlin.resolve.calls.inference.CallHandle
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintContext
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.SPECIAL
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.tests.di.createContainerForTests
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
import java.io.File
abstract class AbstractConstraintSystemTest : KotlinTestWithEnvironment() {
private var _typeResolver: TypeResolver? = null
private val typeResolver: TypeResolver
get() = _typeResolver!!
private var _testDeclarations: ConstraintSystemTestData? = null
private val testDeclarations: ConstraintSystemTestData
get() = _testDeclarations!!
override fun createEnvironment(): KotlinCoreEnvironment {
return createEnvironmentWithMockJdk(ConfigurationKind.ALL)
}
override fun setUp() {
super.setUp()
_typeResolver = createContainerForTests(project, KotlinTestUtils.createEmptyModule()).typeResolver
_testDeclarations = analyzeDeclarations()
}
override fun tearDown() {
_typeResolver = null
_testDeclarations = null
super.tearDown()
}
private val testDataPath: String
get() = KotlinTestUtils.getTestDataPathBase() + "/constraintSystem/"
private fun analyzeDeclarations(): ConstraintSystemTestData {
val fileName = "declarations.kt"
val psiFile = KotlinTestUtils.createFile(fileName, KotlinTestUtils.doLoadFile(testDataPath, fileName), project)
val bindingContext = JvmResolveUtil.analyzeOneFileWithJavaIntegrationAndCheckForErrors(psiFile).bindingContext
return ConstraintSystemTestData(bindingContext, project, typeResolver)
}
fun doTest(filePath: String) {
val constraintsFile = File(filePath)
val constraintsFileText = constraintsFile.readLines()
val builder = ConstraintSystemBuilderImpl()
val variables = parseVariables(constraintsFileText)
val fixVariables = constraintsFileText.contains("FIX_VARIABLES")
val typeParameterDescriptors = variables.map { testDeclarations.getParameterDescriptor(it) }
val substitutor = builder.registerTypeVariables(CallHandle.NONE, typeParameterDescriptors)
val constraints = parseConstraints(constraintsFileText)
fun getType(typeString: String): KotlinType {
val type = testDeclarations.getType(typeString).apply {
assert(!ErrorUtils.containsErrorType(this)) { "Type $this is resolved to or contains error type" }
}
return substitutor.substitute(type, Variance.INVARIANT) ?: error("Failed to substitute $type")
}
for (constraint in constraints) {
val firstType = getType(constraint.firstType)
val secondType = getType(constraint.secondType)
val context = ConstraintContext(SPECIAL.position(), initial = true)
when (constraint.kind) {
MyConstraintKind.SUBTYPE -> builder.addSubtypeConstraint(firstType, secondType, context.position)
MyConstraintKind.SUPERTYPE -> builder.addSubtypeConstraint(secondType, firstType, context.position)
MyConstraintKind.EQUAL -> builder.addConstraint(
ConstraintSystemBuilderImpl.ConstraintKind.EQUAL, firstType, secondType, context
)
}
}
if (fixVariables) builder.fixVariables()
val system = builder.build()
val resultingStatus = Renderers.renderConstraintSystem(system, shortTypeBounds = true)
val resultingSubstitutor = system.resultingSubstitutor
val result = typeParameterDescriptors.map {
val parameterType = testDeclarations.getType(it.name.asString())
val resultType = resultingSubstitutor.substitute(parameterType, Variance.INVARIANT)
"${it.name}=${resultType?.let { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) }}"
}.joinToString("\n", prefix = "result:\n")
val boundsFile = File(filePath.replace("constraints", "bounds"))
KotlinTestUtils.assertEqualsToFile(boundsFile, "${constraintsFileText.joinToString("\n")}\n\n$resultingStatus\n\n$result")
}
class MyConstraint(val kind: MyConstraintKind, val firstType: String, val secondType: String)
enum class MyConstraintKind(val token: String) {
SUBTYPE("<:"), SUPERTYPE(">:"), EQUAL(":=")
}
private fun parseVariables(lines: List<String>): List<String> {
val first = lines.first()
val variablesString = "VARIABLES "
assert (first.startsWith(variablesString)) { "The first line should contain variables: $first"}
val variables = first.substringAfter(variablesString).split(' ')
return variables.toList()
}
private fun parseConstraints(lines: List<String>): List<MyConstraint> {
val kindsMap = MyConstraintKind.values().map { it.token to it }.toMap()
val kinds = kindsMap.keys
val linesWithConstraints = lines.filter { line -> kinds.any { kind -> line.contains(kind) } }
return linesWithConstraints.map {
line ->
val kind = kinds.first { line.contains(it) }
val firstType = line.substringBefore(kind).trim()
val secondType = line.substringAfter(kind).trim()
MyConstraint(kindsMap[kind]!!, firstType, secondType)
}
}
}
@@ -0,0 +1,78 @@
/*
* 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.resolve.constraintSystem
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.TypeResolver
import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstructor
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeImpl
import java.util.regex.Pattern
class ConstraintSystemTestData(
context: BindingContext,
private val project: Project,
private val typeResolver: TypeResolver
) {
private val functionFoo: FunctionDescriptor
private val scopeToResolveTypeParameters: LexicalScope
init {
val functions = context.getSliceContents(BindingContext.FUNCTION)
functionFoo = findFunctionByName(functions.values, "foo")
val function = DescriptorToSourceUtils.descriptorToDeclaration(functionFoo) as KtFunction
val fooBody = function.bodyExpression
scopeToResolveTypeParameters = context.get(BindingContext.LEXICAL_SCOPE, fooBody)!!
}
private fun findFunctionByName(functions: Collection<FunctionDescriptor>, name: String): FunctionDescriptor {
return functions.firstOrNull { it.name.asString() == name } ?:
throw AssertionError("Function ${name} is not declared")
}
fun getParameterDescriptor(name: String): TypeParameterDescriptor {
return functionFoo.typeParameters.firstOrNull { it.name.asString() == name } ?:
throw AssertionError("Unsupported type parameter name: $name. You may add it to constraintSystem/declarations.kt")
}
fun getType(name: String): KotlinType {
val matcher = INTEGER_VALUE_TYPE_PATTERN.matcher(name)
if (matcher.find()) {
val number = matcher.group(1)!!
return KotlinTypeImpl.create(
Annotations.EMPTY, IntegerValueTypeConstructor(number.toLong(), functionFoo.builtIns), false, listOf(),
MemberScope.Empty
)
}
return typeResolver.resolveType(
scopeToResolveTypeParameters, KtPsiFactory(project).createType(name),
KotlinTestUtils.DUMMY_TRACE, true)
}
}
private val INTEGER_VALUE_TYPE_PATTERN = Pattern.compile("""IntegerValueType\((\d*)\)""")
@@ -0,0 +1,117 @@
/*
* 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.resolve.lazy;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.context.ModuleContext;
import org.jetbrains.kotlin.descriptors.PackagePartProvider;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.AnalyzingUtils;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
import java.util.Collection;
import java.util.Collections;
public class JvmResolveUtil {
public static String TEST_MODULE_NAME = "java-integration-test";
@NotNull
public static AnalysisResult analyzeOneFileWithJavaIntegrationAndCheckForErrors(@NotNull KtFile file) {
return analyzeOneFileWithJavaIntegrationAndCheckForErrors(file, PackagePartProvider.Companion.getEMPTY());
}
@NotNull
public static AnalysisResult analyzeOneFileWithJavaIntegrationAndCheckForErrors(@NotNull KtFile file, @NotNull PackagePartProvider provider) {
AnalyzingUtils.checkForSyntacticErrors(file);
AnalysisResult analysisResult = analyzeOneFileWithJavaIntegration(file, provider);
AnalyzingUtils.throwExceptionOnErrors(analysisResult.getBindingContext());
return analysisResult;
}
@NotNull
public static AnalysisResult analyzeOneFileWithJavaIntegration(@NotNull KtFile file, @NotNull KotlinCoreEnvironment environment) {
return analyzeOneFileWithJavaIntegration(file, new JvmPackagePartProvider(environment));
}
@NotNull
public static AnalysisResult analyzeOneFileWithJavaIntegration(@NotNull KtFile file, @NotNull PackagePartProvider provider) {
return analyzeFilesWithJavaIntegration(file.getProject(), Collections.singleton(file), provider);
}
@NotNull
public static AnalysisResult analyzeOneFileWithJavaIntegration(@NotNull KtFile file) {
return analyzeOneFileWithJavaIntegration(file, PackagePartProvider.Companion.getEMPTY());
}
@NotNull
public static AnalysisResult analyzeFilesWithJavaIntegrationAndCheckForErrors(
@NotNull Project project,
@NotNull Collection<KtFile> files
) {
return analyzeFilesWithJavaIntegrationAndCheckForErrors(project, files, PackagePartProvider.Companion.getEMPTY());
}
@NotNull
public static AnalysisResult analyzeFilesWithJavaIntegrationAndCheckForErrors(
@NotNull Project project,
@NotNull Collection<KtFile> files,
@NotNull PackagePartProvider packagePartProvider
) {
for (KtFile file : files) {
AnalyzingUtils.checkForSyntacticErrors(file);
}
AnalysisResult analysisResult = analyzeFilesWithJavaIntegration(project, files, packagePartProvider);
AnalyzingUtils.throwExceptionOnErrors(analysisResult.getBindingContext());
return analysisResult;
}
@NotNull
public static AnalysisResult analyzeFilesWithJavaIntegration(
@NotNull Project project,
@NotNull Collection<KtFile> files,
@NotNull KotlinCoreEnvironment environment
) {
return analyzeFilesWithJavaIntegration(project, files, new JvmPackagePartProvider(environment));
}
@NotNull
public static AnalysisResult analyzeFilesWithJavaIntegration(
@NotNull Project project,
@NotNull Collection<KtFile> files,
@NotNull PackagePartProvider packagePartProvider
) {
ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(project, TEST_MODULE_NAME);
BindingTrace trace = new CliLightClassGenerationSupport.CliBindingTrace();
return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(moduleContext, files, trace, null, null,
packagePartProvider);
}
}
@@ -0,0 +1,96 @@
/*
* 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.resolve.lazy;
import com.google.common.collect.Sets;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport;
import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.context.ModuleContext;
import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.name.SpecialNames;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.TopDownAnalysisMode;
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class LazyResolveTestUtil {
private LazyResolveTestUtil() {
}
@NotNull
public static ModuleDescriptor resolveProject(@NotNull Project project, @NotNull KotlinCoreEnvironment environment) {
return resolve(project, Collections.<KtFile>emptyList(), environment);
}
@NotNull
public static ModuleDescriptor resolve(@NotNull Project project, @NotNull List<KtFile> sourceFiles, @NotNull KotlinCoreEnvironment environment) {
return resolve(project, new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(), sourceFiles, environment);
}
@NotNull
public static ModuleDescriptor resolve(
@NotNull Project project,
@NotNull BindingTrace trace,
@NotNull List<KtFile> sourceFiles,
@NotNull KotlinCoreEnvironment environment
) {
ModuleContext moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(project, JvmResolveUtil.TEST_MODULE_NAME);
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationNoIncremental(
moduleContext, sourceFiles, trace, TopDownAnalysisMode.TopLevelDeclarations,
new JvmPackagePartProvider(environment)
);
return moduleContext.getModule();
}
@NotNull
public static KotlinCodeAnalyzer resolveLazilyWithSession(
@NotNull List<KtFile> files,
@NotNull KotlinCoreEnvironment environment,
boolean addBuiltIns
) {
return LazyResolveTestUtilsKt.createResolveSessionForFiles(environment.getProject(), files, addBuiltIns);
}
public static ModuleDescriptor resolveLazily(List<KtFile> files, KotlinCoreEnvironment environment) {
return resolveLazily(files, environment, true);
}
public static ModuleDescriptor resolveLazily(List<KtFile> files, KotlinCoreEnvironment environment, boolean addBuiltIns) {
return resolveLazilyWithSession(files, environment, addBuiltIns).getModuleDescriptor();
}
@NotNull
public static Set<Name> getTopLevelPackagesFromFileList(@NotNull List<KtFile> files) {
Set<Name> shortNames = Sets.newLinkedHashSet();
for (KtFile file : files) {
List<Name> packageFqNameSegments = file.getPackageFqName().pathSegments();
Name name = packageFqNameSegments.isEmpty() ? SpecialNames.ROOT_PACKAGE : packageFqNameSegments.get(0);
shortNames.add(name);
}
return shortNames;
}
}
@@ -0,0 +1,56 @@
/*
* 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.resolve.lazy
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.ModuleContent
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.resolve.jvm.JvmAnalyzerFacade
import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters
fun createResolveSessionForFiles(
project: Project,
syntheticFiles: Collection<KtFile>,
addBuiltIns: Boolean
): ResolveSession {
val projectContext = ProjectContext(project)
val testModule = TestModule(addBuiltIns)
val resolverForProject = JvmAnalyzerFacade.setupResolverForProject(
"test",
projectContext, listOf(testModule),
{ ModuleContent(syntheticFiles, GlobalSearchScope.allScope(project)) },
JvmPlatformParameters { testModule },
CompilerEnvironment
)
return resolverForProject.resolverForModule(testModule).componentProvider.get<ResolveSession>()
}
private class TestModule(val dependsOnBuiltIns: Boolean) : ModuleInfo {
override val name: Name = Name.special("<Test module for lazy resolve>")
override fun dependencies() = listOf(this)
override fun dependencyOnBuiltIns() =
if (dependsOnBuiltIns)
ModuleInfo.DependenciesOnBuiltIns.LAST
else
ModuleInfo.DependenciesOnBuiltIns.NONE
}
@@ -0,0 +1,34 @@
/*
* 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.scripts
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptNameUtil
import org.jetbrains.kotlin.script.ScriptParameter
class TestScriptDefinition(
val extension: String,
val parameters: List<ScriptParameter>
) : KotlinScriptDefinition {
override fun getScriptParameters(scriptDescriptor: ScriptDescriptor) = parameters
override fun isScript(file: PsiFile): Boolean = file.name.endsWith(extension)
override fun getScriptName(script: KtScript): Name = ScriptNameUtil.fileNameWithExtensionStripped(script, extension)
}
@@ -0,0 +1,28 @@
/*
* 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.test
enum class ConfigurationKind(
val withRuntime: Boolean = false,
val withMockRuntime: Boolean = false,
val withReflection: Boolean = false
) {
JDK_ONLY(),
MOCK_RUNTIME(withMockRuntime = true),
NO_KOTLIN_REFLECT(withRuntime = true),
ALL(withRuntime = true, withReflection = true),
}
@@ -0,0 +1,200 @@
/*
* 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.test;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
public final class InTextDirectivesUtils {
private InTextDirectivesUtils() {
}
@Nullable
public static Integer getPrefixedInt(String fileText, String prefix) {
String[] strings = findArrayWithPrefixes(fileText, prefix);
if (strings.length > 0) {
assert strings.length == 1;
return Integer.parseInt(strings[0]);
}
return null;
}
@Nullable
public static Boolean getPrefixedBoolean(String fileText, String prefix) {
String[] strings = findArrayWithPrefixes(fileText, prefix);
if (strings.length > 0) {
assert strings.length == 1;
return Boolean.parseBoolean(strings[0]);
}
return null;
}
@NotNull
public static String[] findArrayWithPrefixes(@NotNull String fileText, @NotNull String... prefixes) {
return ArrayUtil.toStringArray(findListWithPrefixes(fileText, prefixes));
}
@NotNull
public static List<String> findListWithPrefixes(@NotNull String fileText, @NotNull String... prefixes) {
List<String> result = new ArrayList<String>();
for (String line : findLinesWithPrefixesRemoved(fileText, prefixes)) {
String unquoted = StringUtil.unquoteString(line);
if (!unquoted.equals(line)) {
result.add(unquoted);
}
else{
String[] variants = line.split(",");
for (String variant : variants) {
result.add(variant.trim());
}
}
}
return result;
}
public static boolean isDirectiveDefined(String fileText, String directive) {
return !findListWithPrefixes(fileText, directive).isEmpty();
}
@Nullable
public static String findStringWithPrefixes(String fileText, String... prefixes) {
List<String> strings = findListWithPrefixes(fileText, prefixes);
if (strings.isEmpty()) {
return null;
}
if (strings.size() != 1) {
throw new IllegalStateException("There is more than one string with given prefixes " +
Arrays.toString(prefixes) + ":\n" +
StringUtil.join(strings, "\n") + "\n" +
"Use findListWithPrefixes() instead.");
}
return strings.get(0);
}
@NotNull
public static List<String> findLinesWithPrefixesRemoved(String fileText, String... prefixes) {
List<String> result = new ArrayList<String>();
List<String> cleanedPrefixes = cleanDirectivesFromComments(Arrays.asList(prefixes));
for (String line : fileNonEmptyCommentedLines(fileText)) {
for (String prefix : cleanedPrefixes) {
if (line.startsWith(prefix)) {
String noPrefixLine = line.substring(prefix.length());
if (noPrefixLine.isEmpty() ||
Character.isWhitespace(noPrefixLine.charAt(0)) ||
Character.isWhitespace(prefix.charAt(prefix.length() - 1))) {
result.add(noPrefixLine.trim());
break;
} else {
throw new AssertionError(
"Line starts with prefix \"" + prefix + "\", but doesn't have space symbol after it: " + line);
}
}
}
}
return result;
}
public static void assertHasUnknownPrefixes(String fileText, Collection<String> knownPrefixes) {
Set<String> prefixes = Sets.newHashSet();
for (String line : fileNonEmptyCommentedLines(fileText)) {
String prefix = probableDirective(line);
if (prefix != null) {
prefixes.add(prefix);
}
}
prefixes.removeAll(cleanDirectivesFromComments(knownPrefixes));
Assert.assertTrue("File contains some unexpected directives" + prefixes, prefixes.isEmpty());
}
private static String probableDirective(String line) {
String[] arr = line.split(" ", 2);
String firstWord = arr[0];
if (firstWord.length() > 1 && StringUtil.toUpperCase(firstWord).equals(firstWord)) {
return firstWord;
}
return null;
}
private static List<String> cleanDirectivesFromComments(Collection<String> prefixes) {
List<String> resultPrefixes = Lists.newArrayList();
for (String prefix : prefixes) {
if (prefix.startsWith("//") || prefix.startsWith("##")) {
resultPrefixes.add(StringUtil.trimLeading(prefix.substring(2)));
}
else {
resultPrefixes.add(prefix);
}
}
return resultPrefixes;
}
@NotNull
private static List<String> fileNonEmptyCommentedLines(String fileText) {
List<String> result = new ArrayList<String>();
try {
BufferedReader reader = new BufferedReader(new StringReader(fileText));
try {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("//") || line.startsWith("##")) {
String uncommentedLine = line.substring(2).trim();
if (!uncommentedLine.isEmpty()) {
result.add(uncommentedLine);
}
}
}
} finally {
reader.close();
}
} catch(IOException e) {
throw new AssertionError(e);
}
return result;
}
}
@@ -0,0 +1,185 @@
/*
* 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.test;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.junit.internal.MethodSorter;
import org.junit.internal.runners.JUnit38ClassRunner;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.*;
import org.junit.runner.notification.RunNotifier;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* This runner runs class with all inners test classes, but monitors situation when those classes are planned to be executed
* with IDEA package test runner.
*/
public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortable {
private static final Set<Class> requestedRunners = new HashSet<Class>();
private JUnit38ClassRunner delegateRunner;
private final Class<?> klass;
private boolean isFakeTest = false;
private static class FakeEmptyClassTest implements Test {
private final Class<?> klass;
public FakeEmptyClassTest(Class<?> klass) {
this.klass = klass;
}
@Override
public int countTestCases() {
return 0;
}
@Override
public void run(TestResult result) {
result.startTest(this);
result.endTest(this);
}
public Class<?> getTestClass() {
return klass;
}
@Override
public String toString() {
return "Empty class with inners";
}
}
public JUnit3RunnerWithInners(Class<?> klass) {
this.klass = klass;
requestedRunners.add(klass);
}
@Override
public void run(RunNotifier notifier) {
initialize();
delegateRunner.run(notifier);
}
@Override
public Description getDescription() {
initialize();
return isFakeTest ? Description.EMPTY : delegateRunner.getDescription();
}
@Override
public void filter(Filter filter) throws NoTestsRemainException {
delegateRunner = new JUnit38ClassRunner(klass);
delegateRunner.filter(filter);
}
@Override
public void sort(Sorter sorter) {
initialize();
delegateRunner.sort(sorter);
}
protected void initialize() {
if (delegateRunner != null) return;
delegateRunner = new JUnit38ClassRunner(getCollectedTests());
}
protected Test getCollectedTests() {
List<Class> innerClasses = collectDeclaredClasses(klass, false);
Set<Class> unprocessedInnerClasses = unprocessedClasses(innerClasses);
if (unprocessedInnerClasses.isEmpty()) {
if (!innerClasses.isEmpty() && !hasTestMethods(klass)) {
isFakeTest = true;
return new FakeEmptyClassTest(klass);
}
else {
return new TestSuite(klass.asSubclass(TestCase.class));
}
}
else if (unprocessedInnerClasses.size() == innerClasses.size()) {
return createTreeTestSuite(klass);
}
else {
return new TestSuite(klass.asSubclass(TestCase.class));
}
}
private static Test createTreeTestSuite(Class root) {
Set<Class> classes = new LinkedHashSet<Class>(collectDeclaredClasses(root, true));
Map<Class, TestSuite> classSuites = new HashMap<Class, TestSuite>();
for (Class aClass : classes) {
classSuites.put(aClass, hasTestMethods(aClass) ? new TestSuite(aClass) : new TestSuite(aClass.getCanonicalName()));
}
for (Class aClass : classes) {
if (aClass.getEnclosingClass() != null && classes.contains(aClass.getEnclosingClass())) {
classSuites.get(aClass.getEnclosingClass()).addTest(classSuites.get(aClass));
}
}
return classSuites.get(root);
}
private static Set<Class> unprocessedClasses(Collection<Class> classes) {
Set<Class> result = new LinkedHashSet<Class>();
for (Class aClass : classes) {
if (!requestedRunners.contains(aClass)) {
result.add(aClass);
}
}
return result;
}
private static List<Class> collectDeclaredClasses(Class klass, boolean withItself) {
List<Class> result = new ArrayList<Class>();
if (withItself) {
result.add(klass);
}
for (Class aClass : klass.getDeclaredClasses()) {
result.addAll(collectDeclaredClasses(aClass, true));
}
return result;
}
private static boolean hasTestMethods(Class klass) {
for (Class currentClass = klass; Test.class.isAssignableFrom(currentClass); currentClass = currentClass.getSuperclass()) {
for (Method each : MethodSorter.getDeclaredMethods(currentClass)) {
if (isTestMethod(each)) return true;
}
}
return false;
}
private static boolean isTestMethod(Method method) {
return method.getParameterTypes().length == 0 &&
method.getName().startsWith("test") &&
method.getReturnType().equals(Void.TYPE) &&
Modifier.isPublic(method.getModifiers());
}
}
@@ -0,0 +1,969 @@
/*
* 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.test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.impl.PsiFileFactoryImpl;
import com.intellij.rt.execution.junit.FileComparisonFailure;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.testFramework.TestDataFile;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import junit.framework.TestCase;
import kotlin.collections.CollectionsKt;
import kotlin.collections.SetsKt;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.CommonConfigurationKeys;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.config.ContentRoot;
import org.jetbrains.kotlin.config.KotlinSourceRoot;
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.diagnostics.Severity;
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages;
import org.jetbrains.kotlin.idea.KotlinLanguage;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtPsiFactoryKt;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.TargetPlatform;
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics;
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.resolve.lazy.LazyResolveTestUtil;
import org.jetbrains.kotlin.storage.LockBasedStorageManager;
import org.jetbrains.kotlin.test.util.JetTestUtilsKt;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.expressions.KotlinTypeInfo;
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice;
import org.jetbrains.kotlin.util.slicedMap.SlicedMap;
import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.kotlin.utils.PathUtil;
import org.junit.Assert;
import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys.MODULE_NAME;
import static org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil.compileKotlinToDirAndGetAnalysisResult;
import static org.jetbrains.kotlin.test.ConfigurationKind.ALL;
public class KotlinTestUtils {
public static final String TEST_GENERATOR_NAME = "org.jetbrains.kotlin.generators.tests.TestsPackage";
public static final String PLEASE_REGENERATE_TESTS = "Please regenerate tests (GenerateTests.kt)";
private static final List<File> filesToDelete = new ArrayList<File>();
/**
* Syntax:
*
* // MODULE: name(dependency1, dependency2, ...)
*
* // FILE: name
*
* Several files may follow one module
*/
public static final Pattern FILE_OR_MODULE_PATTERN = Pattern.compile("(?://\\s*MODULE:\\s*(\\w+)(\\(\\w+(?:, \\w+)*\\))?\\s*)?" +
"//\\s*FILE:\\s*(.*)$", Pattern.MULTILINE);
public static final Pattern DIRECTIVE_PATTERN = Pattern.compile("^//\\s*!(\\w+)(:\\s*(.*)$)?", Pattern.MULTILINE);
public static final BindingTrace DUMMY_TRACE = new BindingTrace() {
@NotNull
@Override
public BindingContext getBindingContext() {
return new BindingContext() {
@NotNull
@Override
public Diagnostics getDiagnostics() {
return Diagnostics.Companion.getEMPTY();
}
@Override
public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
return DUMMY_TRACE.get(slice, key);
}
@NotNull
@Override
public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
return DUMMY_TRACE.getKeys(slice);
}
@NotNull
@TestOnly
@Override
public <K, V> ImmutableMap<K, V> getSliceContents(@NotNull ReadOnlySlice<K, V> slice) {
return ImmutableMap.of();
}
@Nullable
@Override
public KotlinType getType(@NotNull KtExpression expression) {
return DUMMY_TRACE.getType(expression);
}
@Override
public void addOwnDataTo(@NotNull BindingTrace trace, boolean commitDiagnostics) {
// do nothing
}
};
}
@Override
public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
}
@Override
public <K> void record(WritableSlice<K, Boolean> slice, K key) {
}
@Override
@SuppressWarnings("unchecked")
public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
if (slice == BindingContext.PROCESSED) return (V) Boolean.FALSE;
return SlicedMap.DO_NOTHING.get(slice, key);
}
@NotNull
@Override
public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
assert slice.isCollective();
return Collections.emptySet();
}
@Nullable
@Override
public KotlinType getType(@NotNull KtExpression expression) {
KotlinTypeInfo typeInfo = get(BindingContext.EXPRESSION_TYPE_INFO, expression);
return typeInfo != null ? typeInfo.getType() : null;
}
@Override
public void recordType(@NotNull KtExpression expression, @Nullable KotlinType type) {
}
@Override
public void report(@NotNull Diagnostic diagnostic) {
if (Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS.contains(diagnostic.getFactory())) {
throw new IllegalStateException("Unresolved: " + diagnostic.getPsiElement().getText());
}
}
};
public static BindingTrace DUMMY_EXCEPTION_ON_ERROR_TRACE = new BindingTrace() {
@NotNull
@Override
public BindingContext getBindingContext() {
return new BindingContext() {
@NotNull
@Override
public Diagnostics getDiagnostics() {
throw new UnsupportedOperationException();
}
@Override
public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
return DUMMY_EXCEPTION_ON_ERROR_TRACE.get(slice, key);
}
@NotNull
@Override
public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
return DUMMY_EXCEPTION_ON_ERROR_TRACE.getKeys(slice);
}
@NotNull
@TestOnly
@Override
public <K, V> ImmutableMap<K, V> getSliceContents(@NotNull ReadOnlySlice<K, V> slice) {
return ImmutableMap.of();
}
@Nullable
@Override
public KotlinType getType(@NotNull KtExpression expression) {
return DUMMY_EXCEPTION_ON_ERROR_TRACE.getType(expression);
}
@Override
public void addOwnDataTo(@NotNull BindingTrace trace, boolean commitDiagnostics) {
// do nothing
}
};
}
@Override
public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
}
@Override
public <K> void record(WritableSlice<K, Boolean> slice, K key) {
}
@Override
public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
return null;
}
@NotNull
@Override
public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
assert slice.isCollective();
return Collections.emptySet();
}
@Nullable
@Override
public KotlinType getType(@NotNull KtExpression expression) {
return null;
}
@Override
public void recordType(@NotNull KtExpression expression, @Nullable KotlinType type) {
}
@Override
public void report(@NotNull Diagnostic diagnostic) {
if (diagnostic.getSeverity() == Severity.ERROR) {
throw new IllegalStateException(DefaultErrorMessages.render(diagnostic));
}
}
};
// We suspect sequences of eight consecutive hexadecimal digits to be a package part hash code
private static final Pattern STRIP_PACKAGE_PART_HASH_PATTERN = Pattern.compile("\\$([0-9a-f]{8})");
private KotlinTestUtils() {
}
@NotNull
public static AnalysisResult analyzeFile(@NotNull KtFile file, @NotNull KotlinCoreEnvironment environment) {
return JvmResolveUtil.analyzeOneFileWithJavaIntegration(file, environment);
}
@NotNull
public static KotlinCoreEnvironment createEnvironmentWithMockJdkAndIdeaAnnotations(Disposable disposable) {
return createEnvironmentWithMockJdkAndIdeaAnnotations(disposable, ConfigurationKind.ALL);
}
@NotNull
public static KotlinCoreEnvironment createEnvironmentWithMockJdkAndIdeaAnnotations(Disposable disposable, @NotNull ConfigurationKind configurationKind) {
return createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(disposable, configurationKind, TestJdkKind.MOCK_JDK);
}
@NotNull
public static KotlinCoreEnvironment createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(
@NotNull Disposable disposable,
@NotNull ConfigurationKind configurationKind,
@NotNull TestJdkKind jdkKind
) {
return KotlinCoreEnvironment.createForTests(
disposable,
compilerConfigurationForTests(configurationKind, jdkKind, getAnnotationsJar()),
EnvironmentConfigFiles.JVM_CONFIG_FILES);
}
@NotNull
public static String getTestDataPathBase() {
return getHomeDirectory() + "/compiler/testData";
}
@NotNull
public static String getHomeDirectory() {
File resourceRoot = PathUtil.getResourcePathForClass(KotlinTestUtils.class);
return FileUtil.toSystemIndependentName(resourceRoot.getParentFile().getParentFile().getParent());
}
public static File findMockJdkRtJar() {
return new File(getHomeDirectory(), "compiler/testData/mockJDK/jre/lib/rt.jar");
}
public static File findAndroidApiJar() {
return new File(getHomeDirectory(), "dependencies/android.jar");
}
public static File getAnnotationsJar() {
return new File(getHomeDirectory(), "compiler/testData/mockJDK/jre/lib/annotations.jar");
}
public static void mkdirs(@NotNull File file) {
if (file.isDirectory()) {
return;
}
if (!file.mkdirs()) {
if (file.exists()) {
throw new IllegalStateException("Failed to create " + file + ": file exists and not a directory");
}
throw new IllegalStateException("Failed to create " + file);
}
}
@NotNull
public static File tmpDirForTest(TestCase test) throws IOException {
File answer = FileUtil.createTempDirectory(test.getClass().getSimpleName(), test.getName());
deleteOnShutdown(answer);
return answer;
}
@NotNull
public static File tmpDir(String name) throws IOException {
// we should use this form. otherwise directory will be deleted on each test
File answer = FileUtil.createTempDirectory(new File(System.getProperty("java.io.tmpdir")), name, "");
deleteOnShutdown(answer);
return answer;
}
public static void deleteOnShutdown(File file) {
if (filesToDelete.isEmpty()) {
ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
@Override
public void run() {
ShutDownTracker.invokeAndWait(true, true, new Runnable() {
@Override
public void run() {
for (File victim : filesToDelete) {
FileUtil.delete(victim);
}
}
});
}
});
}
filesToDelete.add(file);
}
@NotNull
public static KtFile createFile(@NotNull @NonNls final String name, @NotNull String text, @NotNull Project project) {
String shortName = name.substring(name.lastIndexOf('/') + 1);
shortName = shortName.substring(shortName.lastIndexOf('\\') + 1);
LightVirtualFile virtualFile = new LightVirtualFile(shortName, KotlinLanguage.INSTANCE, text) {
@NotNull
@Override
public String getPath() {
//TODO: patch LightVirtualFile
return "/" + name;
}
};
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
PsiFileFactoryImpl factory = (PsiFileFactoryImpl) PsiFileFactory.getInstance(project);
//noinspection ConstantConditions
return (KtFile) factory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false);
}
public static String doLoadFile(String myFullDataPath, String name) throws IOException {
String fullName = myFullDataPath + File.separatorChar + name;
return doLoadFile(new File(fullName));
}
public static String doLoadFile(@NotNull File file) throws IOException {
return FileUtil.loadFile(file, CharsetToolkit.UTF8, true);
}
public static String getFilePath(File file) {
return FileUtil.toSystemIndependentName(file.getPath());
}
@NotNull
public static CompilerConfiguration compilerConfigurationForTests(
@NotNull ConfigurationKind configurationKind,
@NotNull TestJdkKind jdkKind,
@NotNull File... extraClasspath
) {
return compilerConfigurationForTests(configurationKind, jdkKind, Arrays.asList(extraClasspath), Collections.<File>emptyList());
}
@NotNull
public static CompilerConfiguration compilerConfigurationForTests(
@NotNull ConfigurationKind configurationKind,
@NotNull TestJdkKind jdkKind,
@NotNull List<File> classpath,
@NotNull List<File> javaSource
) {
CompilerConfiguration configuration = new CompilerConfiguration();
JvmContentRootsKt.addJavaSourceRoots(configuration, javaSource);
if (jdkKind == TestJdkKind.MOCK_JDK) {
JvmContentRootsKt.addJvmClasspathRoot(configuration, findMockJdkRtJar());
}
else if (jdkKind == TestJdkKind.ANDROID_API) {
JvmContentRootsKt.addJvmClasspathRoot(configuration, findAndroidApiJar());
}
else {
JvmContentRootsKt.addJvmClasspathRoots(configuration, PathUtil.getJdkClassesRoots());
}
if (configurationKind.getWithRuntime()) {
JvmContentRootsKt.addJvmClasspathRoot(configuration, ForTestCompileRuntime.runtimeJarForTests());
JvmContentRootsKt.addJvmClasspathRoot(configuration, ForTestCompileRuntime.kotlinTestJarForTests());
}
else if (configurationKind.getWithMockRuntime()) {
JvmContentRootsKt.addJvmClasspathRoot(configuration, ForTestCompileRuntime.mockRuntimeJarForTests());
}
if (configurationKind.getWithReflection()) {
JvmContentRootsKt.addJvmClasspathRoot(configuration, ForTestCompileRuntime.reflectJarForTests());
}
JvmContentRootsKt.addJvmClasspathRoots(configuration, classpath);
configuration.put(MODULE_NAME, "compilerConfigurationForTests");
return configuration;
}
public static void resolveAllKotlinFiles(KotlinCoreEnvironment environment) throws IOException {
List<ContentRoot> paths = environment.getConfiguration().get(CommonConfigurationKeys.CONTENT_ROOTS);
if (paths == null) return;
List<KtFile> jetFiles = Lists.newArrayList();
for (ContentRoot root : paths) {
if (!(root instanceof KotlinSourceRoot)) continue;
String path = ((KotlinSourceRoot) root).getPath();
File file = new File(path);
if (file.isFile()) {
jetFiles.add(loadJetFile(environment.getProject(), file));
}
else {
//noinspection ConstantConditions
for (File childFile : file.listFiles()) {
if (childFile.getName().endsWith(".kt")) {
jetFiles.add(loadJetFile(environment.getProject(), childFile));
}
}
}
}
LazyResolveTestUtil.resolve(environment.getProject(), jetFiles, environment);
}
public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull Editor editor) {
String actualText = editor.getDocument().getText();
String afterText = new StringBuilder(actualText).insert(editor.getCaretModel().getOffset(), "<caret>").toString();
assertEqualsToFile(expectedFile, afterText);
}
public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull String actual) {
assertEqualsToFile(expectedFile, actual, new Function1<String, String>() {
@Override
public String invoke(String s) {
return s;
}
});
}
public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull String actual, @NotNull Function1<String, String> sanitizer) {
try {
String actualText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(actual.trim()));
if (!expectedFile.exists()) {
FileUtil.writeToFile(expectedFile, actualText);
Assert.fail("Expected data file did not exist. Generating: " + expectedFile);
}
String expected = FileUtil.loadFile(expectedFile, CharsetToolkit.UTF8, true);
String expectedText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(expected.trim()));
if (!Comparing.equal(sanitizer.invoke(expectedText), sanitizer.invoke(actualText))) {
throw new FileComparisonFailure("Actual data differs from file content: " + expectedFile.getName(),
expected, actual, expectedFile.getAbsolutePath());
}
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
public static void compileKotlinWithJava(
@NotNull List<File> javaFiles,
@NotNull List<File> ktFiles,
@NotNull File outDir,
@NotNull Disposable disposable,
@Nullable File javaErrorFile
) throws IOException {
if (!ktFiles.isEmpty()) {
compileKotlinToDirAndGetAnalysisResult(ktFiles, outDir, disposable, ALL, false);
}
else {
boolean mkdirs = outDir.mkdirs();
assert mkdirs : "Not created: " + outDir;
}
if (!javaFiles.isEmpty()) {
compileJavaFiles(javaFiles, Arrays.asList(
"-classpath", outDir.getPath() + File.pathSeparator + ForTestCompileRuntime.runtimeJarForTests(),
"-d", outDir.getPath()
), javaErrorFile);
}
}
public interface TestFileFactory<M, F> {
F createFile(@Nullable M module, @NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives);
M createModule(@NotNull String name, @NotNull List<String> dependencies);
}
public static abstract class TestFileFactoryNoModules<F> implements TestFileFactory<Void, F> {
@Override
public final F createFile(
@Nullable Void module,
@NotNull String fileName,
@NotNull String text,
@NotNull Map<String, String> directives
) {
return create(fileName, text, directives);
}
@NotNull
public abstract F create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives);
@Override
public Void createModule(@NotNull String name, @NotNull List<String> dependencies) {
return null;
}
}
@NotNull
public static <M, F> List<F> createTestFiles(String testFileName, String expectedText, TestFileFactory<M, F> factory) {
Map<String, String> directives = parseDirectives(expectedText);
List<F> testFiles = Lists.newArrayList();
Matcher matcher = FILE_OR_MODULE_PATTERN.matcher(expectedText);
if (!matcher.find()) {
// One file
testFiles.add(factory.createFile(null, testFileName, expectedText, directives));
}
else {
int processedChars = 0;
M module = null;
// Many files
while (true) {
String moduleName = matcher.group(1);
String moduleDependencies = matcher.group(2);
if (moduleName != null) {
module = factory.createModule(moduleName, parseDependencies(moduleDependencies));
}
String fileName = matcher.group(3);
int start = processedChars;
boolean nextFileExists = matcher.find();
int end;
if (nextFileExists) {
end = matcher.start();
}
else {
end = expectedText.length();
}
String fileText = expectedText.substring(start, end);
processedChars = end;
testFiles.add(factory.createFile(module, fileName, fileText, directives));
if (!nextFileExists) break;
}
assert processedChars == expectedText.length() : "Characters skipped from " +
processedChars +
" to " +
(expectedText.length() - 1);
}
return testFiles;
}
private static List<String> parseDependencies(@Nullable String dependencies) {
if (dependencies == null) return Collections.emptyList();
Matcher matcher = Pattern.compile("\\w+").matcher(dependencies);
List<String> result = new ArrayList<String>();
while (matcher.find()) {
result.add(matcher.group());
}
return result;
}
@NotNull
public static Map<String, String> parseDirectives(String expectedText) {
Map<String, String> directives = Maps.newHashMap();
Matcher directiveMatcher = DIRECTIVE_PATTERN.matcher(expectedText);
int start = 0;
while (directiveMatcher.find()) {
if (directiveMatcher.start() != start) {
Assert.fail("Directives should only occur at the beginning of a file: " + directiveMatcher.group());
}
String name = directiveMatcher.group(1);
String value = directiveMatcher.group(3);
String oldValue = directives.put(name, value);
Assert.assertNull("Directive overwritten: " + name + " old value: " + oldValue + " new value: " + value, oldValue);
start = directiveMatcher.end() + 1;
}
return directives;
}
public static List<String> loadBeforeAfterText(String filePath) {
String content;
try {
content = FileUtil.loadFile(new File(filePath), true);
}
catch (IOException e) {
throw new RuntimeException(e);
}
List<String> files = createTestFiles("", content, new TestFileFactoryNoModules<String>() {
@NotNull
@Override
public String create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
int firstLineEnd = text.indexOf('\n');
return StringUtil.trimTrailing(text.substring(firstLineEnd + 1));
}
});
Assert.assertTrue("Exactly two files expected: ", files.size() == 2);
return files;
}
public static String getLastCommentedLines(@NotNull Document document) {
List<CharSequence> resultLines = new ArrayList<CharSequence>();
for (int i = document.getLineCount() - 1; i >= 0; i--) {
int lineStart = document.getLineStartOffset(i);
int lineEnd = document.getLineEndOffset(i);
if (document.getCharsSequence().subSequence(lineStart, lineEnd).toString().trim().isEmpty()) {
continue;
}
if ("//".equals(document.getCharsSequence().subSequence(lineStart, lineStart + 2).toString())) {
resultLines.add(document.getCharsSequence().subSequence(lineStart + 2, lineEnd));
}
else {
break;
}
}
Collections.reverse(resultLines);
StringBuilder result = new StringBuilder();
for (CharSequence line : resultLines) {
result.append(line).append("\n");
}
result.delete(result.length() - 1, result.length());
return result.toString();
}
public enum CommentType {
ALL,
LINE_COMMENT,
BLOCK_COMMENT
}
@NotNull
public static String getLastCommentInFile(@NotNull KtFile file) {
return CollectionsKt.first(getLastCommentsInFile(file, CommentType.ALL, true));
}
@NotNull
public static List<String> getLastCommentsInFile(@NotNull KtFile file, CommentType commentType, boolean assertMustExist) {
PsiElement lastChild = file.getLastChild();
if (lastChild != null && lastChild.getNode().getElementType().equals(KtTokens.WHITE_SPACE)) {
lastChild = lastChild.getPrevSibling();
}
assert lastChild != null;
List<String> comments = ContainerUtil.newArrayList();
while (true) {
if (lastChild.getNode().getElementType().equals(KtTokens.BLOCK_COMMENT)) {
if (commentType == CommentType.ALL || commentType == CommentType.BLOCK_COMMENT) {
String lastChildText = lastChild.getText();
comments.add(lastChildText.substring(2, lastChildText.length() - 2).trim());
}
}
else if (lastChild.getNode().getElementType().equals(KtTokens.EOL_COMMENT)) {
if (commentType == CommentType.ALL || commentType == CommentType.LINE_COMMENT) {
comments.add(lastChild.getText().substring(2).trim());
}
}
else {
break;
}
lastChild = lastChild.getPrevSibling();
}
if (comments.isEmpty() && assertMustExist) {
throw new AssertionError(String.format(
"Test file '%s' should end in a comment of type %s; last node was: %s", file.getName(), commentType, lastChild));
}
return comments;
}
public static void compileJavaFiles(@NotNull Collection<File> files, List<String> options) throws IOException {
compileJavaFiles(files, options, null);
}
public static void compileJavaFiles(@NotNull Collection<File> files, List<String> options, @Nullable File javaErrorFile) throws IOException {
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(diagnosticCollector, Locale.ENGLISH, Charset.forName("utf-8"));
try {
Iterable<? extends JavaFileObject> javaFileObjectsFromFiles = fileManager.getJavaFileObjectsFromFiles(files);
JavaCompiler.CompilationTask task = javaCompiler.getTask(
new StringWriter(), // do not write to System.err
fileManager,
diagnosticCollector,
options,
null,
javaFileObjectsFromFiles);
Boolean success = task.call(); // do NOT inline this variable, call() should complete before errorsToString()
if (javaErrorFile == null || !javaErrorFile.exists()) {
Assert.assertTrue(errorsToString(diagnosticCollector, true), success);
}
else {
assertEqualsToFile(javaErrorFile, errorsToString(diagnosticCollector, false));
}
} finally {
fileManager.close();
}
}
@NotNull
private static String errorsToString(@NotNull DiagnosticCollector<JavaFileObject> diagnosticCollector, boolean humanReadable) {
StringBuilder builder = new StringBuilder();
for (javax.tools.Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {
if (diagnostic.getKind() != javax.tools.Diagnostic.Kind.ERROR) continue;
if (humanReadable) {
builder.append(diagnostic).append("\n");
}
else {
builder.append(diagnostic.getSource().getName()).append(":")
.append(diagnostic.getLineNumber()).append(":")
.append(diagnostic.getColumnNumber()).append(":")
.append(diagnostic.getCode()).append("\n");
}
}
return builder.toString();
}
public static String navigationMetadata(@TestDataFile String testFile) {
return testFile;
}
public static String getTestsRoot(@NotNull Class<?> testCaseClass) {
TestMetadata testClassMetadata = testCaseClass.getAnnotation(TestMetadata.class);
Assert.assertNotNull("No metadata for class: " + testCaseClass, testClassMetadata);
return testClassMetadata.value();
}
public static void assertAllTestsPresentByMetadata(
@NotNull Class<?> testCaseClass,
@NotNull File testDataDir,
@NotNull Pattern filenamePattern,
boolean recursive,
@NotNull String... excludeDirs
) {
File rootFile = new File(getTestsRoot(testCaseClass));
Set<String> filePaths = collectPathsMetadata(testCaseClass);
Set<String> exclude = SetsKt.setOf(excludeDirs);
File[] files = testDataDir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
if (recursive && containsTestData(file, filenamePattern) && !exclude.contains(file.getName())) {
assertTestClassPresentByMetadata(testCaseClass, file);
}
}
else if (filenamePattern.matcher(file.getName()).matches()) {
assertFilePathPresent(file, rootFile, filePaths);
}
}
}
}
public static void assertAllTestsPresentInSingleGeneratedClass(
@NotNull Class<?> testCaseClass,
@NotNull File testDataDir,
@NotNull final Pattern filenamePattern
) {
final File rootFile = new File(getTestsRoot(testCaseClass));
final Set<String> filePaths = collectPathsMetadata(testCaseClass);
FileUtil.processFilesRecursively(testDataDir, new Processor<File>() {
@Override
public boolean process(File file) {
if (file.isFile() && filenamePattern.matcher(file.getName()).matches()) {
assertFilePathPresent(file, rootFile, filePaths);
}
return true;
}
});
}
private static void assertFilePathPresent(File file, File rootFile, Set<String> filePaths) {
String path = FileUtil.getRelativePath(rootFile, file);
if (path != null) {
String relativePath = FileUtil.nameToCompare(path);
if (!filePaths.contains(relativePath)) {
Assert.fail("Test data file missing from the generated test class: " + file + "\n" + PLEASE_REGENERATE_TESTS);
}
}
}
private static Set<String> collectPathsMetadata(Class<?> testCaseClass) {
return ContainerUtil.newHashSet(
ContainerUtil.map(collectMethodsMetadata(testCaseClass), new Function<String, String>() {
@Override
public String fun(String pathData) {
return FileUtil.nameToCompare(pathData);
}
}));
}
private static Set<String> collectMethodsMetadata(Class<?> testCaseClass) {
Set<String> filePaths = Sets.newHashSet();
for (Method method : testCaseClass.getDeclaredMethods()) {
TestMetadata testMetadata = method.getAnnotation(TestMetadata.class);
if (testMetadata != null) {
filePaths.add(testMetadata.value());
}
}
return filePaths;
}
private static boolean containsTestData(File dir, Pattern filenamePattern) {
File[] files = dir.listFiles();
assert files != null;
for (File file : files) {
if (file.isDirectory()) {
if (containsTestData(file, filenamePattern)) {
return true;
}
}
else {
if (filenamePattern.matcher(file.getName()).matches()) {
return true;
}
}
}
return false;
}
private static void assertTestClassPresentByMetadata(@NotNull Class<?> outerClass, @NotNull File testDataDir) {
for (Class<?> nestedClass : outerClass.getDeclaredClasses()) {
TestMetadata testMetadata = nestedClass.getAnnotation(TestMetadata.class);
if (testMetadata != null && testMetadata.value().equals(getFilePath(testDataDir))) {
return;
}
}
Assert.fail("Test data directory missing from the generated test class: " + testDataDir + "\n" + PLEASE_REGENERATE_TESTS);
}
@NotNull
public static KtFile loadJetFile(@NotNull Project project, @NotNull File ioFile) throws IOException {
String text = FileUtil.loadFile(ioFile, true);
return KtPsiFactoryKt.KtPsiFactory(project).createPhysicalFile(ioFile.getName(), text);
}
@NotNull
public static List<KtFile> loadToJetFiles(@NotNull KotlinCoreEnvironment environment, @NotNull List<File> files) throws IOException {
List<KtFile> jetFiles = Lists.newArrayList();
for (File file : files) {
jetFiles.add(loadJetFile(environment.getProject(), file));
}
return jetFiles;
}
@NotNull
public static ModuleDescriptorImpl createEmptyModule() {
return createEmptyModule("<empty-for-test>");
}
@NotNull
public static ModuleDescriptorImpl createEmptyModule(@NotNull String name) {
return createEmptyModule(name, JvmPlatform.INSTANCE);
}
@NotNull
public static ModuleDescriptorImpl createEmptyModule(@NotNull String name, @NotNull TargetPlatform platform) {
return new ModuleDescriptorImpl(
Name.special(name), LockBasedStorageManager.NO_LOCKS, platform.getDefaultModuleParameters(), platform.getBuiltIns()
);
}
@NotNull
public static File replaceExtension(@NotNull File file, @Nullable String newExtension) {
return new File(file.getParentFile(), FileUtil.getNameWithoutExtension(file) + (newExtension == null ? "" : "." + newExtension));
}
@NotNull
public static String replaceHashWithStar(@NotNull String string) {
return replaceHash(string, "*");
}
public static String replaceHash(@NotNull String string, @NotNull String replacement) {
//TODO: hashes are still used in SamWrapperCodegen
Matcher matcher = STRIP_PACKAGE_PART_HASH_PATTERN.matcher(string);
if (matcher.find()) {
return matcher.replaceAll("\\$" + replacement);
}
return string;
}
public static boolean isAllFilesPresentTest(String testName) {
//noinspection SpellCheckingInspection
return testName.toLowerCase().startsWith("allfilespresentin");
}
}
@@ -0,0 +1,76 @@
/*
* 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.test;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.lang.reflect.Field;
public abstract class KotlinTestWithEnvironment extends KotlinTestWithEnvironmentManagement {
private KotlinCoreEnvironment environment;
private Application application;
@Override
protected void setUp() throws Exception {
application = ApplicationManager.getApplication();
super.setUp();
environment = createEnvironment();
}
@Override
protected void tearDown() throws Exception {
removeEnvironment();
environment = null;
super.tearDown();
if (application == null) {
resetApplicationToNull();
}
application = null;
}
protected void resetApplicationToNull() {
try {
Field ourApplicationField = ApplicationManager.class.getDeclaredField("ourApplication");
ourApplicationField.setAccessible(true);
ourApplicationField.set(null, null);
}
catch (Exception e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
protected abstract KotlinCoreEnvironment createEnvironment() throws Exception;
protected void removeEnvironment() throws Exception {}
@NotNull
public KotlinCoreEnvironment getEnvironment() {
return environment;
}
@NotNull
public Project getProject() {
return getEnvironment().getProject();
}
}
@@ -0,0 +1,39 @@
/*
* 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.test;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
import org.jetbrains.kotlin.types.DelegatingFlexibleType;
public abstract class KotlinTestWithEnvironmentManagement extends KtUsefulTestCase {
static {
System.setProperty("java.awt.headless", "true");
DelegatingFlexibleType.RUN_SLOW_ASSERTIONS = true;
}
@NotNull
protected KotlinCoreEnvironment createEnvironmentWithMockJdk(@NotNull ConfigurationKind configurationKind) {
return createEnvironmentWithJdk(configurationKind, TestJdkKind.MOCK_JDK);
}
@NotNull
protected KotlinCoreEnvironment createEnvironmentWithJdk(@NotNull ConfigurationKind configurationKind, @NotNull TestJdkKind jdkKind) {
return KotlinTestUtils.createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(getTestRootDisposable(), configurationKind, jdkKind);
}
}
@@ -0,0 +1,263 @@
/*
* 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.test;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.io.ZipUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.ExitCode;
import org.jetbrains.kotlin.cli.js.K2JSCompiler;
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.preloading.ClassPreloadingUtils;
import org.jetbrains.kotlin.preloading.Preloader;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.kotlin.utils.PathUtil;
import java.io.*;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import java.util.zip.ZipOutputStream;
import static org.junit.Assert.assertEquals;
public class MockLibraryUtil {
private static SoftReference<ClassLoader> compilerClassLoader = new SoftReference<ClassLoader>(null);
@NotNull
public static File compileLibraryToJar(
@NotNull String sourcesPath,
@NotNull String jarName,
boolean addSources,
boolean isJsLibrary,
boolean allowKotlinPackage,
@NotNull String... extraClasspath
) {
if (isJsLibrary) {
return compileJsLibraryToJar(sourcesPath, jarName, addSources);
}
else {
return compileLibraryToJar(sourcesPath, jarName, addSources, allowKotlinPackage, extraClasspath);
}
}
@NotNull
public static File compileLibraryToJar(
@NotNull String sourcesPath,
@NotNull String jarName,
boolean addSources,
boolean allowKotlinPackage,
@NotNull String... extraClasspath
) {
try {
File contentDir = KotlinTestUtils.tmpDir("testLibrary-" + jarName);
File classesDir = new File(contentDir, "classes");
File srcFile = new File(sourcesPath);
List<File> kotlinFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.kt"), srcFile);
if (srcFile.isFile() || !kotlinFiles.isEmpty()) {
compileKotlin(sourcesPath, classesDir, allowKotlinPackage, extraClasspath);
}
List<File> javaFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.java"), srcFile);
if (!javaFiles.isEmpty()) {
List<String> classpath = new ArrayList<String>();
classpath.add(ForTestCompileRuntime.runtimeJarForTests().getPath());
classpath.add(KotlinTestUtils.getAnnotationsJar().getPath());
Collections.addAll(classpath, extraClasspath);
// Probably no kotlin files were present, so dir might not have been created after kotlin compiler
if (classesDir.exists()) {
classpath.add(classesDir.getPath());
}
else {
FileUtil.createDirectory(classesDir);
}
List<String> options = Arrays.asList(
"-classpath", StringUtil.join(classpath, File.pathSeparator),
"-d", classesDir.getPath()
);
KotlinTestUtils.compileJavaFiles(javaFiles, options);
}
return createJarFile(contentDir, classesDir, sourcesPath, jarName, addSources);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
public static File compileJsLibraryToJar(
@NotNull String sourcesPath,
@NotNull String jarName,
boolean addSources
) {
try {
File contentDir = KotlinTestUtils.tmpDir("testLibrary-" + jarName);
File outDir = new File(contentDir, "out");
File outputFile = new File(outDir, jarName + ".js");
File outputMetaFile = new File(outDir, jarName + ".meta.js");
compileKotlin2JS(sourcesPath, outputFile, outputMetaFile, true);
return createJarFile(contentDir, outDir, sourcesPath, jarName, addSources);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
private static File createJarFile(File contentDir, File dirToAdd, String sourcesPath, String jarName, boolean addSources) throws IOException {
File jarFile = new File(contentDir, jarName + ".jar");
ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(jarFile));
ZipUtil.addDirToZipRecursively(zip, jarFile, dirToAdd, "", null, null);
if (addSources) {
ZipUtil.addDirToZipRecursively(zip, jarFile, new File(sourcesPath), "src", null, null);
}
zip.close();
return jarFile;
}
private static void runJvmCompiler(@NotNull List<String> args) {
runCompiler(getCompiler2JVMClass(), args);
}
private static void runJsCompiler(@NotNull List<String> args) {
runCompiler(getCompiler2JSClass(), args);
}
// Runs compiler in custom class loader to avoid effects caused by replacing Application with another one created in compiler.
private static void runCompiler(@NotNull Class<?> compilerClass, @NotNull List<String> args) {
try {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
Object compiler = compilerClass.newInstance();
Method execMethod = compilerClass.getMethod("exec", PrintStream.class, String[].class);
Enum<?> invocationResult = (Enum<?>) execMethod.invoke(compiler, new PrintStream(outStream), ArrayUtil.toStringArray(args));
assertEquals(new String(outStream.toByteArray()), ExitCode.OK.name(), invocationResult.name());
}
catch (Throwable e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
public static void compileKotlin(@NotNull String sourcesPath, @NotNull File outDir, @NotNull String... extraClasspath) {
compileKotlin(sourcesPath, outDir, false, extraClasspath);
}
public static void compileKotlin(
@NotNull String sourcesPath,
@NotNull File outDir,
boolean allowKotlinPackage,
@NotNull String... extraClasspath
) {
List<String> classpath = new ArrayList<String>();
if (new File(sourcesPath).isDirectory()) {
classpath.add(sourcesPath);
}
Collections.addAll(classpath, extraClasspath);
List<String> args = new ArrayList<String>();
args.add(sourcesPath);
args.add("-d");
args.add(outDir.getAbsolutePath());
args.add("-classpath");
args.add(StringUtil.join(classpath, File.pathSeparator));
if (allowKotlinPackage) {
args.add("-Xallow-kotlin-package");
}
runJvmCompiler(args);
}
public static void compileKotlin2JS(@NotNull String sourcesPath, @NotNull File outputFile, @Nullable File metaFile, boolean kjsm) {
List<String> args = new ArrayList<String>();
if (metaFile != null) {
args.add("-meta-info");
}
if (kjsm) {
args.add("-kjsm");
}
args.add("-output");
args.add(outputFile.getAbsolutePath());
args.add(sourcesPath);
runJsCompiler(args);
}
public static void compileKotlinModule(@NotNull String modulePath) {
runJvmCompiler(Arrays.asList("-no-stdlib", "-module", modulePath));
}
@NotNull
private static synchronized Class<?> getCompiler2JVMClass() {
return loadCompilerClass(K2JVMCompiler.class.getName());
}
@NotNull
private static synchronized Class<?> getCompiler2JSClass() {
return loadCompilerClass(K2JSCompiler.class.getName());
}
private static synchronized Class<?> loadCompilerClass(String compilerClassName) {
try {
ClassLoader classLoader = compilerClassLoader.get();
if (classLoader == null) {
classLoader = createCompilerClassLoader();
compilerClassLoader = new SoftReference<ClassLoader>(classLoader);
}
return classLoader.loadClass(compilerClassName);
}
catch (Throwable e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
private static synchronized ClassLoader createCompilerClassLoader() {
try {
File kotlinCompilerJar = new File(PathUtil.getKotlinPathsForDistDirectory().getLibPath(), "kotlin-compiler.jar");
return ClassPreloadingUtils.preloadClasses(
Collections.singletonList(kotlinCompilerJar), Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE, null, null, null
);
}
catch (Throwable e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
private MockLibraryUtil() {
}
}
@@ -0,0 +1,31 @@
/*
* 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.test;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
import java.io.File;
public abstract class TestCaseWithTmpdir extends KtUsefulTestCase {
protected File tmpdir;
@Override
protected void setUp() throws Exception {
super.setUp();
tmpdir = KotlinTestUtils.tmpDirForTest(this);
}
}
@@ -0,0 +1,23 @@
/*
* 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.test;
public enum TestJdkKind {
MOCK_JDK,
FULL_JDK,
ANDROID_API,
}
@@ -0,0 +1,28 @@
/*
* 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.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface TestMetadata {
String value();
}
@@ -0,0 +1,48 @@
/*
* 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.test.testFramework
import org.jetbrains.annotations.TestOnly
import java.lang.reflect.InvocationTargetException
import javax.swing.SwingUtilities
class EdtTestUtil {
companion object {
@TestOnly @JvmStatic fun runInEdtAndWait(runnable: Runnable) {
runInEdtAndWait { runnable.run() }
}
}
}
// Test only because in production you must use Application.invokeAndWait(Runnable, ModalityState).
// The problem is - Application logs errors, but not throws. But in tests must be thrown.
// In any case name "runInEdtAndWait" is better than "invokeAndWait".
@TestOnly
fun runInEdtAndWait(runnable: () -> Unit) {
if (SwingUtilities.isEventDispatchThread()) {
runnable()
}
else {
try {
SwingUtilities.invokeAndWait(runnable)
}
catch (e: InvocationTargetException) {
throw e.cause ?: e
}
}
}
@@ -0,0 +1,340 @@
/*
* Copyright 2000-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.test.testFramework;
import com.intellij.core.CoreASTFactory;
import com.intellij.lang.*;
import com.intellij.lang.impl.PsiBuilderFactoryImpl;
import com.intellij.mock.MockFileDocumentManagerImpl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.options.SchemesManagerFactory;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.impl.CoreProgressManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.pom.PomModel;
import com.intellij.pom.core.impl.PomModelImpl;
import com.intellij.pom.tree.TreeAspect;
import com.intellij.psi.*;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.impl.PsiCachedValuesFactory;
import com.intellij.psi.impl.PsiFileFactoryImpl;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl;
import com.intellij.psi.impl.source.text.BlockSupportImpl;
import com.intellij.psi.impl.source.text.DiffLog;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.testFramework.TestDataFile;
import com.intellij.util.CachedValuesManagerImpl;
import com.intellij.util.Function;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusFactory;
import junit.framework.TestCase;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.test.testFramework.mock.*;
import org.picocontainer.ComponentAdapter;
import org.picocontainer.MutablePicoContainer;
import java.io.File;
import java.io.IOException;
import java.util.Set;
@SuppressWarnings("ALL")
public abstract class KtParsingTestCase extends KtPlatformLiteFixture {
public static final Key<Document> HARD_REF_TO_DOCUMENT_KEY = Key.create("HARD_REF_TO_DOCUMENT_KEY");
protected String myFilePrefix = "";
protected String myFileExt;
protected final String myFullDataPath;
protected PsiFile myFile;
private MockPsiManager myPsiManager;
private PsiFileFactoryImpl myFileFactory;
protected Language myLanguage;
private final ParserDefinition[] myDefinitions;
private final boolean myLowercaseFirstLetter;
protected KtParsingTestCase(@NonNls @NotNull String dataPath, @NotNull String fileExt, @NotNull ParserDefinition... definitions) {
this(dataPath, fileExt, false, definitions);
}
protected KtParsingTestCase(@NonNls @NotNull String dataPath, @NotNull String fileExt, boolean lowercaseFirstLetter, @NotNull ParserDefinition... definitions) {
myDefinitions = definitions;
myFullDataPath = getTestDataPath() + "/" + dataPath;
myFileExt = fileExt;
myLowercaseFirstLetter = lowercaseFirstLetter;
}
@Override
protected void setUp() throws Exception {
super.setUp();
initApplication();
ComponentAdapter component = getApplication().getPicoContainer().getComponentAdapter(ProgressManager.class.getName());
Extensions.registerAreaClass("IDEA_PROJECT", null);
myProject = new MockProjectEx(getTestRootDisposable());
myPsiManager = new MockPsiManager(myProject);
myFileFactory = new PsiFileFactoryImpl(myPsiManager);
MutablePicoContainer appContainer = getApplication().getPicoContainer();
registerComponentInstance(appContainer, MessageBus.class, MessageBusFactory.newMessageBus(getApplication()));
registerComponentInstance(appContainer, SchemesManagerFactory.class, new MockSchemesManagerFactory());
final MockEditorFactory editorFactory = new MockEditorFactory();
registerComponentInstance(appContainer, EditorFactory.class, editorFactory);
registerComponentInstance(appContainer, FileDocumentManager.class, new MockFileDocumentManagerImpl(new Function<CharSequence, Document>() {
@Override
public Document fun(CharSequence charSequence) {
return editorFactory.createDocument(charSequence);
}
}, HARD_REF_TO_DOCUMENT_KEY));
registerComponentInstance(appContainer, PsiDocumentManager.class, new MockPsiDocumentManager());
registerApplicationService(PsiBuilderFactory.class, new PsiBuilderFactoryImpl());
registerApplicationService(DefaultASTFactory.class, new CoreASTFactory());
registerApplicationService(ReferenceProvidersRegistry.class, new ReferenceProvidersRegistryImpl());
registerApplicationService(ProgressManager.class, new CoreProgressManager());
myProject.registerService(CachedValuesManager.class, new CachedValuesManagerImpl(myProject, new PsiCachedValuesFactory(myPsiManager)));
myProject.registerService(PsiManager.class, myPsiManager);
this.registerExtensionPoint(FileTypeFactory.FILE_TYPE_FACTORY_EP, FileTypeFactory.class);
for (ParserDefinition definition : myDefinitions) {
addExplicitExtension(LanguageParserDefinitions.INSTANCE, definition.getFileNodeType().getLanguage(), definition);
}
if (myDefinitions.length > 0) {
configureFromParserDefinition(myDefinitions[0], myFileExt);
}
// That's for reparse routines
final PomModelImpl pomModel = new PomModelImpl(myProject);
myProject.registerService(PomModel.class, pomModel);
new TreeAspect(pomModel);
}
public void configureFromParserDefinition(ParserDefinition definition, String extension) {
myLanguage = definition.getFileNodeType().getLanguage();
myFileExt = extension;
addExplicitExtension(LanguageParserDefinitions.INSTANCE, this.myLanguage, definition);
registerComponentInstance(
getApplication().getPicoContainer(), FileTypeManager.class,
new KtMockFileTypeManager(new KtMockLanguageFileType(myLanguage, myFileExt)));
}
protected <T> void addExplicitExtension(final LanguageExtension<T> instance, final Language language, final T object) {
instance.addExplicitExtension(language, object);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
instance.removeExplicitExtension(language, object);
}
});
}
@Override
protected <T> void registerExtensionPoint(final ExtensionPointName<T> extensionPointName, Class<T> aClass) {
super.registerExtensionPoint(extensionPointName, aClass);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
Extensions.getRootArea().unregisterExtensionPoint(extensionPointName.getName());
}
});
}
protected <T> void registerApplicationService(final Class<T> aClass, T object) {
getApplication().registerService(aClass, object);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
getApplication().getPicoContainer().unregisterComponent(aClass.getName());
}
});
}
public MockProjectEx getProject() {
return myProject;
}
public MockPsiManager getPsiManager() {
return myPsiManager;
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
myFile = null;
myProject = null;
myPsiManager = null;
}
protected String getTestDataPath() {
return PathManager.getHomePath();
}
@NotNull
public final String getTestName() {
return getTestName(myLowercaseFirstLetter);
}
protected boolean includeRanges() {
return false;
}
protected boolean skipSpaces() {
return false;
}
protected boolean checkAllPsiRoots() {
return true;
}
protected void doTest(boolean checkResult) {
String name = getTestName();
try {
String text = loadFile(name + "." + myFileExt);
myFile = createPsiFile(name, text);
ensureParsed(myFile);
assertEquals("light virtual file text mismatch", text, ((LightVirtualFile)myFile.getVirtualFile()).getContent().toString());
assertEquals("virtual file text mismatch", text, LoadTextUtil.loadText(myFile.getVirtualFile()));
assertEquals("doc text mismatch", text, myFile.getViewProvider().getDocument().getText());
assertEquals("psi text mismatch", text, myFile.getText());
ensureCorrectReparse(myFile);
if (checkResult){
checkResult(name, myFile);
}
else{
toParseTreeText(myFile, skipSpaces(), includeRanges());
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void doTest(String suffix) throws IOException {
String name = getTestName();
String text = loadFile(name + "." + myFileExt);
myFile = createPsiFile(name, text);
ensureParsed(myFile);
assertEquals(text, myFile.getText());
checkResult(name + suffix, myFile);
}
protected void doCodeTest(String code) throws IOException {
String name = getTestName();
myFile = createPsiFile("a", code);
ensureParsed(myFile);
assertEquals(code, myFile.getText());
checkResult(myFilePrefix + name, myFile);
}
protected PsiFile createPsiFile(String name, String text) {
return createFile(name + "." + myFileExt, text);
}
protected PsiFile createFile(@NonNls String name, String text) {
LightVirtualFile virtualFile = new LightVirtualFile(name, myLanguage, text);
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
return createFile(virtualFile);
}
protected PsiFile createFile(LightVirtualFile virtualFile) {
return myFileFactory.trySetupPsiForFile(virtualFile, myLanguage, true, false);
}
protected void checkResult(@NonNls @TestDataFile String targetDataName, final PsiFile file) throws IOException {
doCheckResult(myFullDataPath, file, checkAllPsiRoots(), targetDataName, skipSpaces(), includeRanges());
}
public static void doCheckResult(String testDataDir,
PsiFile file,
boolean checkAllPsiRoots,
String targetDataName,
boolean skipSpaces,
boolean printRanges) throws IOException {
FileViewProvider provider = file.getViewProvider();
Set<Language> languages = provider.getLanguages();
if (!checkAllPsiRoots || languages.size() == 1) {
doCheckResult(testDataDir, targetDataName + ".txt", toParseTreeText(file, skipSpaces, printRanges).trim());
return;
}
for (Language language : languages) {
PsiFile root = provider.getPsi(language);
String expectedName = targetDataName + "." + language.getID() + ".txt";
doCheckResult(testDataDir, expectedName, toParseTreeText(root, skipSpaces, printRanges).trim());
}
}
protected void checkResult(String actual) throws IOException {
String name = getTestName();
doCheckResult(myFullDataPath, myFilePrefix + name + ".txt", actual);
}
protected void checkResult(@TestDataFile @NonNls String targetDataName, String actual) throws IOException {
doCheckResult(myFullDataPath, targetDataName, actual);
}
public static void doCheckResult(String fullPath, String targetDataName, String actual) throws IOException {
String expectedFileName = fullPath + File.separatorChar + targetDataName;
KtUsefulTestCase.assertSameLinesWithFile(expectedFileName, actual);
}
protected static String toParseTreeText(PsiElement file, boolean skipSpaces, boolean printRanges) {
return DebugUtil.psiToString(file, skipSpaces, printRanges);
}
protected String loadFile(@NonNls @TestDataFile String name) throws IOException {
return loadFileDefault(myFullDataPath, name);
}
public static String loadFileDefault(String dir, String name) throws IOException {
return FileUtil.loadFile(new File(dir, name), CharsetToolkit.UTF8, true).trim();
}
public static void ensureParsed(PsiFile file) {
file.accept(new PsiElementVisitor() {
@Override
public void visitElement(PsiElement element) {
element.acceptChildren(this);
}
});
}
public static void ensureCorrectReparse(@NotNull PsiFile file) {
String psiToStringDefault = DebugUtil.psiToString(file, false, false);
String fileText = file.getText();
DiffLog diffLog = (new BlockSupportImpl(file.getProject())).reparseRange(file, TextRange.allOf(fileText), fileText, new EmptyProgressIndicator(), fileText);
diffLog.performActualPsiChange(file);
TestCase.assertEquals(psiToStringDefault, DebugUtil.psiToString(file, false, false));
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2000-2014 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.test.testFramework;
import com.intellij.core.CoreEncodingProjectManager;
import com.intellij.mock.MockApplicationEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.ExtensionsArea;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import org.picocontainer.MutablePicoContainer;
import java.lang.reflect.Modifier;
public abstract class KtPlatformLiteFixture extends KtUsefulTestCase {
protected MockProjectEx myProject;
@Override
protected void setUp() throws Exception {
super.setUp();
Extensions.cleanRootArea(getTestRootDisposable());
}
public static MockApplicationEx getApplication() {
return (MockApplicationEx)ApplicationManager.getApplication();
}
public void initApplication() {
MockApplicationEx instance = new MockApplicationEx(getTestRootDisposable());
ApplicationManager.setApplication(instance,
new Getter<FileTypeRegistry>() {
@Override
public FileTypeRegistry get() {
return FileTypeManager.getInstance();
}
},
getTestRootDisposable());
getApplication().registerService(EncodingManager.class, CoreEncodingProjectManager.class);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
clearFields(this);
myProject = null;
}
protected <T> void registerExtensionPoint(ExtensionPointName<T> extensionPointName, Class<T> aClass) {
registerExtensionPoint(Extensions.getRootArea(), extensionPointName, aClass);
}
private static <T> void registerExtensionPoint(
ExtensionsArea area, ExtensionPointName<T> extensionPointName,
Class<? extends T> aClass
) {
String name = extensionPointName.getName();
if (!area.hasExtensionPoint(name)) {
ExtensionPoint.Kind kind = aClass.isInterface() || (aClass.getModifiers() & Modifier.ABSTRACT) != 0 ? ExtensionPoint.Kind.INTERFACE : ExtensionPoint.Kind.BEAN_CLASS;
area.registerExtensionPoint(name, aClass.getName(), kind);
}
}
public static <T> T registerComponentInstance(MutablePicoContainer container, Class<T> key, T implementation) {
Object old = container.getComponentInstance(key);
container.unregisterComponent(key);
container.registerComponentInstance(key, implementation);
//noinspection unchecked
return (T)old;
}
}
@@ -0,0 +1,71 @@
/*
* 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.test.testFramework;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
// Based on com.intellij.testFramework.PlatformTestUtil
public class KtPlatformTestUtil {
@NotNull
public static String getTestName(@NotNull String name, boolean lowercaseFirstLetter) {
name = StringUtil.trimStart(name, "test");
return StringUtil.isEmpty(name) ? "" : lowercaseFirstLetter(name, lowercaseFirstLetter);
}
@NotNull
public static String lowercaseFirstLetter(@NotNull String name, boolean lowercaseFirstLetter) {
if (lowercaseFirstLetter && !isAllUppercaseName(name)) {
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
return name;
}
public static boolean isAllUppercaseName(@NotNull String name) {
int uppercaseChars = 0;
for (int i = 0; i < name.length(); i++) {
if (Character.isLowerCase(name.charAt(i))) {
return false;
}
if (Character.isUpperCase(name.charAt(i))) {
uppercaseChars++;
}
}
return uppercaseChars >= 3;
}
@Nullable
protected static String toString(@Nullable Object node, @Nullable Queryable.PrintInfo printInfo) {
if (node instanceof AbstractTreeNode) {
if (printInfo != null) {
return ((AbstractTreeNode) node).toTestString(printInfo);
}
else {
@SuppressWarnings({"deprecation", "UnnecessaryLocalVariable"}) String presentation =
((AbstractTreeNode) node).getTestPresentation();
return presentation;
}
}
if (node == null) {
return "NULL";
}
return node.toString();
}
}
@@ -0,0 +1,441 @@
/*
* 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.test.testFramework;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileSystemUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.rt.execution.junit.FileComparisonFailure;
import com.intellij.util.Function;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashSet;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public abstract class KtUsefulTestCase extends TestCase {
private static final String TEMP_DIR_MARKER = "unitTest_";
private static final String ORIGINAL_TEMP_DIR = FileUtil.getTempDirectory();
private static final Map<String, Long> TOTAL_SETUP_COST_MILLIS = new HashMap<String, Long>();
private static final Map<String, Long> TOTAL_TEARDOWN_COST_MILLIS = new HashMap<String, Long>();
@NotNull
protected final Disposable myTestRootDisposable = new Disposable() {
@Override
public void dispose() { }
@Override
public String toString() {
String testName = getTestName(false);
return KtUsefulTestCase.this.getClass() + (StringUtil.isEmpty(testName) ? "" : ".test" + testName);
}
};
private static final String ourPathToKeep = null;
private final List<String> myPathsToKeep = new ArrayList<String>();
private String myTempDir;
static {
// Radar #5755208: Command line Java applications need a way to launch without a Dock icon.
System.setProperty("apple.awt.UIElement", "true");
}
private boolean oldDisposerDebug;
@Override
protected void setUp() throws Exception {
super.setUp();
String testName = FileUtil.sanitizeFileName(getTestName(true));
if (StringUtil.isEmptyOrSpaces(testName)) testName = "";
testName = new File(testName).getName(); // in case the test name contains file separators
myTempDir = new File(ORIGINAL_TEMP_DIR, TEMP_DIR_MARKER + testName).getPath();
FileUtil.resetCanonicalTempPathCache(myTempDir);
boolean isPerformanceTest = isPerformanceTest();
ApplicationInfoImpl.setInPerformanceTest(isPerformanceTest);
// turn off Disposer debugging for performance tests
oldDisposerDebug = Disposer.setDebugMode(Disposer.isDebugMode() && !isPerformanceTest);
}
@Override
protected void tearDown() throws Exception {
try {
Disposer.dispose(myTestRootDisposable);
cleanupSwingDataStructures();
cleanupDeleteOnExitHookList();
}
finally {
Disposer.setDebugMode(oldDisposerDebug);
FileUtil.resetCanonicalTempPathCache(ORIGINAL_TEMP_DIR);
if (hasTmpFilesToKeep()) {
File[] files = new File(myTempDir).listFiles();
if (files != null) {
for (File file : files) {
if (!shouldKeepTmpFile(file)) {
FileUtil.delete(file);
}
}
}
}
else {
FileUtil.delete(new File(myTempDir));
}
}
UIUtil.removeLeakingAppleListeners();
super.tearDown();
}
private boolean hasTmpFilesToKeep() {
return !myPathsToKeep.isEmpty();
}
private boolean shouldKeepTmpFile(File file) {
String path = file.getPath();
if (FileUtil.pathsEqual(path, ourPathToKeep)) return true;
for (String pathToKeep : myPathsToKeep) {
if (FileUtil.pathsEqual(path, pathToKeep)) return true;
}
return false;
}
private static final Set<String> DELETE_ON_EXIT_HOOK_DOT_FILES;
private static final Class DELETE_ON_EXIT_HOOK_CLASS;
static {
Class<?> aClass;
try {
aClass = Class.forName("java.io.DeleteOnExitHook");
}
catch (Exception e) {
throw new RuntimeException(e);
}
Set<String> files = ReflectionUtil.getStaticFieldValue(aClass, Set.class, "files");
DELETE_ON_EXIT_HOOK_CLASS = aClass;
DELETE_ON_EXIT_HOOK_DOT_FILES = files;
}
private static void cleanupDeleteOnExitHookList() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// try to reduce file set retained by java.io.DeleteOnExitHook
List<String> list;
synchronized (DELETE_ON_EXIT_HOOK_CLASS) {
if (DELETE_ON_EXIT_HOOK_DOT_FILES.isEmpty()) return;
list = new ArrayList<String>(DELETE_ON_EXIT_HOOK_DOT_FILES);
}
for (int i = list.size() - 1; i >= 0; i--) {
String path = list.get(i);
if (FileSystemUtil.getAttributes(path) == null || new File(path).delete()) {
synchronized (DELETE_ON_EXIT_HOOK_CLASS) {
DELETE_ON_EXIT_HOOK_DOT_FILES.remove(path);
}
}
}
}
private static void cleanupSwingDataStructures() throws Exception {
Object manager = ReflectionUtil.getDeclaredMethod(Class.forName("javax.swing.KeyboardManager"), "getCurrentManager").invoke(null);
Map componentKeyStrokeMap = ReflectionUtil.getField(manager.getClass(), manager, Hashtable.class, "componentKeyStrokeMap");
componentKeyStrokeMap.clear();
Map containerMap = ReflectionUtil.getField(manager.getClass(), manager, Hashtable.class, "containerMap");
containerMap.clear();
}
@NotNull
public final Disposable getTestRootDisposable() {
return myTestRootDisposable;
}
@Override
protected void runTest() throws Throwable {
final Throwable[] throwables = new Throwable[1];
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
KtUsefulTestCase.super.runTest();
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throwables[0] = e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throwables[0] = e;
}
catch (Throwable e) {
throwables[0] = e;
}
}
};
invokeTestRunnable(runnable);
if (throwables[0] != null) {
throw throwables[0];
}
}
private static void invokeTestRunnable(@NotNull Runnable runnable) throws Exception {
EdtTestUtil.runInEdtAndWait(runnable);
}
private void defaultRunBare() throws Throwable {
Throwable exception = null;
try {
long setupStart = System.nanoTime();
setUp();
long setupCost = (System.nanoTime() - setupStart) / 1000000;
logPerClassCost(setupCost, TOTAL_SETUP_COST_MILLIS);
runTest();
}
catch (Throwable running) {
exception = running;
}
finally {
try {
long teardownStart = System.nanoTime();
tearDown();
long teardownCost = (System.nanoTime() - teardownStart) / 1000000;
logPerClassCost(teardownCost, TOTAL_TEARDOWN_COST_MILLIS);
}
catch (Throwable tearingDown) {
if (exception == null) exception = tearingDown;
}
}
if (exception != null) throw exception;
}
/**
* Logs the setup cost grouped by test fixture class (superclass of the current test class).
*
* @param cost setup cost in milliseconds
*/
private void logPerClassCost(long cost, Map<String, Long> costMap) {
Class<?> superclass = getClass().getSuperclass();
Long oldCost = costMap.get(superclass.getName());
long newCost = oldCost == null ? cost : oldCost + cost;
costMap.put(superclass.getName(), newCost);
}
@Override
public void runBare() throws Throwable {
this.defaultRunBare();
}
@NonNls
public static String toString(Iterable<?> collection) {
if (!collection.iterator().hasNext()) {
return "<empty>";
}
StringBuilder builder = new StringBuilder();
for (Object o : collection) {
if (o instanceof THashSet) {
builder.append(new TreeSet<Object>((THashSet)o));
}
else {
builder.append(o);
}
builder.append("\n");
}
return builder.toString();
}
private static <T> void assertOrderedEquals(String errorMsg, @NotNull Iterable<T> actual, @NotNull T... expected) {
Assert.assertNotNull(actual);
Assert.assertNotNull(expected);
assertOrderedEquals(errorMsg, actual, Arrays.asList(expected));
}
public static <T> void assertOrderedEquals(
String erroMsg,
Iterable<? extends T> actual,
Collection<? extends T> expected) {
ArrayList<T> list = new ArrayList<T>();
for (T t : actual) {
list.add(t);
}
if (!list.equals(new ArrayList<T>(expected))) {
String expectedString = toString(expected);
String actualString = toString(actual);
Assert.assertEquals(erroMsg, expectedString, actualString);
Assert.fail("Warning! 'toString' does not reflect the difference.\nExpected: " + expectedString + "\nActual: " + actualString);
}
}
public static <T> void assertSameElements(T[] collection, T... expected) {
assertSameElements(Arrays.asList(collection), expected);
}
public static <T> void assertSameElements(Collection<? extends T> collection, T... expected) {
assertSameElements(collection, Arrays.asList(expected));
}
public static <T> void assertSameElements(Collection<? extends T> collection, Collection<T> expected) {
assertSameElements(null, collection, expected);
}
public static <T> void assertSameElements(String message, Collection<? extends T> collection, Collection<T> expected) {
assertNotNull(collection);
assertNotNull(expected);
if (collection.size() != expected.size() || !new HashSet<T>(expected).equals(new HashSet<T>(collection))) {
Assert.assertEquals(message, toString(expected, "\n"), toString(collection, "\n"));
Assert.assertEquals(message, new HashSet<T>(expected), new HashSet<T>(collection));
}
}
public static String toString(Object[] collection, String separator) {
return toString(Arrays.asList(collection), separator);
}
public static String toString(Collection<?> collection, String separator) {
List<String> list = ContainerUtil.map2List(collection, new Function<Object, String>() {
@Override
public String fun(Object o) {
return String.valueOf(o);
}
});
Collections.sort(list);
StringBuilder builder = new StringBuilder();
boolean flag = false;
for (String o : list) {
if (flag) {
builder.append(separator);
}
builder.append(o);
flag = true;
}
return builder.toString();
}
@Contract("null, _ -> fail")
public static <T> T assertInstanceOf(Object o, Class<T> aClass) {
Assert.assertNotNull("Expected instance of: " + aClass.getName() + " actual: " + null, o);
Assert.assertTrue("Expected instance of: " + aClass.getName() + " actual: " + o.getClass().getName(), aClass.isInstance(o));
@SuppressWarnings("unchecked") T t = (T)o;
return t;
}
protected static <T> void assertEmpty(String errorMsg, Collection<T> collection) {
//noinspection unchecked
assertOrderedEquals(errorMsg, collection);
}
protected static void assertSize(int expectedSize, Object[] array) {
assertEquals(toString(Arrays.asList(array)), expectedSize, array.length);
}
public static void assertSameLines(String expected, String actual) {
String expectedText = StringUtil.convertLineSeparators(expected.trim());
String actualText = StringUtil.convertLineSeparators(actual.trim());
Assert.assertEquals(expectedText, actualText);
}
protected String getTestName(boolean lowercaseFirstLetter) {
return getTestName(getName(), lowercaseFirstLetter);
}
public static String getTestName(String name, boolean lowercaseFirstLetter) {
return name == null ? "" : KtPlatformTestUtil.getTestName(name, lowercaseFirstLetter);
}
/** @deprecated use {@link KtPlatformTestUtil#lowercaseFirstLetter(String, boolean)} (to be removed in IDEA 17) */
@SuppressWarnings("unused")
public static String lowercaseFirstLetter(String name, boolean lowercaseFirstLetter) {
return KtPlatformTestUtil.lowercaseFirstLetter(name, lowercaseFirstLetter);
}
/** @deprecated use {@link KtPlatformTestUtil#isAllUppercaseName(String)} (to be removed in IDEA 17) */
@SuppressWarnings("unused")
public static boolean isAllUppercaseName(String name) {
return KtPlatformTestUtil.isAllUppercaseName(name);
}
public static void assertSameLinesWithFile(String filePath, String actualText) {
assertSameLinesWithFile(filePath, actualText, true);
}
public static void assertSameLinesWithFile(String filePath, String actualText, boolean trimBeforeComparing) {
String fileText;
try {
fileText = FileUtil.loadFile(new File(filePath), CharsetToolkit.UTF8_CHARSET);
}
catch (FileNotFoundException e) {
VfsTestUtil.overwriteTestData(filePath, actualText);
throw new AssertionFailedError("No output text found. File " + filePath + " created.");
}
catch (IOException e) {
throw new RuntimeException(e);
}
String expected = StringUtil.convertLineSeparators(trimBeforeComparing ? fileText.trim() : fileText);
String actual = StringUtil.convertLineSeparators(trimBeforeComparing ? actualText.trim() : actualText);
if (!Comparing.equal(expected, actual)) {
throw new FileComparisonFailure(null, expected, actual, filePath);
}
}
public static void clearFields(Object test) throws IllegalAccessException {
Class aClass = test.getClass();
while (aClass != null) {
clearDeclaredFields(test, aClass);
aClass = aClass.getSuperclass();
}
}
private static void clearDeclaredFields(Object test, Class aClass) throws IllegalAccessException {
if (aClass == null) return;
for (Field field : aClass.getDeclaredFields()) {
@NonNls String name = field.getDeclaringClass().getName();
if (!name.startsWith("junit.framework.") && !name.startsWith("com.intellij.testFramework.")) {
int modifiers = field.getModifiers();
if ((modifiers & Modifier.FINAL) == 0 && (modifiers & Modifier.STATIC) == 0 && !field.getType().isPrimitive()) {
field.setAccessible(true);
field.set(test, null);
}
}
}
}
private boolean isPerformanceTest() {
String name = getName();
return name != null && name.contains("Performance") || getClass().getName().contains("Performance");
}
}
@@ -0,0 +1,36 @@
/*
* 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.test.testFramework
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
interface ProjectEx : Project {
fun init()
fun setProjectName(name: String)
}
class MockProjectEx(parentDisposable: Disposable) : MockProject(if (ApplicationManager.getApplication() != null) ApplicationManager.getApplication().picoContainer else null, parentDisposable), ProjectEx {
override fun setProjectName(name: String) {
}
override fun init() {
}
}
@@ -0,0 +1,40 @@
/*
* 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.test.testFramework;
import org.jetbrains.annotations.TestOnly;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestRunnerUtil {
private TestRunnerUtil() {
}
@TestOnly
public static boolean isJUnit4TestClass(Class aClass) {
int modifiers = aClass.getModifiers();
if ((modifiers & Modifier.ABSTRACT) != 0) return false;
if ((modifiers & Modifier.PUBLIC) == 0) return false;
if (aClass.getAnnotation(RunWith.class) != null) return true;
for (Method method : aClass.getMethods()) {
if (method.getAnnotation(Test.class) != null) return true;
}
return false;
}
}
@@ -0,0 +1,95 @@
/*
* 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.test.testFramework;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PathUtil;
import com.intellij.util.text.StringTokenizer;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
public class VfsTestUtil {
private VfsTestUtil() {
}
public static VirtualFile createFile(VirtualFile root, String relativePath) {
return createFile(root, relativePath, "");
}
public static VirtualFile createFile(VirtualFile root, String relativePath, String text) {
return createFileOrDir(root, relativePath, text, false);
}
private static VirtualFile createFileOrDir(
VirtualFile root,
String relativePath,
String text,
boolean dir) {
try {
AccessToken token = WriteAction.start();
try {
VirtualFile parent = root;
Assert.assertNotNull(parent);
StringTokenizer parents = new StringTokenizer(PathUtil.getParentPath(relativePath), "/");
while (parents.hasMoreTokens()) {
String name = parents.nextToken();
VirtualFile child = parent.findChild(name);
if (child == null || !child.isValid()) {
child = parent.createChildDirectory(VfsTestUtil.class, name);
}
parent = child;
}
VirtualFile file;
parent.getChildren();//need this to ensure that fileCreated event is fired
if (dir) {
file = parent.createChildDirectory(VfsTestUtil.class, PathUtil.getFileName(relativePath));
}
else {
file = parent.findFileByRelativePath(relativePath);
if (file == null) {
file = parent.createChildData(VfsTestUtil.class, PathUtil.getFileName(relativePath));
}
VfsUtil.saveText(file, text);
}
return file;
}
finally {
token.finish();
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("UnusedDeclaration")
public static void overwriteTestData(String filePath, String actual) {
try {
FileUtil.writeToFile(new File(filePath), actual);
}
catch (IOException e) {
throw new AssertionError(e);
}
}
}
@@ -0,0 +1,167 @@
/*
* 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.test.testFramework.mock;
import com.intellij.openapi.fileTypes.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class KtMockFileTypeManager extends FileTypeManager {
private final FileType fileType;
public KtMockFileTypeManager(FileType fileType) {
this.fileType = fileType;
}
@Override
@NotNull
public String getIgnoredFilesList() {
throw new IncorrectOperationException();
}
@Override
public void setIgnoredFilesList(@NotNull String list) {
}
@Override
public void registerFileType(@NotNull FileType type, @NotNull List<FileNameMatcher> defaultAssociations) {
}
@Override
@NotNull
public FileType getFileTypeByFileName(@NotNull String fileName) {
return fileType;
}
@Override
@NotNull
public FileType getFileTypeByFile(@NotNull VirtualFile file) {
return fileType;
}
@Override
@NotNull
public FileType getFileTypeByExtension(@NotNull String extension) {
return fileType;
}
@Override
@NotNull
public FileType[] getRegisteredFileTypes() {
return FileType.EMPTY_ARRAY;
}
@Override
public boolean isFileIgnored(@NotNull String name) {
return false;
}
@Override
public boolean isFileIgnored(@NotNull VirtualFile file) {
return false;
}
@Override
@NotNull
public String[] getAssociatedExtensions(@NotNull FileType type) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
@Override
public void addFileTypeListener(@NotNull FileTypeListener listener) {
}
@Override
public void removeFileTypeListener(@NotNull FileTypeListener listener) {
}
@Override
public FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file) {
return file.getFileType();
}
@Override
public FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file, @NotNull Project project) {
return getKnownFileTypeOrAssociate(file);
}
@Override
@NotNull
public List<FileNameMatcher> getAssociations(@NotNull FileType type) {
return Collections.emptyList();
}
@Override
public void associate(@NotNull FileType type, @NotNull FileNameMatcher matcher) {
}
@Override
public void removeAssociation(@NotNull FileType type, @NotNull FileNameMatcher matcher) {
}
@Override
@NotNull
public FileType getStdFileType(@NotNull @NonNls String fileTypeName) {
if ("ARCHIVE".equals(fileTypeName) || "CLASS".equals(fileTypeName)) return UnknownFileType.INSTANCE;
if ("PLAIN_TEXT".equals(fileTypeName)) return PlainTextFileType.INSTANCE;
if ("JAVA".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.JavaFileType", fileTypeName);
if ("XML".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.XmlFileType", fileTypeName);
if ("DTD".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.DTDFileType", fileTypeName);
if ("JSP".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.NewJspFileType", fileTypeName);
if ("JSPX".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.JspxFileType", fileTypeName);
if ("HTML".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.HtmlFileType", fileTypeName);
if ("XHTML".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.XHtmlFileType", fileTypeName);
if ("JavaScript".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.lang.javascript.JavaScriptFileType", fileTypeName);
if ("Properties".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.lang.properties.PropertiesFileType", fileTypeName);
return new KtMockLanguageFileType(PlainTextLanguage.INSTANCE, fileTypeName.toLowerCase());
}
private static FileType loadFileTypeSafe(String className, String fileTypeName) {
try {
return (FileType)Class.forName(className).getField("INSTANCE").get(null);
}
catch (Exception ignored) {
return new KtMockLanguageFileType(PlainTextLanguage.INSTANCE, fileTypeName.toLowerCase(Locale.ENGLISH));
}
}
@Override
public boolean isFileOfType(@NotNull VirtualFile file, @NotNull FileType type) {
return false;
}
@NotNull
@Override
public FileType detectFileTypeFromContent(@NotNull VirtualFile file) {
return UnknownFileType.INSTANCE;
}
@Nullable
@Override
public FileType findFileTypeByName(@NotNull String fileTypeName) {
return null;
}
}
@@ -0,0 +1,64 @@
/*
* 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.test.testFramework.mock;
import com.intellij.lang.Language;
import com.intellij.openapi.fileTypes.LanguageFileType;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class KtMockLanguageFileType extends LanguageFileType {
private final String myExtension;
public KtMockLanguageFileType(@NotNull Language language, String extension) {
super(language);
this.myExtension = extension;
}
@Override
@NotNull
public String getName() {
return this.getLanguage().getID();
}
@Override
@NotNull
public String getDescription() {
return "";
}
@Override
@NotNull
public String getDefaultExtension() {
String var10000 = this.myExtension;
if(this.myExtension == null) {
throw new IllegalStateException(String.format("@NotNull method %s.%s must not return null", new Object[]{"com/intellij/mock/KtMockLanguageFileType", "getDefaultExtension"}));
} else {
return var10000;
}
}
@Override
public Icon getIcon() {
return null;
}
public boolean equals(Object obj) {
return obj instanceof LanguageFileType && this.getLanguage().equals(((LanguageFileType) obj).getLanguage());
}
}
@@ -0,0 +1,94 @@
/*
* 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.test.testFramework.mock;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.event.*;
import org.jetbrains.annotations.NotNull;
public class MockEditorEventMulticaster implements EditorEventMulticaster {
public MockEditorEventMulticaster() {
}
@Override
public void addDocumentListener(@NotNull DocumentListener listener) {
}
@Override
public void addDocumentListener(@NotNull DocumentListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeDocumentListener(@NotNull DocumentListener listener) {
}
@Override
public void addEditorMouseListener(@NotNull EditorMouseListener listener) {
}
@Override
public void addEditorMouseListener(@NotNull EditorMouseListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeEditorMouseListener(@NotNull EditorMouseListener listener) {
}
@Override
public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
}
@Override
public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
}
@Override
public void addCaretListener(@NotNull CaretListener listener) {
}
@Override
public void addCaretListener(@NotNull CaretListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeCaretListener(@NotNull CaretListener listener) {
}
@Override
public void addSelectionListener(@NotNull SelectionListener listener) {
}
@Override
public void addSelectionListener(@NotNull SelectionListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeSelectionListener(@NotNull SelectionListener listener) {
}
@Override
public void addVisibleAreaListener(@NotNull VisibleAreaListener listener) {
}
@Override
public void removeVisibleAreaListener(@NotNull VisibleAreaListener listener) {
}
}
@@ -0,0 +1,118 @@
/*
* 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.test.testFramework.mock;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.EditorEventMulticaster;
import com.intellij.openapi.editor.event.EditorFactoryListener;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.text.CharArrayCharSequence;
import org.jetbrains.annotations.NotNull;
public class MockEditorFactory extends EditorFactory {
@Override
public Editor createEditor(@NotNull Document document) {
return null;
}
@Override
public Editor createViewer(@NotNull Document document) {
return null;
}
@Override
public Editor createEditor(@NotNull Document document, Project project) {
return null;
}
@Override
public Editor createEditor(@NotNull Document document, Project project, @NotNull VirtualFile file, boolean isViewer) {
return null;
}
@Override
public Editor createEditor(@NotNull Document document, Project project, @NotNull FileType fileType, boolean isViewer) {
return null;
}
@Override
public Editor createViewer(@NotNull Document document, Project project) {
return null;
}
@Override
public void releaseEditor(@NotNull Editor editor) {
}
@Override
@NotNull
public Editor[] getEditors(@NotNull Document document, Project project) {
return Editor.EMPTY_ARRAY;
}
@Override
@NotNull
public Editor[] getEditors(@NotNull Document document) {
return getEditors(document, null);
}
@Override
@NotNull
public Editor[] getAllEditors() {
return Editor.EMPTY_ARRAY;
}
@Override
public void addEditorFactoryListener(@NotNull EditorFactoryListener listener) {
}
@Override
public void addEditorFactoryListener(@NotNull EditorFactoryListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeEditorFactoryListener(@NotNull EditorFactoryListener listener) {
}
@Override
@NotNull
public EditorEventMulticaster getEventMulticaster() {
return new MockEditorEventMulticaster();
}
@Override
@NotNull
public Document createDocument(@NotNull CharSequence text) {
return new DocumentImpl(text);
}
@Override
@NotNull
public Document createDocument(@NotNull char[] text) {
return createDocument(new CharArrayCharSequence(text));
}
@Override
public void refreshAllEditors() {
}
}
@@ -0,0 +1,107 @@
/*
* 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.test.testFramework.mock;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SingleRootFileViewProvider;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.file.impl.FileManager;
import com.intellij.util.containers.WeakFactoryMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
public class MockFileManager implements FileManager {
private final PsiManagerEx myManager;
// in mock tests it's LightVirtualFile, they're only alive when they're referenced,
// and there can not be several instances representing the same file
private final WeakFactoryMap<VirtualFile, FileViewProvider> myViewProviders = new WeakFactoryMap<VirtualFile, FileViewProvider>() {
@Override
protected FileViewProvider create(VirtualFile key) {
return new SingleRootFileViewProvider(myManager, key);
}
};
@Override
@NotNull
public FileViewProvider createFileViewProvider(@NotNull VirtualFile file, boolean eventSystemEnabled) {
return new SingleRootFileViewProvider(myManager, file, eventSystemEnabled);
}
public MockFileManager(PsiManagerEx manager) {
myManager = manager;
}
@Override
public void dispose() {
throw new UnsupportedOperationException("Method dispose is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public PsiFile findFile(@NotNull VirtualFile vFile) {
return getCachedPsiFile(vFile);
}
@Override
@Nullable
public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
throw new UnsupportedOperationException("Method findDirectory is not yet implemented in " + getClass().getName());
}
@Override
public void reloadFromDisk(@NotNull PsiFile file) //Q: move to PsiFile(Impl)?
{
throw new UnsupportedOperationException("Method reloadFromDisk is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) {
FileViewProvider provider = findCachedViewProvider(vFile);
return provider.getPsi(provider.getBaseLanguage());
}
@Override
public void cleanupForNextTest() {
myViewProviders.clear();
}
@Override
public FileViewProvider findViewProvider(@NotNull VirtualFile file) {
throw new UnsupportedOperationException("Method findViewProvider is not yet implemented in " + getClass().getName());
}
@Override
public FileViewProvider findCachedViewProvider(@NotNull VirtualFile file) {
return myViewProviders.get(file);
}
@Override
public void setViewProvider(@NotNull VirtualFile virtualFile, FileViewProvider fileViewProvider) {
myViewProviders.put(virtualFile, fileViewProvider);
}
@Override
@NotNull
public List<PsiFile> getAllCachedFiles() {
throw new UnsupportedOperationException("Method getAllCachedFiles is not yet implemented in " + getClass().getName());
}
}
@@ -0,0 +1,146 @@
/*
* 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.test.testFramework.mock;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public class MockPsiDocumentManager extends PsiDocumentManager {
@Override
@Nullable
public PsiFile getPsiFile(@NotNull Document document) {
throw new UnsupportedOperationException("Method getPsiFile is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public PsiFile getCachedPsiFile(@NotNull Document document) {
throw new UnsupportedOperationException("Method getCachedPsiFile is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public Document getDocument(@NotNull PsiFile file) {
return null;
}
@Override
@Nullable
public Document getCachedDocument(@NotNull PsiFile file) {
VirtualFile vFile = file.getViewProvider().getVirtualFile();
return FileDocumentManager.getInstance().getCachedDocument(vFile);
}
@Override
public void commitAllDocuments() {
}
@Override
public void performForCommittedDocument(@NotNull Document document, @NotNull Runnable action) {
action.run();
}
@Override
public void commitDocument(@NotNull Document document) {
}
@NotNull
@Override
public CharSequence getLastCommittedText(@NotNull Document document) {
return document.getImmutableCharSequence();
}
@Override
public long getLastCommittedStamp(@NotNull Document document) {
return document.getModificationStamp();
}
@Nullable
@Override
public Document getLastCommittedDocument(@NotNull PsiFile file) {
return null;
}
@Override
@NotNull
public Document[] getUncommittedDocuments() {
throw new UnsupportedOperationException("Method getUncommittedDocuments is not yet implemented in " + getClass().getName());
}
@Override
public boolean isUncommited(@NotNull Document document) {
throw new UnsupportedOperationException("Method isUncommited is not yet implemented in " + getClass().getName());
}
@Override
public boolean isCommitted(@NotNull Document document) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasUncommitedDocuments() {
throw new UnsupportedOperationException("Method hasUncommitedDocuments is not yet implemented in " + getClass().getName());
}
@Override
public void commitAndRunReadAction(@NotNull Runnable runnable) {
throw new UnsupportedOperationException("Method commitAndRunReadAction is not yet implemented in " + getClass().getName());
}
@Override
public <T> T commitAndRunReadAction(@NotNull Computable<T> computation) {
throw new UnsupportedOperationException("Method commitAndRunReadAction is not yet implemented in " + getClass().getName());
}
@Override
public void addListener(@NotNull Listener listener) {
throw new UnsupportedOperationException("Method addListener is not yet implemented in " + getClass().getName());
}
@Override
public void removeListener(@NotNull Listener listener) {
throw new UnsupportedOperationException("Method removeListener is not yet implemented in " + getClass().getName());
}
@Override
public boolean isDocumentBlockedByPsi(@NotNull Document doc) {
throw new UnsupportedOperationException("Method isDocumentBlockedByPsi is not yet implemented in " + getClass().getName());
}
@Override
public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {
throw new UnsupportedOperationException(
"Method doPostponedOperationsAndUnblockDocument is not yet implemented in " + getClass().getName());
}
@Override
public boolean performWhenAllCommitted(@NotNull Runnable action) {
throw new UnsupportedOperationException();
}
@Override
public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,189 @@
/*
* 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.test.testFramework.mock;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.PsiModificationTrackerImpl;
import com.intellij.psi.impl.PsiTreeChangeEventImpl;
import com.intellij.psi.impl.file.impl.FileManager;
import com.intellij.psi.util.PsiModificationTracker;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public class MockPsiManager extends PsiManagerEx {
private final Project myProject;
private final Map<VirtualFile,PsiDirectory> myDirectories = new THashMap<VirtualFile, PsiDirectory>();
private MockFileManager myMockFileManager;
private PsiModificationTrackerImpl myPsiModificationTracker;
public MockPsiManager(@NotNull Project project) {
myProject = project;
}
@SuppressWarnings("unused")
public void addPsiDirectory(VirtualFile file, PsiDirectory psiDirectory) {
myDirectories.put(file, psiDirectory);
}
@Override
@NotNull
public Project getProject() {
return myProject;
}
@Override
public PsiFile findFile(@NotNull VirtualFile file) {
return null;
}
@Override
@Nullable
public
FileViewProvider findViewProvider(@NotNull VirtualFile file) {
return null;
}
@Override
public PsiDirectory findDirectory(@NotNull VirtualFile file) {
return myDirectories.get(file);
}
@Override
public boolean areElementsEquivalent(PsiElement element1, PsiElement element2) {
return Comparing.equal(element1, element2);
}
@Override
public void reloadFromDisk(@NotNull PsiFile file) {
}
@Override
public void addPsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) {
}
@Override
public void addPsiTreeChangeListener(@NotNull PsiTreeChangeListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removePsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) {
}
@Override
@NotNull
public PsiModificationTracker getModificationTracker() {
if (myPsiModificationTracker == null) {
myPsiModificationTracker = new PsiModificationTrackerImpl(myProject);
}
return myPsiModificationTracker;
}
@Override
public void startBatchFilesProcessingMode() {
}
@Override
public void finishBatchFilesProcessingMode() {
}
@Override
public <T> T getUserData(@NotNull Key<T> key) {
return null;
}
@Override
public <T> void putUserData(@NotNull Key<T> key, T value) {
}
@Override
public boolean isDisposed() {
return false;
}
@Override
public void dropResolveCaches() {
getFileManager().cleanupForNextTest();
}
@Override
public boolean isInProject(@NotNull PsiElement element) {
return false;
}
@Override
public boolean isBatchFilesProcessingMode() {
return false;
}
@Override
public boolean isAssertOnFileLoading(@NotNull VirtualFile file) {
return false;
}
@Override
public void beforeChange(boolean isPhysical) {
throw new UnsupportedOperationException();
}
@Override
public void afterChange(boolean isPhysical) {
throw new UnsupportedOperationException();
}
@Override
public void registerRunnableToRunOnChange(@NotNull Runnable runnable) {
}
@Override
public void registerRunnableToRunOnAnyChange(@NotNull Runnable runnable) {
}
@Override
public void registerRunnableToRunAfterAnyChange(@NotNull Runnable runnable) {
throw new UnsupportedOperationException("Method registerRunnableToRunAfterAnyChange is not yet implemented in " + getClass().getName());
}
@Override
@NotNull
public FileManager getFileManager() {
if (myMockFileManager == null) {
myMockFileManager = new MockFileManager(this);
}
return myMockFileManager;
}
@Override
public void beforeChildRemoval(@NotNull PsiTreeChangeEventImpl event) {
}
@Override
public void beforeChildReplacement(@NotNull PsiTreeChangeEventImpl event) {
}
@Override
public void beforeChildAddition(@NotNull PsiTreeChangeEventImpl event) {
}
}
@@ -0,0 +1,34 @@
/*
* 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.test.testFramework.mock;
import com.intellij.openapi.components.RoamingType;
import com.intellij.openapi.options.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MockSchemesManagerFactory extends SchemesManagerFactory {
private static final SchemesManager EMPTY = new EmptySchemesManager();
@Override
protected <T extends Scheme, E extends ExternalizableScheme> SchemesManager<T, E> create(@NotNull String directoryName,
@NotNull SchemeProcessor<E> processor,
@NotNull RoamingType roamingType,
@Nullable String presentableName) {
//noinspection unchecked
return EMPTY;
}
}
@@ -0,0 +1,23 @@
/*
* 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.test.testFramework
import com.intellij.openapi.application.ApplicationManager
fun <T> runWriteAction(action: () -> T): T {
return ApplicationManager.getApplication().runWriteAction<T>(action)
}
@@ -0,0 +1,558 @@
/*
* 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.test.util;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.types.KotlinType;
import org.junit.Assert;
import java.io.PrintStream;
import java.util.Collection;
import java.util.List;
public class DescriptorValidator {
public static void validate(@NotNull ValidationVisitor validationStrategy, DeclarationDescriptor descriptor) {
DiagnosticCollectorForTests collector = new DiagnosticCollectorForTests();
validate(validationStrategy, descriptor, collector);
collector.done();
}
public static void validate(
@NotNull ValidationVisitor validator,
@NotNull DeclarationDescriptor descriptor,
@NotNull DiagnosticCollector collector
) {
RecursiveDescriptorProcessor.process(descriptor, collector, validator);
}
private static void report(@NotNull DiagnosticCollector collector, @NotNull DeclarationDescriptor descriptor, @NotNull String message) {
collector.report(new ValidationDiagnostic(descriptor, message));
}
public interface DiagnosticCollector {
void report(@NotNull ValidationDiagnostic diagnostic);
}
public static class ValidationVisitor implements DeclarationDescriptorVisitor<Boolean, DiagnosticCollector> {
public static ValidationVisitor errorTypesForbidden() {
return new ValidationVisitor();
}
public static ValidationVisitor errorTypesAllowed() {
return new ValidationVisitor().allowErrorTypes();
}
private boolean allowErrorTypes = false;
private Predicate<DeclarationDescriptor> recursiveFilter = Predicates.alwaysTrue();
protected ValidationVisitor() {
}
@NotNull
public ValidationVisitor withStepIntoFilter(@NotNull Predicate<DeclarationDescriptor> filter) {
this.recursiveFilter = filter;
return this;
}
@NotNull
public ValidationVisitor allowErrorTypes() {
this.allowErrorTypes = true;
return this;
}
protected void validateScope(DeclarationDescriptor scopeOwner, @NotNull MemberScope scope, @NotNull DiagnosticCollector collector) {
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(scope)) {
if (recursiveFilter.apply(descriptor)) {
descriptor.accept(new ScopeValidatorVisitor(collector), scope);
}
}
}
private void validateType(
@NotNull DeclarationDescriptor descriptor,
@Nullable KotlinType type,
@NotNull DiagnosticCollector collector
) {
if (type == null) {
report(collector, descriptor, "No type");
return;
}
if (!allowErrorTypes && type.isError()) {
report(collector, descriptor, "Error type: " + type);
return;
}
validateScope(descriptor, type.getMemberScope(), collector);
}
private void validateReturnType(CallableDescriptor descriptor, DiagnosticCollector collector) {
validateType(descriptor, descriptor.getReturnType(), collector);
}
private static void validateTypeParameters(DiagnosticCollector collector, List<TypeParameterDescriptor> parameters) {
for (int i = 0; i < parameters.size(); i++) {
TypeParameterDescriptor typeParameterDescriptor = parameters.get(i);
if (typeParameterDescriptor.getIndex() != i) {
report(collector, typeParameterDescriptor, "Incorrect index: " + typeParameterDescriptor.getIndex() + " but must be " + i);
}
}
}
private static void validateValueParameters(DiagnosticCollector collector, List<ValueParameterDescriptor> parameters) {
for (int i = 0; i < parameters.size(); i++) {
ValueParameterDescriptor valueParameterDescriptor = parameters.get(i);
if (valueParameterDescriptor.getIndex() != i) {
report(collector, valueParameterDescriptor, "Incorrect index: " + valueParameterDescriptor.getIndex() + " but must be " + i);
}
}
}
private void validateTypes(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
Collection<KotlinType> types
) {
for (KotlinType type : types) {
validateType(descriptor, type, collector);
}
}
private void validateCallable(CallableDescriptor descriptor, DiagnosticCollector collector) {
validateReturnType(descriptor, collector);
validateTypeParameters(collector, descriptor.getTypeParameters());
validateValueParameters(collector, descriptor.getValueParameters());
}
private static <T> void assertEquals(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
String name,
T expected,
T actual
) {
if (!expected.equals(actual)) {
report(collector, descriptor, "Wrong " + name + ": " + actual + " must be " + expected);
}
}
private static <T> void assertEqualTypes(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
String name,
KotlinType expected,
KotlinType actual
) {
if (expected.isError() && actual.isError()) {
assertEquals(descriptor, collector, name, expected.toString(), actual.toString());
}
else if (!expected.equals(actual)) {
report(collector, descriptor, "Wrong " + name + ": " + actual + " must be " + expected);
}
}
private static void validateAccessor(
PropertyDescriptor descriptor,
DiagnosticCollector collector,
PropertyAccessorDescriptor accessor,
String name
) {
// TODO: fix the discrepancies in descriptor construction and enable these checks
//assertEquals(accessor, collector, name + " visibility", descriptor.getVisibility(), accessor.getVisibility());
//assertEquals(accessor, collector, name + " modality", descriptor.getModality(), accessor.getModality());
assertEquals(accessor, collector, "corresponding property", descriptor, accessor.getCorrespondingProperty());
}
@Override
public Boolean visitPackageFragmentDescriptor(
PackageFragmentDescriptor descriptor, DiagnosticCollector collector
) {
validateScope(descriptor, descriptor.getMemberScope(), collector);
return true;
}
@Override
public Boolean visitPackageViewDescriptor(PackageViewDescriptor descriptor, DiagnosticCollector collector) {
if (!recursiveFilter.apply(descriptor)) return false;
validateScope(descriptor, descriptor.getMemberScope(), collector);
return true;
}
@Override
public Boolean visitVariableDescriptor(
VariableDescriptor descriptor, DiagnosticCollector collector
) {
validateReturnType(descriptor, collector);
return true;
}
@Override
public Boolean visitFunctionDescriptor(
FunctionDescriptor descriptor, DiagnosticCollector collector
) {
validateCallable(descriptor, collector);
return true;
}
@Override
public Boolean visitTypeParameterDescriptor(
TypeParameterDescriptor descriptor, DiagnosticCollector collector
) {
validateTypes(descriptor, collector, descriptor.getUpperBounds());
validateType(descriptor, descriptor.getDefaultType(), collector);
return true;
}
@Override
public Boolean visitClassDescriptor(
ClassDescriptor descriptor, DiagnosticCollector collector
) {
validateTypeParameters(collector, descriptor.getDeclaredTypeParameters());
Collection<KotlinType> supertypes = descriptor.getTypeConstructor().getSupertypes();
if (supertypes.isEmpty() && descriptor.getKind() != ClassKind.INTERFACE
&& !KotlinBuiltIns.isSpecialClassWithNoSupertypes(descriptor)) {
report(collector, descriptor, "No supertypes for non-trait");
}
validateTypes(descriptor, collector, supertypes);
validateType(descriptor, descriptor.getDefaultType(), collector);
validateScope(descriptor, descriptor.getUnsubstitutedInnerClassesScope(), collector);
List<ConstructorDescriptor> primary = Lists.newArrayList();
for (ConstructorDescriptor constructorDescriptor : descriptor.getConstructors()) {
if (constructorDescriptor.isPrimary()) {
primary.add(constructorDescriptor);
}
}
if (primary.size() > 1) {
report(collector, descriptor, "Many primary constructors: " + primary);
}
ConstructorDescriptor primaryConstructor = descriptor.getUnsubstitutedPrimaryConstructor();
if (primaryConstructor != null) {
if (!descriptor.getConstructors().contains(primaryConstructor)) {
report(collector, primaryConstructor,
"Primary constructor not in getConstructors() result: " + descriptor.getConstructors());
}
}
ClassDescriptor companionObjectDescriptor = descriptor.getCompanionObjectDescriptor();
if (companionObjectDescriptor != null && !companionObjectDescriptor.isCompanionObject()) {
report(collector, companionObjectDescriptor, "Companion object should be marked as such");
}
return true;
}
@Override
public Boolean visitModuleDeclaration(
ModuleDescriptor descriptor, DiagnosticCollector collector
) {
return true;
}
@Override
public Boolean visitConstructorDescriptor(
ConstructorDescriptor constructorDescriptor, DiagnosticCollector collector
) {
visitFunctionDescriptor(constructorDescriptor, collector);
assertEqualTypes(constructorDescriptor, collector,
"return type",
constructorDescriptor.getContainingDeclaration().getDefaultType(),
constructorDescriptor.getReturnType());
return true;
}
@Override
public Boolean visitScriptDescriptor(
ScriptDescriptor scriptDescriptor, DiagnosticCollector collector
) {
return true;
}
@Override
public Boolean visitPropertyDescriptor(
PropertyDescriptor descriptor, DiagnosticCollector collector
) {
validateCallable(descriptor, collector);
PropertyGetterDescriptor getter = descriptor.getGetter();
if (getter != null) {
assertEqualTypes(getter, collector, "getter return type", descriptor.getType(), getter.getReturnType());
validateAccessor(descriptor, collector, getter, "getter");
}
PropertySetterDescriptor setter = descriptor.getSetter();
if (setter != null) {
assertEquals(setter, collector, "setter parameter count", 1, setter.getValueParameters().size());
assertEqualTypes(setter, collector, "setter parameter type", descriptor.getType(), setter.getValueParameters().get(0).getType());
assertEquals(setter, collector, "corresponding property", descriptor, setter.getCorrespondingProperty());
}
return true;
}
@Override
public Boolean visitValueParameterDescriptor(
ValueParameterDescriptor descriptor, DiagnosticCollector collector
) {
return visitVariableDescriptor(descriptor, collector);
}
@Override
public Boolean visitPropertyGetterDescriptor(
PropertyGetterDescriptor descriptor, DiagnosticCollector collector
) {
return visitFunctionDescriptor(descriptor, collector);
}
@Override
public Boolean visitPropertySetterDescriptor(
PropertySetterDescriptor descriptor, DiagnosticCollector collector
) {
return visitFunctionDescriptor(descriptor, collector);
}
@Override
public Boolean visitReceiverParameterDescriptor(
ReceiverParameterDescriptor descriptor, DiagnosticCollector collector
) {
validateType(descriptor, descriptor.getType(), collector);
return true;
}
}
private static class ScopeValidatorVisitor implements DeclarationDescriptorVisitor<Void, MemberScope> {
private final DiagnosticCollector collector;
public ScopeValidatorVisitor(DiagnosticCollector collector) {
this.collector = collector;
}
private void report(DeclarationDescriptor expected, String message) {
DescriptorValidator.report(collector, expected, message);
}
private void assertFound(
@NotNull MemberScope scope,
@NotNull DeclarationDescriptor expected,
@Nullable DeclarationDescriptor found,
boolean shouldBeSame
) {
if (found == null) {
report(expected, "Not found in " + scope);
}
if (shouldBeSame ? expected != found : !expected.equals(found)) {
report(expected, "Lookup error in " + scope + ": " + found);
}
}
private void assertFound(
@NotNull MemberScope scope,
@NotNull DeclarationDescriptor expected,
@NotNull Collection<? extends DeclarationDescriptor> found
) {
if (!found.contains(expected)) {
report(expected, "Not found in " + scope + ": " + found);
}
}
@Override
public Void visitPackageFragmentDescriptor(
PackageFragmentDescriptor descriptor, MemberScope scope
) {
return null;
}
@Override
public Void visitPackageViewDescriptor(
PackageViewDescriptor descriptor, MemberScope scope
) {
return null;
}
@Override
public Void visitVariableDescriptor(
VariableDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedVariables(descriptor.getName(), NoLookupLocation.FROM_TEST));
return null;
}
@Override
public Void visitFunctionDescriptor(
FunctionDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedFunctions(descriptor.getName(), NoLookupLocation.FROM_TEST));
return null;
}
@Override
public Void visitTypeParameterDescriptor(
TypeParameterDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedClassifier(descriptor.getName(), NoLookupLocation.FROM_TEST), true);
return null;
}
@Override
public Void visitClassDescriptor(
ClassDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedClassifier(descriptor.getName(), NoLookupLocation.FROM_TEST), true);
return null;
}
@Override
public Void visitModuleDeclaration(
ModuleDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Module found in scope: " + scope);
return null;
}
@Override
public Void visitConstructorDescriptor(
ConstructorDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Constructor found in scope: " + scope);
return null;
}
@Override
public Void visitScriptDescriptor(
ScriptDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Script found in scope: " + scope);
return null;
}
@Override
public Void visitPropertyDescriptor(
PropertyDescriptor descriptor, MemberScope scope
) {
return visitVariableDescriptor(descriptor, scope);
}
@Override
public Void visitValueParameterDescriptor(
ValueParameterDescriptor descriptor, MemberScope scope
) {
return visitVariableDescriptor(descriptor, scope);
}
@Override
public Void visitPropertyGetterDescriptor(
PropertyGetterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Getter found in scope: " + scope);
return null;
}
@Override
public Void visitPropertySetterDescriptor(
PropertySetterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Setter found in scope: " + scope);
return null;
}
@Override
public Void visitReceiverParameterDescriptor(
ReceiverParameterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Receiver parameter found in scope: " + scope);
return null;
}
}
public static class ValidationDiagnostic {
private final DeclarationDescriptor descriptor;
private final String message;
private final Throwable stackTrace;
private ValidationDiagnostic(@NotNull DeclarationDescriptor descriptor, @NotNull String message) {
this.descriptor = descriptor;
this.message = message;
this.stackTrace = new Throwable();
}
@NotNull
public DeclarationDescriptor getDescriptor() {
return descriptor;
}
@NotNull
public String getMessage() {
return message;
}
@NotNull
public Throwable getStackTrace() {
return stackTrace;
}
public void printStackTrace(@NotNull PrintStream out) {
out.println(descriptor);
out.println(message);
stackTrace.printStackTrace(out);
}
@Override
public String toString() {
return descriptor + " > " + message;
}
}
private static class DiagnosticCollectorForTests implements DiagnosticCollector {
private boolean errorsFound = false;
@Override
public void report(@NotNull ValidationDiagnostic diagnostic) {
diagnostic.printStackTrace(System.err);
errorsFound = true;
}
public void done() {
if (errorsFound) {
Assert.fail("Descriptor validation failed (see messages above)");
}
}
}
private DescriptorValidator() {}
}
@@ -0,0 +1,328 @@
/*
* 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.test.util;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.jvm.compiler.ExpectedLoadErrorsUtil;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.renderer.*;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.MemberComparator;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.utils.Printer;
import org.junit.Assert;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
import static org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesForbidden;
public class RecursiveDescriptorComparator {
private static final DescriptorRenderer DEFAULT_RENDERER = DescriptorRenderer.Companion.withOptions(
new Function1<DescriptorRendererOptions, Unit>() {
@Override
public Unit invoke(DescriptorRendererOptions options) {
options.setWithDefinedIn(false);
options.setExcludedAnnotationClasses(Collections.singleton(new FqName(ExpectedLoadErrorsUtil.ANNOTATION_CLASS_NAME)));
options.setOverrideRenderingPolicy(OverrideRenderingPolicy.RENDER_OPEN_OVERRIDE);
options.setIncludePropertyConstant(true);
options.setClassifierNamePolicy(ClassifierNamePolicy.FULLY_QUALIFIED.INSTANCE);
options.setVerbose(true);
options.setModifiers(DescriptorRendererModifier.ALL);
return Unit.INSTANCE;
}
}
);
public static final Configuration DONT_INCLUDE_METHODS_OF_OBJECT = new Configuration(false, false, false,
Predicates.<DeclarationDescriptor>alwaysTrue(),
errorTypesForbidden(), DEFAULT_RENDERER);
public static final Configuration RECURSIVE = new Configuration(false, false, true,
Predicates.<DeclarationDescriptor>alwaysTrue(),
errorTypesForbidden(), DEFAULT_RENDERER);
public static final Configuration RECURSIVE_ALL = new Configuration(true, true, true,
Predicates.<DeclarationDescriptor>alwaysTrue(),
errorTypesForbidden(), DEFAULT_RENDERER);
public static final Predicate<DeclarationDescriptor> SKIP_BUILT_INS_PACKAGES = new Predicate<DeclarationDescriptor>() {
@Override
public boolean apply(DeclarationDescriptor descriptor) {
if (descriptor instanceof PackageViewDescriptor) {
return !KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME.equals(((PackageViewDescriptor) descriptor).getFqName());
}
return true;
}
};
private static final ImmutableSet<String> KOTLIN_ANY_METHOD_NAMES = ImmutableSet.of("equals", "hashCode", "toString");
private final Configuration conf;
public RecursiveDescriptorComparator(@NotNull Configuration conf) {
this.conf = conf;
}
public String serializeRecursively(@NotNull DeclarationDescriptor declarationDescriptor) {
StringBuilder result = new StringBuilder();
appendDeclarationRecursively(declarationDescriptor, DescriptorUtils.getContainingModule(declarationDescriptor),
new Printer(result, 1), true);
return result.toString();
}
private void appendDeclarationRecursively(
@NotNull DeclarationDescriptor descriptor,
@NotNull ModuleDescriptor module,
@NotNull Printer printer,
boolean topLevel
) {
boolean isEnumEntry = isEnumEntry(descriptor);
boolean isClassOrPackage =
(descriptor instanceof ClassOrPackageFragmentDescriptor || descriptor instanceof PackageViewDescriptor) && !isEnumEntry;
if (isClassOrPackage && !topLevel) {
printer.println();
}
boolean isPrimaryConstructor = descriptor instanceof ConstructorDescriptor && ((ConstructorDescriptor) descriptor).isPrimary();
printer.print(isPrimaryConstructor && conf.checkPrimaryConstructors ? "/*primary*/ " : "", conf.renderer.render(descriptor));
if (isClassOrPackage) {
if (!topLevel) {
printer.printlnWithNoIndent(" {").pushIndent();
}
else {
printer.println();
printer.println();
}
if (descriptor instanceof ClassDescriptor) {
ClassDescriptor klass = (ClassDescriptor) descriptor;
appendSubDescriptors(descriptor, module,
klass.getDefaultType().getMemberScope(), klass.getConstructors(), printer);
MemberScope staticScope = klass.getStaticScope();
if (!DescriptorUtils.getAllDescriptors(staticScope).isEmpty()) {
printer.println();
printer.println("// Static members");
appendSubDescriptors(descriptor, module, staticScope, Collections.<DeclarationDescriptor>emptyList(), printer);
}
}
else if (descriptor instanceof PackageFragmentDescriptor) {
appendSubDescriptors(descriptor, module,
((PackageFragmentDescriptor) descriptor).getMemberScope(),
Collections.<DeclarationDescriptor>emptyList(), printer);
}
else if (descriptor instanceof PackageViewDescriptor) {
appendSubDescriptors(descriptor, module,
((PackageViewDescriptor) descriptor).getMemberScope(),
Collections.<DeclarationDescriptor>emptyList(), printer);
}
if (!topLevel) {
printer.popIndent().println("}");
}
}
else if (conf.checkPropertyAccessors && descriptor instanceof PropertyDescriptor) {
printer.printlnWithNoIndent();
printer.pushIndent();
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
PropertyGetterDescriptor getter = propertyDescriptor.getGetter();
if (getter != null) {
printer.println(conf.renderer.render(getter));
}
PropertySetterDescriptor setter = propertyDescriptor.getSetter();
if (setter != null) {
printer.println(conf.renderer.render(setter));
}
printer.popIndent();
}
else {
printer.printlnWithNoIndent();
}
if (isEnumEntry) {
printer.println();
}
}
private boolean shouldSkip(@NotNull DeclarationDescriptor subDescriptor) {
boolean isFunctionFromAny = subDescriptor.getContainingDeclaration() instanceof ClassDescriptor
&& subDescriptor instanceof FunctionDescriptor
&& KOTLIN_ANY_METHOD_NAMES.contains(subDescriptor.getName().asString());
return (isFunctionFromAny && !conf.includeMethodsOfKotlinAny) || !conf.recursiveFilter.apply(subDescriptor);
}
private void appendSubDescriptors(
@NotNull DeclarationDescriptor descriptor,
@NotNull ModuleDescriptor module,
@NotNull MemberScope memberScope,
@NotNull Collection<? extends DeclarationDescriptor> extraSubDescriptors,
@NotNull Printer printer
) {
if (!module.equals(DescriptorUtils.getContainingModule(descriptor))) {
printer.println(String.format("// -- Module: %s --", DescriptorUtils.getContainingModule(descriptor).getName()));
return;
}
List<DeclarationDescriptor> subDescriptors = Lists.newArrayList();
subDescriptors.addAll(DescriptorUtils.getAllDescriptors(memberScope));
subDescriptors.addAll(extraSubDescriptors);
Collections.sort(subDescriptors, MemberComparator.INSTANCE);
for (DeclarationDescriptor subDescriptor : subDescriptors) {
if (!shouldSkip(subDescriptor)) {
appendDeclarationRecursively(subDescriptor, module, printer, false);
}
}
}
private static void compareDescriptorWithFile(
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@NotNull File txtFile
) {
doCompareDescriptors(null, actual, configuration, txtFile);
}
public static void compareDescriptors(
@NotNull DeclarationDescriptor expected,
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@Nullable File txtFile
) {
if (expected == actual) {
throw new IllegalArgumentException("Don't invoke this method with expected == actual." +
"Invoke compareDescriptorWithFile() instead.");
}
doCompareDescriptors(expected, actual, configuration, txtFile);
}
public static void validateAndCompareDescriptorWithFile(
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@NotNull File txtFile
) {
DescriptorValidator.validate(configuration.validationStrategy, actual);
compareDescriptorWithFile(actual, configuration, txtFile);
}
public static void validateAndCompareDescriptors(
@NotNull DeclarationDescriptor expected,
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@Nullable File txtFile
) {
DescriptorValidator.validate(configuration.validationStrategy, expected);
DescriptorValidator.validate(configuration.validationStrategy, actual);
compareDescriptors(expected, actual, configuration, txtFile);
}
private static void doCompareDescriptors(
@Nullable DeclarationDescriptor expected,
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@Nullable File txtFile
) {
RecursiveDescriptorComparator comparator = new RecursiveDescriptorComparator(configuration);
String actualSerialized = comparator.serializeRecursively(actual);
if (expected != null) {
String expectedSerialized = comparator.serializeRecursively(expected);
Assert.assertEquals("Expected and actual descriptors differ", expectedSerialized, actualSerialized);
}
if (txtFile != null) {
KotlinTestUtils.assertEqualsToFile(txtFile, actualSerialized);
}
}
public static class Configuration {
private final boolean checkPrimaryConstructors;
private final boolean checkPropertyAccessors;
private final boolean includeMethodsOfKotlinAny;
private final Predicate<DeclarationDescriptor> recursiveFilter;
private final DescriptorRenderer renderer;
private final DescriptorValidator.ValidationVisitor validationStrategy;
public Configuration(
boolean checkPrimaryConstructors,
boolean checkPropertyAccessors,
boolean includeMethodsOfKotlinAny,
Predicate<DeclarationDescriptor> recursiveFilter,
DescriptorValidator.ValidationVisitor validationStrategy,
DescriptorRenderer renderer
) {
this.checkPrimaryConstructors = checkPrimaryConstructors;
this.checkPropertyAccessors = checkPropertyAccessors;
this.includeMethodsOfKotlinAny = includeMethodsOfKotlinAny;
this.recursiveFilter = recursiveFilter;
this.validationStrategy = validationStrategy;
this.renderer = renderer;
}
public Configuration filterRecursion(@NotNull Predicate<DeclarationDescriptor> stepIntoFilter) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny, stepIntoFilter,
validationStrategy.withStepIntoFilter(stepIntoFilter), renderer);
}
public Configuration checkPrimaryConstructors(boolean checkPrimaryConstructors) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny, recursiveFilter,
validationStrategy, renderer);
}
public Configuration checkPropertyAccessors(boolean checkPropertyAccessors) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny, recursiveFilter,
validationStrategy, renderer);
}
public Configuration includeMethodsOfKotlinAny(boolean includeMethodsOfKotlinAny) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny, recursiveFilter,
validationStrategy, renderer);
}
public Configuration withValidationStrategy(@NotNull DescriptorValidator.ValidationVisitor validationStrategy) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny, recursiveFilter,
validationStrategy, renderer);
}
public Configuration withRenderer(@NotNull DescriptorRenderer renderer) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny, recursiveFilter,
validationStrategy, renderer);
}
}
}
@@ -0,0 +1,148 @@
/*
* 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.test.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import java.util.Collection;
public class RecursiveDescriptorProcessor {
public static <D> boolean process(
@NotNull DeclarationDescriptor descriptor,
D data,
@NotNull DeclarationDescriptorVisitor<Boolean, D> visitor
) {
return descriptor.accept(new RecursiveVisitor<D>(visitor), data);
}
private static class RecursiveVisitor<D> implements DeclarationDescriptorVisitor<Boolean, D> {
private final DeclarationDescriptorVisitor<Boolean, D> worker;
private RecursiveVisitor(@NotNull DeclarationDescriptorVisitor<Boolean, D> worker) {
this.worker = worker;
}
private boolean visitChildren(Collection<? extends DeclarationDescriptor> descriptors, D data) {
for (DeclarationDescriptor descriptor : descriptors) {
if (!descriptor.accept(this, data)) return false;
}
return true;
}
private boolean visitChildren(@Nullable DeclarationDescriptor descriptor, D data) {
if (descriptor == null) return true;
return descriptor.accept(this, data);
}
private boolean applyWorker(@NotNull DeclarationDescriptor descriptor, D data) {
return descriptor.accept(worker, data);
}
private boolean processCallable(CallableDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(descriptor.getTypeParameters(), data)
&& visitChildren(descriptor.getExtensionReceiverParameter(), data)
&& visitChildren(descriptor.getValueParameters(), data);
}
@Override
public Boolean visitPackageFragmentDescriptor(PackageFragmentDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getMemberScope()), data);
}
@Override
public Boolean visitPackageViewDescriptor(PackageViewDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getMemberScope()), data);
}
@Override
public Boolean visitVariableDescriptor(VariableDescriptor descriptor, D data) {
return processCallable(descriptor, data);
}
@Override
public Boolean visitPropertyDescriptor(PropertyDescriptor descriptor, D data) {
return processCallable(descriptor, data)
&& visitChildren(descriptor.getGetter(), data)
&& visitChildren(descriptor.getSetter(), data);
}
@Override
public Boolean visitFunctionDescriptor(FunctionDescriptor descriptor, D data) {
return processCallable(descriptor, data);
}
@Override
public Boolean visitTypeParameterDescriptor(TypeParameterDescriptor descriptor, D data) {
return applyWorker(descriptor, data);
}
@Override
public Boolean visitClassDescriptor(ClassDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(descriptor.getThisAsReceiverParameter(), data)
&& visitChildren(descriptor.getConstructors(), data)
&& visitChildren(descriptor.getTypeConstructor().getParameters(), data)
&& visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getDefaultType().getMemberScope()), data);
}
@Override
public Boolean visitModuleDeclaration(ModuleDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(descriptor.getPackage(FqName.ROOT), data);
}
@Override
public Boolean visitConstructorDescriptor(ConstructorDescriptor constructorDescriptor, D data) {
return visitFunctionDescriptor(constructorDescriptor, data);
}
@Override
public Boolean visitScriptDescriptor(ScriptDescriptor scriptDescriptor, D data) {
return visitClassDescriptor(scriptDescriptor, data);
}
@Override
public Boolean visitValueParameterDescriptor(ValueParameterDescriptor descriptor, D data) {
return visitVariableDescriptor(descriptor, data);
}
@Override
public Boolean visitPropertyGetterDescriptor(PropertyGetterDescriptor descriptor, D data) {
return visitFunctionDescriptor(descriptor, data);
}
@Override
public Boolean visitPropertySetterDescriptor(PropertySetterDescriptor descriptor, D data) {
return visitFunctionDescriptor(descriptor, data);
}
@Override
public Boolean visitReceiverParameterDescriptor(ReceiverParameterDescriptor descriptor, D data) {
return applyWorker(descriptor, data);
}
}
}
@@ -0,0 +1,57 @@
/*
* 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.test.util
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.SmartFMap
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
fun String.trimTrailingWhitespacesAndAddNewlineAtEOF(): String =
this.split('\n').map { it.trimEnd() }.joinToString(separator = "\n").let {
result -> if (result.endsWith("\n")) result else result + "\n"
}
fun PsiFile.findElementByCommentPrefix(commentText: String): PsiElement? =
findElementsByCommentPrefix(commentText).keys.singleOrNull()
fun PsiFile.findElementsByCommentPrefix(prefix: String): Map<PsiElement, String> {
var result = SmartFMap.emptyMap<PsiElement, String>()
accept(
object : KtTreeVisitorVoid() {
override fun visitComment(comment: PsiComment) {
val commentText = comment.text
if (commentText.startsWith(prefix)) {
val parent = comment.parent
val elementToAdd = when (parent) {
is KtDeclaration -> parent
is PsiMember -> parent
else -> PsiTreeUtil.skipSiblingsForward(
comment,
PsiWhiteSpace::class.java, PsiComment::class.java, KtPackageDirective::class.java
)
} as? PsiElement ?: return
result = result.plus(elementToAdd, commentText.substring(prefix.length).trim())
}
}
}
)
return result
}
@@ -0,0 +1,46 @@
/*
* 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.tests.di
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.container.*
import org.jetbrains.kotlin.context.ModuleContext
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.frontend.di.configureModule
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.resolve.DescriptorResolver
import org.jetbrains.kotlin.resolve.FunctionDescriptorResolver
import org.jetbrains.kotlin.resolve.TypeResolver
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
import org.jetbrains.kotlin.types.expressions.FakeCallResolver
fun createContainerForTests(project: Project, module: ModuleDescriptor): ContainerForTests {
return ContainerForTests(createContainer("Tests") {
configureModule(ModuleContext(module, project), JvmPlatform)
useInstance(LookupTracker.DO_NOTHING)
useImpl<ExpressionTypingServices>()
})
}
class ContainerForTests(container: StorageComponentContainer) {
val descriptorResolver: DescriptorResolver by container
val functionDescriptorResolver: FunctionDescriptorResolver by container
val typeResolver: TypeResolver by container
val fakeCallResolver: FakeCallResolver by container
val expressionTypingServices: ExpressionTypingServices by container
}
+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$" isTestSource="true" />
</content>
<orderEntry type="jdk" jdkName="1.6" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="descriptors" />
<orderEntry type="module" module-name="backend" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="frontend.java" />
<orderEntry type="module" module-name="cli" />
<orderEntry type="module" module-name="light-classes" />
<orderEntry type="library" name="dx-android" level="project" />
<orderEntry type="module" module-name="serialization" />
<orderEntry type="module" module-name="builtins-serializer" />
<orderEntry type="module" module-name="descriptors.runtime" />
<orderEntry type="module" module-name="js.frontend" />
<orderEntry type="module" module-name="js.serializer" />
<orderEntry type="module" module-name="preloader" />
<orderEntry type="module" module-name="android-extensions-compiler" scope="TEST" />
<orderEntry type="module" module-name="js.serializer" />
<orderEntry type="module" module-name="js.translator" scope="TEST" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="daemon-client" />
<orderEntry type="module" module-name="daemon-common" />
<orderEntry type="library" scope="TEST" name="kotlin-test" level="project" />
<orderEntry type="library" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" name="intellij-core" level="project" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../ideaSDK/lib/commons-httpclient-3.1-patched.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../dependencies/idea/idea-for-compiler-test/idea_rt.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../dependencies/idea/idea-for-compiler-test/openapi.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/../../ideaSDK/core/asm-all.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>