Move common test parts to tests-common. Minify test jar dependencies
This commit is contained in:
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.psi.PsiClass
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
||||
import org.jetbrains.kotlin.checkers.KotlinMultiFileTestWithJava
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractCompilerLightClassTest : KotlinMultiFileTestWithJava<Void, Void>() {
|
||||
override fun getConfigurationKind(): ConfigurationKind = ConfigurationKind.ALL
|
||||
|
||||
override fun isKotlinSourceRootNeeded(): Boolean = true
|
||||
|
||||
override fun doMultiFileTest(file: File, modules: MutableMap<String, ModuleAndDependencies>, files: MutableList<Void>) {
|
||||
val environment = createEnvironment(file)
|
||||
val expectedFile = KotlinTestUtils.replaceExtension(file, "java")
|
||||
LightClassTestCommon.testLightClass(
|
||||
expectedFile,
|
||||
file,
|
||||
{ fqname -> findLightClass(environment, fqname) },
|
||||
LightClassTestCommon::removeEmptyDefaultImpls
|
||||
)
|
||||
}
|
||||
|
||||
override fun createTestModule(name: String): Void? = null
|
||||
|
||||
override fun createTestFile(module: Void?, fileName: String, text: String, directives: Map<String, String>): Void? = null
|
||||
|
||||
companion object {
|
||||
fun findLightClass(environment: KotlinCoreEnvironment, fqname: String): PsiClass? {
|
||||
KotlinTestUtils.resolveAllKotlinFiles(environment)
|
||||
|
||||
val lightCLassForScript = LightClassGenerationSupport
|
||||
.getInstance(environment.project)
|
||||
.getScriptClasses(FqName(fqname), GlobalSearchScope.allScope(environment.project))
|
||||
.firstOrNull()
|
||||
|
||||
return lightCLassForScript ?: JavaElementFinder
|
||||
.getInstance(environment.project)
|
||||
.findClass(fqname, GlobalSearchScope.allScope(environment.project))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.cfg;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public abstract class AbstractControlFlowTest extends AbstractPseudocodeTest {
|
||||
|
||||
@Override
|
||||
protected void dumpInstructions(
|
||||
@NotNull PseudocodeImpl pseudocode,
|
||||
@NotNull StringBuilder out,
|
||||
@NotNull BindingContext bindingContext
|
||||
) {
|
||||
int nextInstructionsColumnWidth = countNextInstructionsColumnWidth(pseudocode.getInstructionsIncludingDeadCode());
|
||||
|
||||
dumpInstructions(pseudocode, out, (instruction, next, prev) -> {
|
||||
StringBuilder result = new StringBuilder();
|
||||
Collection<Instruction> nextInstructions = instruction.getNextInstructions();
|
||||
|
||||
if (!sameContents(next, nextInstructions)) {
|
||||
result.append(" NEXT:").append(
|
||||
String.format("%1$-" + nextInstructionsColumnWidth + "s", formatInstructionList(nextInstructions)));
|
||||
}
|
||||
Collection<Instruction> previousInstructions = instruction.getPreviousInstructions();
|
||||
if (!sameContents(prev, previousInstructions)) {
|
||||
result.append(" PREV:").append(formatInstructionList(previousInstructions));
|
||||
}
|
||||
return result.toString();
|
||||
});
|
||||
}
|
||||
|
||||
private static String formatInstructionList(Collection<Instruction> instructions) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('[');
|
||||
for (Iterator<Instruction> iterator = instructions.iterator(); iterator.hasNext(); ) {
|
||||
Instruction instruction = iterator.next();
|
||||
String instructionText = instruction.toString();
|
||||
String[] parts = instructionText.split("\n");
|
||||
if (parts.length > 1) {
|
||||
StringBuilder instructionSb = new StringBuilder();
|
||||
for (String part : parts) {
|
||||
instructionSb.append(part.trim()).append(' ');
|
||||
}
|
||||
if (instructionSb.toString().length() > 30) {
|
||||
sb.append(instructionSb.substring(0, 28)).append("..)");
|
||||
}
|
||||
else {
|
||||
sb.append(instructionSb);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sb.append(instruction);
|
||||
}
|
||||
if (iterator.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
sb.append(']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
private static int countNextInstructionsColumnWidth(List<Instruction> instructions) {
|
||||
int maxWidth = 0;
|
||||
for (Instruction instruction : instructions) {
|
||||
String instructionListText = formatInstructionList(instruction.getNextInstructions());
|
||||
if (instructionListText.length() > maxWidth) {
|
||||
maxWidth = instructionListText.length();
|
||||
}
|
||||
}
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
private static boolean sameContents(@Nullable Instruction natural, Collection<Instruction> actual) {
|
||||
if (natural == null) {
|
||||
return actual.isEmpty();
|
||||
}
|
||||
return Collections.singleton(natural).equals(new HashSet<>(actual));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkPseudocode(PseudocodeImpl pseudocode) {
|
||||
//check edges directions
|
||||
Collection<Instruction> instructions = pseudocode.getInstructionsIncludingDeadCode();
|
||||
for (Instruction instruction : instructions) {
|
||||
if (!((InstructionImpl)instruction).getMarkedAsDead()) {
|
||||
for (Instruction nextInstruction : instruction.getNextInstructions()) {
|
||||
assertTrue("instruction '" + instruction + "' has '" + nextInstruction + "' among next instructions list, but not vice versa",
|
||||
nextInstruction.getPreviousInstructions().contains(instruction));
|
||||
}
|
||||
for (Instruction prevInstruction : instruction.getPreviousInstructions()) {
|
||||
assertTrue("instruction '" + instruction + "' has '" + prevInstruction + "' among previous instructions list, but not vice versa",
|
||||
prevInstruction.getNextInstructions().contains(instruction));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.cfg;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import javaslang.Tuple2;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.Edges;
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractDataFlowTest extends AbstractPseudocodeTest {
|
||||
|
||||
@Override
|
||||
public void dumpInstructions(
|
||||
@NotNull PseudocodeImpl pseudocode,
|
||||
@NotNull StringBuilder out,
|
||||
@NotNull BindingContext bindingContext
|
||||
) {
|
||||
PseudocodeVariablesData pseudocodeVariablesData = new PseudocodeVariablesData(pseudocode.getRootPseudocode(), bindingContext);
|
||||
Map<Instruction, Edges<ReadOnlyInitControlFlowInfo>> variableInitializers =
|
||||
pseudocodeVariablesData.getVariableInitializers();
|
||||
Map<Instruction, Edges<ReadOnlyControlFlowInfo<VariableUseState>>> useStatusData =
|
||||
pseudocodeVariablesData.getVariableUseStatusData();
|
||||
String initPrefix = " INIT:";
|
||||
String usePrefix = " USE:";
|
||||
int initializersColumnWidth = countDataColumnWidth(initPrefix, pseudocode.getInstructionsIncludingDeadCode(), variableInitializers,
|
||||
pseudocodeVariablesData);
|
||||
|
||||
dumpInstructions(pseudocode, out, (instruction, next, prev) -> {
|
||||
StringBuilder result = new StringBuilder();
|
||||
Edges<ReadOnlyInitControlFlowInfo> initializersEdges = variableInitializers.get(instruction);
|
||||
Edges<ReadOnlyInitControlFlowInfo> previousInitializersEdges = variableInitializers.get(prev);
|
||||
String initializersData = "";
|
||||
if (initializersEdges != null && !initializersEdges.equals(previousInitializersEdges)) {
|
||||
initializersData = dumpEdgesData(initPrefix, initializersEdges, pseudocodeVariablesData);
|
||||
}
|
||||
result.append(String.format("%1$-" + initializersColumnWidth + "s", initializersData));
|
||||
|
||||
Edges<ReadOnlyControlFlowInfo<VariableUseState>> useStatusEdges = useStatusData.get(instruction);
|
||||
Edges<ReadOnlyControlFlowInfo<VariableUseState>> nextUseStatusEdges = useStatusData.get(next);
|
||||
if (useStatusEdges != null && !useStatusEdges.equals(nextUseStatusEdges)) {
|
||||
result.append(dumpEdgesData(usePrefix, useStatusEdges, pseudocodeVariablesData));
|
||||
}
|
||||
return result.toString();
|
||||
});
|
||||
}
|
||||
|
||||
private static int countDataColumnWidth(
|
||||
@NotNull String prefix,
|
||||
@NotNull List<Instruction> instructions,
|
||||
@NotNull Map<Instruction, Edges<ReadOnlyInitControlFlowInfo>> data,
|
||||
@NotNull PseudocodeVariablesData variablesData
|
||||
) {
|
||||
int maxWidth = 0;
|
||||
for (Instruction instruction : instructions) {
|
||||
Edges<ReadOnlyInitControlFlowInfo> edges = data.get(instruction);
|
||||
if (edges == null) continue;
|
||||
int length = dumpEdgesData(prefix, edges, variablesData).length();
|
||||
if (maxWidth < length) {
|
||||
maxWidth = length;
|
||||
}
|
||||
}
|
||||
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static <S, I extends ReadOnlyControlFlowInfo<S>> String dumpEdgesData(
|
||||
String prefix,
|
||||
@NotNull Edges<I> edges,
|
||||
@NotNull PseudocodeVariablesData variablesData
|
||||
) {
|
||||
return prefix +
|
||||
" in: " + renderVariableMap(edges.getIncoming().asMap(), variablesData) +
|
||||
" out: " + renderVariableMap(edges.getOutgoing().asMap(), variablesData);
|
||||
}
|
||||
|
||||
private static <S> String renderVariableMap(
|
||||
javaslang.collection.Map<VariableDescriptor, S> map,
|
||||
PseudocodeVariablesData variablesData
|
||||
) {
|
||||
List<String> result = Lists.newArrayList();
|
||||
for (Tuple2<VariableDescriptor, S> entry : map) {
|
||||
VariableDescriptor variable = entry._1;
|
||||
S state = entry._2;
|
||||
|
||||
if (variablesData.isVariableWithTrivialInitializer(variable)) continue;
|
||||
|
||||
result.add(variable.getName() + "=" + state);
|
||||
}
|
||||
Collections.sort(result);
|
||||
return "{" + StringUtil.join(result, ", ") + "}";
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.cfg;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.checkers.AbstractDiagnosticsTest;
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind;
|
||||
import org.jetbrains.kotlin.test.TestJdkKind;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public abstract class AbstractDiagnosticsWithModifiedMockJdkTest extends AbstractDiagnosticsTest {
|
||||
@NotNull
|
||||
@Override
|
||||
protected ConfigurationKind getConfigurationKind() {
|
||||
return ConfigurationKind.ALL;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected TestJdkKind getTestJdkKind(@NotNull File file) {
|
||||
return TestJdkKind.MODIFIED_MOCK_JDK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.cfg
|
||||
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.TypePredicate
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.getExpectedTypePredicate
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractPseudoValueTest : AbstractPseudocodeTest() {
|
||||
override fun dumpInstructions(pseudocode: PseudocodeImpl, out: StringBuilder, bindingContext: BindingContext) {
|
||||
val expectedTypePredicateMap = HashMap<PseudoValue, TypePredicate>()
|
||||
|
||||
fun getElementToValueMap(pseudocode: PseudocodeImpl): Map<KtElement, PseudoValue> {
|
||||
val elementToValues = LinkedHashMap<KtElement, PseudoValue>()
|
||||
pseudocode.correspondingElement.accept(object : KtTreeVisitorVoid() {
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
super.visitKtElement(element)
|
||||
|
||||
val value = pseudocode.getElementValue(element)
|
||||
if (value != null) {
|
||||
elementToValues.put(element, value)
|
||||
}
|
||||
}
|
||||
})
|
||||
return elementToValues
|
||||
}
|
||||
|
||||
fun elementText(element: KtElement?): String =
|
||||
element?.text?.replace("\\s+".toRegex(), " ") ?: ""
|
||||
|
||||
fun valueDecl(value: PseudoValue): String {
|
||||
val typePredicate = expectedTypePredicateMap.getOrPut(value) {
|
||||
getExpectedTypePredicate(value, bindingContext, DefaultBuiltIns.Instance)
|
||||
}
|
||||
return "${value.debugName}: $typePredicate"
|
||||
}
|
||||
|
||||
fun valueDescription(element: KtElement?, value: PseudoValue): String {
|
||||
return when {
|
||||
value.element != element -> "COPY"
|
||||
else -> value.createdAt?.let { "NEW: $it" } ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
val elementToValues = getElementToValueMap(pseudocode)
|
||||
val unboundValues = pseudocode.instructions
|
||||
.mapNotNull { (it as? InstructionWithValue)?.outputValue }
|
||||
.filter { it.element == null }
|
||||
.sortedBy { it.debugName }
|
||||
val allValues = elementToValues.values + unboundValues
|
||||
if (allValues.isEmpty()) return
|
||||
|
||||
val valueDescriptions = LinkedHashMap<Pair<PseudoValue, KtElement?>, String>()
|
||||
for (value in unboundValues) {
|
||||
valueDescriptions[value to null] = valueDescription(null, value)
|
||||
}
|
||||
for ((element, value) in elementToValues.entries) {
|
||||
valueDescriptions[value to element] = valueDescription(element, value)
|
||||
}
|
||||
|
||||
val elementColumnWidth = elementToValues.keys.map { elementText(it).length }.max() ?: 1
|
||||
val valueColumnWidth = allValues.map { valueDecl(it).length }.max()!!
|
||||
val valueDescColumnWidth = valueDescriptions.values.map { it.length }.max()!!
|
||||
|
||||
for ((ve, description) in valueDescriptions.entries) {
|
||||
val (value, element) = ve
|
||||
val line =
|
||||
"%1$-${elementColumnWidth}s".format(elementText(element)) +
|
||||
" " +
|
||||
"%1$-${valueColumnWidth}s".format(valueDecl(value)) +
|
||||
" " +
|
||||
"%1$-${valueDescColumnWidth}s".format(description)
|
||||
out.appendln(line.trimEnd())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDataFileExtension(): String? = "values"
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.cfg;
|
||||
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import kotlin.jvm.functions.Function3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.analyzer.AnalysisResult;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeLabel;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeUtil;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction;
|
||||
import org.jetbrains.kotlin.checkers.CompilerTestLanguageVersionSettingsKt;
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.KotlinTestWithEnvironmentManagement;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class AbstractPseudocodeTest extends KotlinTestWithEnvironmentManagement {
|
||||
protected void doTestWithStdLib(String fileName) throws Exception {
|
||||
doTestWithEnvironment(fileName, createEnvironmentWithMockJdk(ConfigurationKind.NO_KOTLIN_REFLECT));
|
||||
}
|
||||
|
||||
protected void doTest(String fileName) throws Exception {
|
||||
doTestWithEnvironment(fileName, createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY));
|
||||
}
|
||||
|
||||
private void doTestWithEnvironment(String fileName, KotlinCoreEnvironment environment) throws Exception {
|
||||
File file = new File(fileName);
|
||||
|
||||
CompilerTestLanguageVersionSettingsKt.setupLanguageVersionSettingsForCompilerTests(FileUtil.loadFile(file, true), environment);
|
||||
|
||||
KtFile ktFile = KotlinTestUtils.loadJetFile(environment.getProject(), file);
|
||||
|
||||
SetMultimap<KtElement, Pseudocode> data = LinkedHashMultimap.create();
|
||||
AnalysisResult analysisResult = KotlinTestUtils.analyzeFile(ktFile, environment);
|
||||
List<KtDeclaration> declarations = ktFile.getDeclarations();
|
||||
BindingContext bindingContext = analysisResult.getBindingContext();
|
||||
for (KtDeclaration declaration : declarations) {
|
||||
addDeclaration(data, bindingContext, declaration);
|
||||
|
||||
if (declaration instanceof KtDeclarationContainer) {
|
||||
for (KtDeclaration member : ((KtDeclarationContainer) declaration).getDeclarations()) {
|
||||
// Properties and initializers are processed elsewhere
|
||||
if (member instanceof KtNamedFunction || member instanceof KtSecondaryConstructor) {
|
||||
addDeclaration(data, bindingContext, member);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
processCFData(file, data, bindingContext);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally {
|
||||
if ("true".equals(System.getProperty("kotlin.control.flow.test.dump.graphs"))) {
|
||||
CFGraphToDotFilePrinter.dumpDot(file, data.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addDeclaration(SetMultimap<KtElement, Pseudocode> data, BindingContext bindingContext, KtDeclaration declaration) {
|
||||
Pseudocode pseudocode = PseudocodeUtil.generatePseudocode(declaration, bindingContext);
|
||||
data.put(declaration, pseudocode);
|
||||
for (LocalFunctionDeclarationInstruction instruction : pseudocode.getLocalDeclarations()) {
|
||||
Pseudocode localPseudocode = instruction.getBody();
|
||||
data.put(localPseudocode.getCorrespondingElement(), localPseudocode);
|
||||
}
|
||||
}
|
||||
|
||||
private void processCFData(File file, SetMultimap<KtElement, Pseudocode> data, BindingContext bindingContext) throws IOException {
|
||||
Collection<Pseudocode> pseudocodes = data.values();
|
||||
|
||||
StringBuilder instructionDump = new StringBuilder();
|
||||
int i = 0;
|
||||
for (Pseudocode pseudocode : pseudocodes) {
|
||||
KtElement correspondingElement = pseudocode.getCorrespondingElement();
|
||||
String label;
|
||||
assert (correspondingElement instanceof KtNamedDeclaration || correspondingElement instanceof KtPropertyAccessor) :
|
||||
"Unexpected element class is pseudocode: " + correspondingElement.getClass();
|
||||
boolean isAnonymousFunction =
|
||||
correspondingElement instanceof KtFunctionLiteral
|
||||
|| (correspondingElement instanceof KtNamedFunction && correspondingElement.getName() == null);
|
||||
if (isAnonymousFunction) {
|
||||
label = "anonymous_" + i++;
|
||||
}
|
||||
else if (correspondingElement instanceof KtNamedDeclaration) {
|
||||
KtNamedDeclaration namedDeclaration = (KtNamedDeclaration) correspondingElement;
|
||||
label = namedDeclaration.getName();
|
||||
}
|
||||
else {
|
||||
String propertyName = ((KtProperty) correspondingElement.getParent()).getName();
|
||||
label = (((KtPropertyAccessor) correspondingElement).isGetter() ? "get" : "set") + "_" + propertyName;
|
||||
}
|
||||
|
||||
if (pseudocode.isInlined()) {
|
||||
label = "inlined " + label;
|
||||
}
|
||||
|
||||
instructionDump.append("== ").append(label).append(" ==\n");
|
||||
|
||||
instructionDump.append(correspondingElement.getText());
|
||||
instructionDump.append("\n---------------------\n");
|
||||
dumpInstructions((PseudocodeImpl) pseudocode, instructionDump, bindingContext);
|
||||
instructionDump.append("=====================\n");
|
||||
checkPseudocode((PseudocodeImpl) pseudocode);
|
||||
}
|
||||
|
||||
File expectedInstructionsFile = KotlinTestUtils.replaceExtension(file, getDataFileExtension());
|
||||
KotlinTestUtils.assertEqualsToFile(expectedInstructionsFile, instructionDump.toString());
|
||||
}
|
||||
|
||||
protected String getDataFileExtension() {
|
||||
return "instructions";
|
||||
}
|
||||
|
||||
protected void checkPseudocode(PseudocodeImpl pseudocode) {
|
||||
}
|
||||
|
||||
private static String getIsDeadInstructionPrefix(
|
||||
@NotNull Instruction instruction,
|
||||
@NotNull Set<Instruction> remainedAfterPostProcessInstructions
|
||||
) {
|
||||
boolean isRemovedThroughPostProcess = !remainedAfterPostProcessInstructions.contains(instruction);
|
||||
assert isRemovedThroughPostProcess == ((InstructionImpl)instruction).getMarkedAsDead();
|
||||
return isRemovedThroughPostProcess ? "-" : " ";
|
||||
}
|
||||
|
||||
private static String getDepthInstructionPrefix(@NotNull Instruction instruction, @Nullable Instruction previous) {
|
||||
Integer prevDepth = previous != null ? previous.getBlockScope().getDepth() : null;
|
||||
int depth = instruction.getBlockScope().getDepth();
|
||||
if (prevDepth == null || depth != prevDepth) {
|
||||
return String.format("%2d ", depth);
|
||||
}
|
||||
return " ";
|
||||
}
|
||||
|
||||
private static String formatInstruction(Instruction instruction, int maxLength, String prefix) {
|
||||
String[] parts = instruction.toString().split("\n");
|
||||
|
||||
if (parts.length == 1) {
|
||||
return prefix + String.format("%1$-" + maxLength + "s", instruction);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0, partsLength = parts.length; i < partsLength; i++) {
|
||||
String part = parts[i];
|
||||
sb.append(prefix).append(String.format("%1$-" + maxLength + "s", part));
|
||||
if (i < partsLength - 1) sb.append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
protected abstract void dumpInstructions(
|
||||
@NotNull PseudocodeImpl pseudocode,
|
||||
@NotNull StringBuilder out,
|
||||
@NotNull BindingContext bindingContext
|
||||
);
|
||||
|
||||
protected void dumpInstructions(
|
||||
@NotNull PseudocodeImpl pseudocode,
|
||||
@NotNull StringBuilder out,
|
||||
@NotNull Function3<Instruction, /*next*/Instruction, /*prev*/Instruction, String> getInstructionData
|
||||
) {
|
||||
List<Instruction> instructions = pseudocode.getInstructionsIncludingDeadCode();
|
||||
Set<Instruction> remainedAfterPostProcessInstructions = Sets.newHashSet(pseudocode.getInstructions());
|
||||
List<PseudocodeLabel> labels = pseudocode.getLabels();
|
||||
int instructionColumnWidth = countInstructionColumnWidth(instructions);
|
||||
|
||||
for (int i = 0; i < instructions.size(); i++) {
|
||||
Instruction instruction = instructions.get(i);
|
||||
for (PseudocodeLabel label: labels) {
|
||||
if (label.getTargetInstructionIndex() == i) {
|
||||
out.append(label).append(":\n");
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder line = new StringBuilder();
|
||||
|
||||
// Only print NEXT and PREV if the values are non-trivial
|
||||
Instruction next = i == instructions.size() - 1 ? null : instructions.get(i + 1);
|
||||
Instruction prev = i == 0 ? null : instructions.get(i - 1);
|
||||
|
||||
String prefix = getIsDeadInstructionPrefix(instruction, remainedAfterPostProcessInstructions) +
|
||||
getDepthInstructionPrefix(instruction, prev);
|
||||
line.append(formatInstruction(instruction, instructionColumnWidth, prefix));
|
||||
|
||||
line.append(getInstructionData.invoke(instruction, next, prev));
|
||||
|
||||
out.append(StringUtil.trimTrailing(line.toString()));
|
||||
out.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static int countInstructionColumnWidth(List<Instruction> instructions) {
|
||||
int maxWidth = 0;
|
||||
for (Instruction instruction : instructions) {
|
||||
String instuctionText = instruction.toString();
|
||||
if (instuctionText.length() > maxWidth) {
|
||||
String[] parts = instuctionText.split("\n");
|
||||
if (parts.length > 1) {
|
||||
for (String part : parts) {
|
||||
if (part.length() > maxWidth) {
|
||||
maxWidth = part.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (instuctionText.length() > maxWidth) {
|
||||
maxWidth = instuctionText.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxWidth;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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.cfg;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionWithNext;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.*;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction;
|
||||
import org.jetbrains.kotlin.psi.KtElement;
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.*;
|
||||
|
||||
public class CFGraphToDotFilePrinter {
|
||||
public static void dumpDot(File file, Collection<Pseudocode> pseudocodes) throws FileNotFoundException {
|
||||
File target = KotlinTestUtils.replaceExtension(file, "dot");
|
||||
|
||||
PrintStream out = new PrintStream(target);
|
||||
|
||||
out.println("digraph " + FileUtil.getNameWithoutExtension(file) + " {");
|
||||
int[] count = new int[1];
|
||||
Map<Instruction, String> nodeToName = new HashMap<>();
|
||||
for (Pseudocode pseudocode : pseudocodes) {
|
||||
dumpNodes(pseudocode.getInstructionsIncludingDeadCode(), out, count, nodeToName, Sets
|
||||
.newHashSet(pseudocode.getInstructions()));
|
||||
}
|
||||
int i = 0;
|
||||
for (Pseudocode pseudocode : pseudocodes) {
|
||||
String label;
|
||||
KtElement correspondingElement = pseudocode.getCorrespondingElement();
|
||||
if (correspondingElement instanceof KtNamedDeclaration) {
|
||||
KtNamedDeclaration namedDeclaration = (KtNamedDeclaration) correspondingElement;
|
||||
label = namedDeclaration.getName();
|
||||
}
|
||||
else {
|
||||
label = "anonymous_" + i;
|
||||
}
|
||||
out.println("subgraph cluster_" + i + " {\n" +
|
||||
"label=\"" + label + "\";\n" +
|
||||
"color=blue;\n");
|
||||
dumpEdges(pseudocode.getInstructionsIncludingDeadCode(), out, count, nodeToName);
|
||||
out.println("}");
|
||||
i++;
|
||||
}
|
||||
out.println("}");
|
||||
out.close();
|
||||
}
|
||||
|
||||
private static void dumpEdges(List<Instruction> instructions, PrintStream out, int[] count, Map<Instruction, String> nodeToName) {
|
||||
for (Instruction fromInst : instructions) {
|
||||
fromInst.accept(new InstructionVisitor() {
|
||||
@Override
|
||||
public void visitLocalFunctionDeclarationInstruction(@NotNull LocalFunctionDeclarationInstruction instruction) {
|
||||
int index = count[0];
|
||||
// instruction.getBody().dumpSubgraph(out, "subgraph cluster_" + index, count, "color=blue;\nlabel = \"f" + index + "\";", nodeToName);
|
||||
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getBody().getInstructionsIncludingDeadCode().get(0)), null);
|
||||
visitInstructionWithNext(instruction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUnconditionalJump(@NotNull UnconditionalJumpInstruction instruction) {
|
||||
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getResolvedTarget()), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitJump(@NotNull AbstractJumpInstruction instruction) {
|
||||
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getResolvedTarget()), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNondeterministicJump(@NotNull NondeterministicJumpInstruction instruction) {
|
||||
for (Instruction nextInstruction : instruction.getNextInstructions()) {
|
||||
printEdge(out, nodeToName.get(instruction), nodeToName.get(nextInstruction), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReturnValue(@NotNull ReturnValueInstruction instruction) {
|
||||
super.visitReturnValue(instruction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReturnNoValue(@NotNull ReturnNoValueInstruction instruction) {
|
||||
super.visitReturnNoValue(instruction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitConditionalJump(@NotNull ConditionalJumpInstruction instruction) {
|
||||
String from = nodeToName.get(instruction);
|
||||
printEdge(out, from, nodeToName.get(instruction.getNextOnFalse()), "no");
|
||||
printEdge(out, from, nodeToName.get(instruction.getNextOnTrue()), "yes");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInstructionWithNext(@NotNull InstructionWithNext instruction) {
|
||||
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getNext()), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSubroutineExit(@NotNull SubroutineExitInstruction instruction) {
|
||||
if (!instruction.getNextInstructions().isEmpty()) {
|
||||
printEdge(out, nodeToName.get(instruction), nodeToName.get(instruction.getNextInstructions().iterator().next()), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSubroutineSink(@NotNull SubroutineSinkInstruction instruction) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInstruction(@NotNull Instruction instruction) {
|
||||
throw new UnsupportedOperationException(instruction.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpNodes(List<Instruction> instructions, PrintStream out, int[] count, Map<Instruction, String> nodeToName, Set<Instruction> remainedAfterPostProcessInstructions) {
|
||||
for (Instruction node : instructions) {
|
||||
String name = "n" + count[0]++;
|
||||
nodeToName.put(node, name);
|
||||
String text = node.toString();
|
||||
int newline = text.indexOf("\n");
|
||||
if (newline >= 0) {
|
||||
text = text.substring(0, newline);
|
||||
}
|
||||
String shape = "box";
|
||||
if (node instanceof ConditionalJumpInstruction || node instanceof UnconditionalJumpInstruction) {
|
||||
shape = "diamond";
|
||||
}
|
||||
else if (node instanceof NondeterministicJumpInstruction) {
|
||||
shape = "Mdiamond";
|
||||
}
|
||||
else if (node instanceof MagicInstruction && ((MagicInstruction) node).getKind() == MagicKind.UNSUPPORTED_ELEMENT) {
|
||||
shape = "box, fillcolor=red, style=filled";
|
||||
}
|
||||
else if (node instanceof LocalFunctionDeclarationInstruction) {
|
||||
shape = "Mcircle";
|
||||
}
|
||||
else if (node instanceof SubroutineEnterInstruction || node instanceof SubroutineExitInstruction) {
|
||||
shape = "roundrect, style=rounded";
|
||||
}
|
||||
if (!remainedAfterPostProcessInstructions.contains(node)) {
|
||||
shape += "box, fillcolor=grey, style=filled";
|
||||
}
|
||||
out.println(name + "[label=\"" + text + "\", shape=" + shape + "];");
|
||||
}
|
||||
}
|
||||
|
||||
private static void printEdge(PrintStream out, String from, String to, String label) {
|
||||
if (label != null) {
|
||||
label = "[label=\"" + label + "\"]";
|
||||
}
|
||||
else {
|
||||
label = "";
|
||||
}
|
||||
out.println(from + " -> " + to + label + ";");
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.javac
|
||||
|
||||
import org.jetbrains.kotlin.checkers.AbstractDiagnosticsTestWithStdLib
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils.getHomeDirectory
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractDiagnosticsTestWithStdLibUsingJavac : AbstractDiagnosticsTestWithStdLib() {
|
||||
|
||||
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
|
||||
val testDataFileText = testDataFile.readText()
|
||||
if (InTextDirectivesUtils.isDirectiveDefined(testDataFileText, "// JAVAC_SKIP")) {
|
||||
println("${testDataFile.name} test is skipped")
|
||||
return
|
||||
}
|
||||
val groupedByModule = files.groupBy(TestFile::module)
|
||||
val allKtFiles = groupedByModule.values.flatMap { getKtFiles(it, true) }
|
||||
|
||||
if (InTextDirectivesUtils.isDirectiveDefined(testDataFileText, "// FULL_JDK")) {
|
||||
environment.registerJavac(kotlinFiles = allKtFiles)
|
||||
}
|
||||
else {
|
||||
val mockJdk = listOf(File(getHomeDirectory(), "compiler/testData/mockJDK/jre/lib/rt.jar"))
|
||||
environment.registerJavac(kotlinFiles = allKtFiles, bootClasspath = mockJdk)
|
||||
}
|
||||
|
||||
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
|
||||
super.analyzeAndCheck(testDataFile, files)
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.javac
|
||||
|
||||
import org.jetbrains.kotlin.checkers.AbstractForeignAnnotationsTest
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractJavacForeignAnnotationsTest : AbstractForeignAnnotationsTest() {
|
||||
|
||||
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
|
||||
val groupedByModule = files.groupBy(TestFile::module)
|
||||
val allKtFiles = groupedByModule.values.flatMap { getKtFiles(it, true) }
|
||||
environment.registerJavac(kotlinFiles = allKtFiles)
|
||||
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
|
||||
|
||||
super.analyzeAndCheck(testDataFile, files)
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.javac
|
||||
|
||||
import org.jetbrains.kotlin.checkers.AbstractForeignJava8AnnotationsTest
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractJavacForeignJava8AnnotationsTest : AbstractForeignJava8AnnotationsTest() {
|
||||
|
||||
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
|
||||
if (files.any { file -> InTextDirectivesUtils.isDirectiveDefined(file.expectedText, "// SKIP_JAVAC") }) return
|
||||
|
||||
val groupedByModule = files.groupBy(TestFile::module)
|
||||
val allKtFiles = groupedByModule.values.flatMap { getKtFiles(it, true) }
|
||||
environment.registerJavac(kotlinFiles = allKtFiles)
|
||||
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
|
||||
|
||||
super.analyzeAndCheck(testDataFile, files)
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.checkers
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.context.ModuleContext
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
||||
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
|
||||
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
|
||||
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
|
||||
import org.jetbrains.kotlin.js.config.JsConfig
|
||||
import org.jetbrains.kotlin.js.resolve.JsPlatform
|
||||
import org.jetbrains.kotlin.js.resolve.MODULE_KIND
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractDiagnosticsTestWithJsStdLib : AbstractDiagnosticsTest() {
|
||||
private var lazyConfig: Lazy<JsConfig>? = lazy(LazyThreadSafetyMode.NONE) {
|
||||
JsConfig(project, environment.configuration.copy().apply {
|
||||
put(CommonConfigurationKeys.MODULE_NAME, KotlinTestUtils.TEST_MODULE_NAME)
|
||||
put(JSConfigurationKeys.LIBRARIES, JsConfig.JS_STDLIB)
|
||||
})
|
||||
}
|
||||
|
||||
protected val config: JsConfig get() = lazyConfig!!.value
|
||||
|
||||
override fun tearDown() {
|
||||
lazyConfig = null
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
override fun getEnvironmentConfigFiles(): EnvironmentConfigFiles = EnvironmentConfigFiles.JS_CONFIG_FILES
|
||||
|
||||
override fun analyzeModuleContents(
|
||||
moduleContext: ModuleContext,
|
||||
files: List<KtFile>,
|
||||
moduleTrace: BindingTrace,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
separateModules: Boolean
|
||||
): JsAnalysisResult {
|
||||
// TODO: support LANGUAGE directive in JS diagnostic tests
|
||||
moduleTrace.record<ModuleDescriptor, ModuleKind>(MODULE_KIND, moduleContext.module, getModuleKind(files))
|
||||
return TopDownAnalyzerFacadeForJS.analyzeFilesWithGivenTrace(files, moduleTrace, moduleContext, config)
|
||||
}
|
||||
|
||||
private fun getModuleKind(ktFiles: List<KtFile>): ModuleKind {
|
||||
var kind = ModuleKind.PLAIN
|
||||
for (file in ktFiles) {
|
||||
val text = file.text
|
||||
for (textLine in StringUtil.splitByLines(text)) {
|
||||
var line = textLine.trim { it <= ' ' }
|
||||
if (!line.startsWith("//")) continue
|
||||
line = line.substring(2).trim { it <= ' ' }
|
||||
val parts = StringUtil.split(line, ":")
|
||||
if (parts.size != 2) continue
|
||||
|
||||
if (parts[0].trim { it <= ' ' } != "MODULE_KIND") continue
|
||||
kind = ModuleKind.valueOf(parts[1].trim { it <= ' ' })
|
||||
}
|
||||
}
|
||||
|
||||
return kind
|
||||
}
|
||||
|
||||
override fun getAdditionalDependencies(module: ModuleDescriptorImpl): List<ModuleDescriptorImpl> =
|
||||
config.moduleDescriptors.map { it.data }
|
||||
|
||||
override fun shouldSkipJvmSignatureDiagnostics(groupedByModule: Map<TestModule?, List<TestFile>>): Boolean = true
|
||||
|
||||
override fun createModule(moduleName: String, storageManager: StorageManager): ModuleDescriptorImpl =
|
||||
ModuleDescriptorImpl(Name.special("<$moduleName>"), storageManager, JsPlatform.builtIns)
|
||||
|
||||
override fun createSealedModule(storageManager: StorageManager): ModuleDescriptorImpl {
|
||||
val module = createModule("kotlin-js-test-module", storageManager)
|
||||
|
||||
val dependencies = ArrayList<ModuleDescriptorImpl>()
|
||||
dependencies.add(module)
|
||||
|
||||
dependencies.addAll(getAdditionalDependencies(module))
|
||||
|
||||
dependencies.add(module.builtIns.builtInsModule)
|
||||
module.setDependencies(dependencies)
|
||||
|
||||
return module
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.checkers
|
||||
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.context.ModuleContext
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils.hasError
|
||||
import org.jetbrains.kotlin.js.analyzer.JsAnalysisResult
|
||||
import org.jetbrains.kotlin.js.config.JsConfig
|
||||
import org.jetbrains.kotlin.js.facade.K2JSTranslator
|
||||
import org.jetbrains.kotlin.js.facade.MainCallParameters
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
|
||||
abstract class AbstractDiagnosticsTestWithJsStdLibAndBackendCompilation : AbstractDiagnosticsTestWithJsStdLib() {
|
||||
override fun analyzeModuleContents(
|
||||
moduleContext: ModuleContext,
|
||||
files: List<KtFile>,
|
||||
moduleTrace: BindingTrace,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
separateModules: Boolean
|
||||
): JsAnalysisResult {
|
||||
val analysisResult = super.analyzeModuleContents(moduleContext, files, moduleTrace, languageVersionSettings, separateModules)
|
||||
val diagnostics = analysisResult.bindingTrace.bindingContext.diagnostics
|
||||
|
||||
if (!hasError(diagnostics)) {
|
||||
val translator = K2JSTranslator(config)
|
||||
translator.translate(object : JsConfig.Reporter() {}, files, MainCallParameters.noCall(), analysisResult)
|
||||
}
|
||||
|
||||
return analysisResult
|
||||
}
|
||||
}
|
||||
+28
@@ -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.checkers;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind;
|
||||
|
||||
public abstract class AbstractDiagnosticsTestWithStdLib extends AbstractDiagnosticsTest {
|
||||
@NotNull
|
||||
@Override
|
||||
protected ConfigurationKind getConfigurationKind() {
|
||||
return ConfigurationKind.ALL;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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 org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TestJdkKind;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractDiagnosticsWithJdk9Test extends AbstractDiagnosticsTest {
|
||||
@NotNull
|
||||
@Override
|
||||
protected ConfigurationKind getConfigurationKind() {
|
||||
return ConfigurationKind.ALL;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected TestJdkKind getTestJdkKind(@NotNull File file) {
|
||||
return TestJdkKind.FULL_JDK_9;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doMultiFileTest(
|
||||
@NotNull File file,
|
||||
@NotNull Map<String, ModuleAndDependencies> modules,
|
||||
@NotNull List<TestFile> testFiles
|
||||
) {
|
||||
if (KotlinTestUtils.getJdk9HomeIfPossible() == null) {
|
||||
// Skip this test if no Java 9 is found
|
||||
return;
|
||||
}
|
||||
super.doMultiFileTest(file, modules, testFiles);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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 org.jetbrains.kotlin.codegen.CodegenTestUtil
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractForeignAnnotationsNoAnnotationInClasspathTest : AbstractForeignAnnotationsTest() {
|
||||
private val compiledJavaPath = KotlinTestUtils.tmpDir("java-compiled-files")
|
||||
|
||||
override fun getExtraClasspath(): List<File> {
|
||||
val foreignAnnotations = createJarWithForeignAnnotations()
|
||||
val testAnnotations = compileTestAnnotations(foreignAnnotations)
|
||||
|
||||
val additionalClasspath = (foreignAnnotations + testAnnotations).map { it.path }
|
||||
CodegenTestUtil.compileJava(
|
||||
CodegenTestUtil.findJavaSourcesInDirectory(javaFilesDir),
|
||||
additionalClasspath, emptyList(),
|
||||
compiledJavaPath
|
||||
)
|
||||
|
||||
return listOf(compiledJavaPath) + testAnnotations
|
||||
}
|
||||
|
||||
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
|
||||
if (files.any { file -> InTextDirectivesUtils.isDirectiveDefined(file.expectedText, "// SOURCE_RETENTION_ANNOTATIONS") }) return
|
||||
super.analyzeAndCheck(testDataFile, files)
|
||||
}
|
||||
|
||||
override fun isJavaSourceRootNeeded() = false
|
||||
override fun skipDescriptorsValidation() = true
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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 org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
|
||||
abstract class AbstractForeignAnnotationsNoAnnotationInClasspathWithFastClassReadingTest : AbstractForeignAnnotationsNoAnnotationInClasspathTest() {
|
||||
override fun performCustomConfiguration(configuration: CompilerConfiguration) {
|
||||
super.performCustomConfiguration(configuration)
|
||||
configuration.put(JVMConfigurationKeys.USE_FAST_CLASS_FILES_READING, true)
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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 org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
||||
import org.jetbrains.kotlin.config.AnalysisFlag
|
||||
import org.jetbrains.kotlin.config.ApiVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersion
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.MockLibraryUtil
|
||||
import org.jetbrains.kotlin.test.TestJdkKind
|
||||
import org.jetbrains.kotlin.utils.Jsr305State
|
||||
import org.jetbrains.kotlin.utils.ReportLevel
|
||||
import java.io.File
|
||||
|
||||
val FOREIGN_ANNOTATIONS_SOURCES_PATH = "third-party/annotations"
|
||||
val TEST_ANNOTATIONS_SOURCE_PATH = "compiler/testData/foreignAnnotations/testAnnotations"
|
||||
|
||||
abstract class AbstractForeignAnnotationsTest : AbstractDiagnosticsTest() {
|
||||
private val JSR305_GLOBAL_DIRECTIVE = "JSR305_GLOBAL_REPORT"
|
||||
private val JSR305_MIGRATION_DIRECTIVE = "JSR305_MIGRATION_REPORT"
|
||||
private val JSR305_SPECIAL_DIRECTIVE = "JSR305_SPECIAL_REPORT"
|
||||
|
||||
override fun getExtraClasspath(): List<File> {
|
||||
val foreignAnnotations = createJarWithForeignAnnotations()
|
||||
return foreignAnnotations + compileTestAnnotations(foreignAnnotations)
|
||||
}
|
||||
|
||||
protected fun compileTestAnnotations(extraClassPath: List<File>): List<File> =
|
||||
listOf(MockLibraryUtil.compileJavaFilesLibraryToJar(
|
||||
TEST_ANNOTATIONS_SOURCE_PATH,
|
||||
"test-foreign-annotations",
|
||||
extraOptions = listOf("-Xallow-kotlin-package"),
|
||||
extraClasspath = extraClassPath.map { it.path }
|
||||
))
|
||||
|
||||
protected fun createJarWithForeignAnnotations(): List<File> = listOf(
|
||||
MockLibraryUtil.compileJavaFilesLibraryToJar(annotationsPath, "foreign-annotations"),
|
||||
ForTestCompileRuntime.jvmAnnotationsForTests()
|
||||
)
|
||||
|
||||
override fun getConfigurationKind(): ConfigurationKind = ConfigurationKind.ALL
|
||||
|
||||
override fun getTestJdkKind(file: File): TestJdkKind = TestJdkKind.FULL_JDK
|
||||
|
||||
open protected val annotationsPath: String
|
||||
get() = FOREIGN_ANNOTATIONS_SOURCES_PATH
|
||||
|
||||
override fun loadLanguageVersionSettings(module: List<TestFile>): LanguageVersionSettings {
|
||||
val analysisFlags = loadAnalysisFlags(module)
|
||||
return CompilerTestLanguageVersionSettings(
|
||||
DEFAULT_DIAGNOSTIC_TESTS_FEATURES,
|
||||
ApiVersion.LATEST_STABLE,
|
||||
LanguageVersion.LATEST_STABLE,
|
||||
analysisFlags = analysisFlags
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadAnalysisFlags(module: List<TestFile>): Map<AnalysisFlag<*>, Any?> {
|
||||
val globalState = module.getDirectiveValue(JSR305_GLOBAL_DIRECTIVE) ?: ReportLevel.STRICT
|
||||
val migrationState = module.getDirectiveValue(JSR305_MIGRATION_DIRECTIVE)
|
||||
|
||||
val userAnnotationsState = module.flatMap {
|
||||
InTextDirectivesUtils.findListWithPrefixes(it.expectedText, JSR305_SPECIAL_DIRECTIVE)
|
||||
}.mapNotNull {
|
||||
val (name, stateDescription) = it.split(":").takeIf { it.size == 2 } ?: return@mapNotNull null
|
||||
val state = ReportLevel.findByDescription(stateDescription) ?: return@mapNotNull null
|
||||
|
||||
name to state
|
||||
}.toMap()
|
||||
|
||||
return mapOf(AnalysisFlag.jsr305 to Jsr305State(globalState, migrationState, userAnnotationsState))
|
||||
}
|
||||
|
||||
private fun List<TestFile>.getDirectiveValue(directive: String): ReportLevel? = mapNotNull {
|
||||
InTextDirectivesUtils.findLinesWithPrefixesRemoved(it.expectedText, directive).firstOrNull()
|
||||
}.firstOrNull().let { ReportLevel.findByDescription(it) }
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractForeignJava8AnnotationsTest : AbstractForeignAnnotationsTest() {
|
||||
override val annotationsPath: String
|
||||
get() = JAVA8_ANNOTATION_SOURCES_PATH
|
||||
}
|
||||
|
||||
abstract class AbstractForeignJava8AnnotationsNoAnnotationInClasspathTest : AbstractForeignAnnotationsNoAnnotationInClasspathTest() {
|
||||
override val annotationsPath: String
|
||||
get() = JAVA8_ANNOTATION_SOURCES_PATH
|
||||
|
||||
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
|
||||
if (skipForCompiledVersion(files)) return
|
||||
super.analyzeAndCheck(testDataFile, files)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractForeignJava8AnnotationsNoAnnotationInClasspathWithFastClassReadingTest : AbstractForeignAnnotationsNoAnnotationInClasspathWithFastClassReadingTest() {
|
||||
override val annotationsPath: String
|
||||
get() = JAVA8_ANNOTATION_SOURCES_PATH
|
||||
|
||||
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
|
||||
if (skipForCompiledVersion(files)) return
|
||||
super.analyzeAndCheck(testDataFile, files)
|
||||
}
|
||||
}
|
||||
|
||||
private fun skipForCompiledVersion(files: List<BaseDiagnosticsTest.TestFile>) =
|
||||
files.any { file -> InTextDirectivesUtils.isDirectiveDefined(file.expectedText, "// SKIP_COMPILED_JAVA") }
|
||||
|
||||
|
||||
private const val JAVA8_ANNOTATION_SOURCES_PATH = "third-party/jdk8-annotations"
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.google.common.collect.Lists;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.checkers.CheckerTestUtil.ActualDiagnostic;
|
||||
import org.jetbrains.kotlin.checkers.CheckerTestUtil.DiagnosedRange;
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
|
||||
import org.jetbrains.kotlin.psi.KtFile;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
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 java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class CheckerTestUtilTest extends KotlinTestWithEnvironment {
|
||||
@NotNull
|
||||
private static String getTestDataPath() {
|
||||
return KotlinTestUtils.getTestDataPathBase() + "/diagnostics/checkerTestUtil";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KotlinCoreEnvironment createEnvironment() {
|
||||
return createEnvironmentWithMockJdk(ConfigurationKind.ALL);
|
||||
}
|
||||
|
||||
protected void doTest(TheTest theTest) throws Exception {
|
||||
String text = KotlinTestUtils.doLoadFile(getTestDataPath(), "test.kt");
|
||||
theTest.test(TestCheckerUtil.createCheckAndReturnPsiFile("test.kt", text, getProject()), getEnvironment());
|
||||
}
|
||||
|
||||
public void testEquals() throws Exception {
|
||||
doTest(new TheTest() {
|
||||
@Override
|
||||
protected void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMissing() throws Exception {
|
||||
DiagnosticData typeMismatch1 = diagnostics.get(1);
|
||||
doTest(new TheTest(missing(typeMismatch1)) {
|
||||
@Override
|
||||
protected void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges) {
|
||||
diagnostics.remove(typeMismatch1.index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testUnexpected() throws Exception {
|
||||
DiagnosticData typeMismatch1 = diagnostics.get(1);
|
||||
doTest(new TheTest(unexpected(typeMismatch1)) {
|
||||
@Override
|
||||
protected void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges) {
|
||||
diagnosedRanges.remove(typeMismatch1.index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testBoth() throws Exception {
|
||||
DiagnosticData typeMismatch1 = diagnostics.get(1);
|
||||
DiagnosticData unresolvedReference = diagnostics.get(6);
|
||||
doTest(new TheTest(unexpected(typeMismatch1), missing(unresolvedReference)) {
|
||||
@Override
|
||||
protected void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges) {
|
||||
diagnosedRanges.remove(typeMismatch1.rangeIndex);
|
||||
diagnostics.remove(unresolvedReference.index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testMissingInTheMiddle() throws Exception {
|
||||
DiagnosticData noneApplicable = diagnostics.get(4);
|
||||
DiagnosticData typeMismatch3 = diagnostics.get(5);
|
||||
doTest(new TheTest(unexpected(noneApplicable), missing(typeMismatch3)) {
|
||||
@Override
|
||||
protected void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges) {
|
||||
diagnosedRanges.remove(noneApplicable.rangeIndex);
|
||||
diagnostics.remove(typeMismatch3.index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testWrongParameters() throws Exception {
|
||||
DiagnosticData unused = diagnostics.get(2);
|
||||
String unusedDiagnostic = asTextDiagnostic(unused, "i");
|
||||
DiagnosedRange range = asDiagnosticRange(unused, unusedDiagnostic);
|
||||
doTest(new TheTest(wrongParameters(unusedDiagnostic, "UNUSED_VARIABLE(a)", unused.startOffset, unused.endOffset)) {
|
||||
@Override
|
||||
protected void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges) {
|
||||
diagnosedRanges.set(unused.rangeIndex, range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testWrongParameterInMultiRange() throws Exception {
|
||||
DiagnosticData unresolvedReference = diagnostics.get(6);
|
||||
String unusedDiagnostic = asTextDiagnostic(unresolvedReference, "i");
|
||||
String toManyArguments = asTextDiagnostic(diagnostics.get(7));
|
||||
DiagnosedRange range = asDiagnosticRange(unresolvedReference, unusedDiagnostic, toManyArguments);
|
||||
doTest(new TheTest(wrongParameters(unusedDiagnostic, "UNRESOLVED_REFERENCE(xx)", unresolvedReference.startOffset, unresolvedReference.endOffset)) {
|
||||
@Override
|
||||
protected void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges) {
|
||||
diagnosedRanges.set(unresolvedReference.rangeIndex, range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testAbstractJetDiagnosticsTest() throws Exception {
|
||||
AbstractDiagnosticsTest test = new AbstractDiagnosticsTest() {
|
||||
{setUp();}
|
||||
};
|
||||
test.doTest(getTestDataPath() + File.separatorChar + "test_with_diagnostic.kt");
|
||||
}
|
||||
|
||||
private static abstract class TheTest {
|
||||
private final String[] expected;
|
||||
|
||||
protected TheTest(String... expectedMessages) {
|
||||
this.expected = expectedMessages;
|
||||
}
|
||||
|
||||
public void test(@NotNull PsiFile psiFile, @NotNull KotlinCoreEnvironment environment) {
|
||||
BindingContext bindingContext =
|
||||
JvmResolveUtil.analyze((KtFile) psiFile, environment).getBindingContext();
|
||||
|
||||
String expectedText = CheckerTestUtil.addDiagnosticMarkersToText(
|
||||
psiFile,
|
||||
CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors(bindingContext, psiFile, false, null, null, false)
|
||||
).toString();
|
||||
|
||||
List<DiagnosedRange> diagnosedRanges = Lists.newArrayList();
|
||||
CheckerTestUtil.parseDiagnosedRanges(expectedText, diagnosedRanges);
|
||||
|
||||
List<ActualDiagnostic> actualDiagnostics =
|
||||
CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors(bindingContext, psiFile, false, null, null, false);
|
||||
actualDiagnostics.sort(CheckerTestUtil.DIAGNOSTIC_COMPARATOR);
|
||||
|
||||
makeTestData(actualDiagnostics, diagnosedRanges);
|
||||
|
||||
List<String> expectedMessages = Lists.newArrayList(expected);
|
||||
List<String> actualMessages = Lists.newArrayList();
|
||||
|
||||
CheckerTestUtil.diagnosticsDiff(diagnosedRanges, actualDiagnostics, new CheckerTestUtil.DiagnosticDiffCallbacks() {
|
||||
@Override
|
||||
public void missingDiagnostic(CheckerTestUtil.TextDiagnostic diagnostic, int expectedStart, int expectedEnd) {
|
||||
actualMessages.add(missing(diagnostic.getDescription(), expectedStart, expectedEnd));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void wrongParametersDiagnostic(
|
||||
CheckerTestUtil.TextDiagnostic expectedDiagnostic,
|
||||
CheckerTestUtil.TextDiagnostic actualDiagnostic,
|
||||
int start,
|
||||
int end
|
||||
) {
|
||||
actualMessages.add(wrongParameters(expectedDiagnostic.asString(), actualDiagnostic.asString(), start, end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unexpectedDiagnostic(CheckerTestUtil.TextDiagnostic diagnostic, int actualStart, int actualEnd) {
|
||||
actualMessages.add(unexpected(diagnostic.getDescription(), actualStart, actualEnd));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncheckedDiagnostic(CheckerTestUtil.TextDiagnostic diagnostic, int expectedStart, int expectedEnd) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldUseDiagnosticsForNI() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWithNewInferenceDirective() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals(listToString(expectedMessages), listToString(actualMessages));
|
||||
}
|
||||
|
||||
private static String listToString(List<String> expectedMessages) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (String expectedMessage : expectedMessages) {
|
||||
stringBuilder.append(expectedMessage).append("\n");
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
protected abstract void makeTestData(List<ActualDiagnostic> diagnostics, List<DiagnosedRange> diagnosedRanges);
|
||||
}
|
||||
|
||||
private static String wrongParameters(String expected, String actual, int start, int end) {
|
||||
return "Wrong parameters " + expected + " != " + actual +" at " + start + " to " + end;
|
||||
}
|
||||
|
||||
private static String unexpected(String type, int actualStart, int actualEnd) {
|
||||
return "Unexpected " + type + " at " + actualStart + " to " + actualEnd;
|
||||
}
|
||||
|
||||
private static String missing(String type, int expectedStart, int expectedEnd) {
|
||||
return "Missing " + type + " at " + expectedStart + " to " + expectedEnd;
|
||||
}
|
||||
|
||||
private static String unexpected(DiagnosticData data) {
|
||||
return unexpected(data.name, data.startOffset, data.endOffset);
|
||||
}
|
||||
|
||||
private static String missing(DiagnosticData data) {
|
||||
return missing(data.name, data.startOffset, data.endOffset);
|
||||
}
|
||||
|
||||
private static String asTextDiagnostic(DiagnosticData diagnosticData, String... params) {
|
||||
return diagnosticData.name + "(" + StringUtil.join(params, "; ") + ")";
|
||||
}
|
||||
|
||||
private static DiagnosedRange asDiagnosticRange(DiagnosticData diagnosticData, String... textDiagnostics) {
|
||||
DiagnosedRange range = new DiagnosedRange(diagnosticData.startOffset);
|
||||
range.setEnd(diagnosticData.endOffset);
|
||||
for (String textDiagnostic : textDiagnostics)
|
||||
range.addDiagnostic(textDiagnostic);
|
||||
return range;
|
||||
}
|
||||
|
||||
private static class DiagnosticData {
|
||||
public int index;
|
||||
public int rangeIndex;
|
||||
public String name;
|
||||
public int startOffset;
|
||||
public int endOffset;
|
||||
|
||||
private DiagnosticData(int index, int rangeIndex, String name, int startOffset, int endOffset) {
|
||||
this.index = index;
|
||||
this.rangeIndex = rangeIndex;
|
||||
this.name = name;
|
||||
this.startOffset = startOffset;
|
||||
this.endOffset = endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<DiagnosticData> diagnostics = Lists.newArrayList(
|
||||
new DiagnosticData(0, 0, "UNUSED_PARAMETER", 8, 9),
|
||||
new DiagnosticData(1, 1, "CONSTANT_EXPECTED_TYPE_MISMATCH", 56, 57),
|
||||
new DiagnosticData(2, 2, "UNUSED_VARIABLE", 67, 68),
|
||||
new DiagnosticData(3, 3, "TYPE_MISMATCH", 98, 99),
|
||||
new DiagnosticData(4, 4, "NONE_APPLICABLE", 120, 121),
|
||||
new DiagnosticData(5, 5, "TYPE_MISMATCH", 159, 167),
|
||||
new DiagnosticData(6, 6, "UNRESOLVED_REFERENCE", 164, 166),
|
||||
new DiagnosticData(7, 6, "TOO_MANY_ARGUMENTS", 164, 166)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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.cli;
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import kotlin.Pair;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import kotlin.io.FilesKt;
|
||||
import kotlin.text.Charsets;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.checkers.AbstractForeignAnnotationsTestKt;
|
||||
import org.jetbrains.kotlin.cli.common.CLITool;
|
||||
import org.jetbrains.kotlin.cli.common.ExitCode;
|
||||
import org.jetbrains.kotlin.cli.js.K2JSCompiler;
|
||||
import org.jetbrains.kotlin.cli.js.dce.K2JSDce;
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
|
||||
import org.jetbrains.kotlin.config.KotlinCompilerVersion;
|
||||
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion;
|
||||
import org.jetbrains.kotlin.test.CompilerTestUtil;
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
|
||||
import org.jetbrains.kotlin.utils.JsMetadataVersion;
|
||||
import org.jetbrains.kotlin.utils.PathUtil;
|
||||
import org.jetbrains.kotlin.utils.StringsKt;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractCliTest extends TestCaseWithTmpdir {
|
||||
private static final String TESTDATA_DIR = "$TESTDATA_DIR$";
|
||||
|
||||
public static Pair<String, ExitCode> executeCompilerGrabOutput(@NotNull CLITool<?> compiler, @NotNull List<String> args) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
|
||||
int index = 0;
|
||||
do {
|
||||
int next = args.subList(index, args.size()).indexOf("---");
|
||||
if (next == -1) {
|
||||
next = args.size();
|
||||
}
|
||||
Pair<String, ExitCode> pair = CompilerTestUtil.executeCompiler(compiler, args.subList(index, next));
|
||||
output.append(pair.getFirst());
|
||||
if (pair.getSecond() != ExitCode.OK) {
|
||||
return new Pair<>(output.toString(), pair.getSecond());
|
||||
}
|
||||
index = next + 1;
|
||||
}
|
||||
while (index < args.size());
|
||||
|
||||
return new Pair<>(output.toString(), ExitCode.OK);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getNormalizedCompilerOutput(@NotNull String pureOutput, @NotNull ExitCode exitCode, @NotNull String testDataDir) {
|
||||
String testDataAbsoluteDir = new File(testDataDir).getAbsolutePath();
|
||||
String normalizedOutputWithoutExitCode = StringUtil.convertLineSeparators(pureOutput)
|
||||
.replace(testDataAbsoluteDir, TESTDATA_DIR)
|
||||
.replace(FileUtil.toSystemIndependentName(testDataAbsoluteDir), TESTDATA_DIR)
|
||||
.replace(PathUtil.getKotlinPathsForDistDirectory().getHomePath().getAbsolutePath(), "$PROJECT_DIR$")
|
||||
.replace("expected version is " + JvmMetadataVersion.INSTANCE, "expected version is $ABI_VERSION$")
|
||||
.replace("expected version is " + JsMetadataVersion.INSTANCE, "expected version is $ABI_VERSION$")
|
||||
.replace("\\", "/")
|
||||
.replace(KotlinCompilerVersion.VERSION, "$VERSION$");
|
||||
|
||||
return normalizedOutputWithoutExitCode + exitCode + "\n";
|
||||
}
|
||||
|
||||
private void doTest(@NotNull String fileName, @NotNull CLITool<?> compiler) {
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
Pair<String, ExitCode> outputAndExitCode = executeCompilerGrabOutput(compiler, readArgs(fileName, tmpdir.getPath()));
|
||||
String actual = getNormalizedCompilerOutput(
|
||||
outputAndExitCode.getFirst(), outputAndExitCode.getSecond(), new File(fileName).getParent()
|
||||
);
|
||||
|
||||
File outFile = new File(fileName.replaceFirst("\\.args$", ".out"));
|
||||
KotlinTestUtils.assertEqualsToFile(outFile, actual);
|
||||
|
||||
File additionalTestConfig = new File(fileName.replaceFirst("\\.args$", ".test"));
|
||||
if (additionalTestConfig.exists()) {
|
||||
doTestAdditionalChecks(additionalTestConfig, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
private void doTestAdditionalChecks(@NotNull File testConfigFile, @NotNull String argsFilePath) {
|
||||
List<String> diagnostics = new ArrayList<>(0);
|
||||
String content = FilesKt.readText(testConfigFile, Charsets.UTF_8);
|
||||
|
||||
List<String> existsList = InTextDirectivesUtils.findListWithPrefixes(content, "// EXISTS: ");
|
||||
for (String fileName : existsList) {
|
||||
File file = checkedPathToFile(fileName, argsFilePath);
|
||||
if (!file.exists()) {
|
||||
diagnostics.add("File does not exist, but should: " + fileName);
|
||||
}
|
||||
else if (!file.isFile()) {
|
||||
diagnostics.add("File is a directory, but should be a normal file: " + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> absentList = InTextDirectivesUtils.findListWithPrefixes(content, "// ABSENT: ");
|
||||
for (String fileName : absentList) {
|
||||
File file = checkedPathToFile(fileName, argsFilePath);
|
||||
if (file.exists() && file.isFile()) {
|
||||
diagnostics.add("File exists, but shouldn't: " + fileName);
|
||||
}
|
||||
}
|
||||
|
||||
List<String> containsTextList = InTextDirectivesUtils.findLinesWithPrefixesRemoved(content, "// CONTAINS: ");
|
||||
for (String containsSpec : containsTextList) {
|
||||
String[] parts = containsSpec.split(",", 2);
|
||||
String fileName = parts[0].trim();
|
||||
String contentToSearch = parts[1].trim();
|
||||
File file = checkedPathToFile(fileName, argsFilePath);
|
||||
if (!file.exists()) {
|
||||
diagnostics.add("File does not exist: " + fileName);
|
||||
}
|
||||
else if (file.isDirectory()) {
|
||||
diagnostics.add("File is a directory: " + fileName);
|
||||
}
|
||||
else {
|
||||
String text = FilesKt.readText(file, Charsets.UTF_8);
|
||||
if (!text.contains(contentToSearch)) {
|
||||
diagnostics.add("File " + fileName + " does not contain string: " + contentToSearch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!diagnostics.isEmpty()) {
|
||||
diagnostics.add(0, diagnostics.size() + " problem(s) found:");
|
||||
Assert.fail(StringsKt.join(diagnostics, "\n"));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private File checkedPathToFile(@NotNull String path, @NotNull String argsFilePath) {
|
||||
if (path.startsWith(TESTDATA_DIR + "/")) {
|
||||
return new File(new File(argsFilePath).getParent(), path.substring(TESTDATA_DIR.length() + 1));
|
||||
}
|
||||
else {
|
||||
return new File(tmpdir, path);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> readArgs(@NotNull String argsFilePath, @NotNull String tempDir) {
|
||||
List<String> lines = FilesKt.readLines(new File(argsFilePath), Charsets.UTF_8);
|
||||
|
||||
return CollectionsKt.mapNotNull(lines, arg -> {
|
||||
if (arg.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Do not replace ':' after '\' (used in compiler plugin tests)
|
||||
String argsWithColonsReplaced = arg
|
||||
.replace("\\:", "$COLON$")
|
||||
.replace(":", File.pathSeparator)
|
||||
.replace("$COLON$", ":");
|
||||
|
||||
return argsWithColonsReplaced
|
||||
.replace("$TEMP_DIR$", tempDir)
|
||||
.replace(TESTDATA_DIR, new File(argsFilePath).getParent())
|
||||
.replace(
|
||||
"$FOREIGN_ANNOTATIONS_DIR$",
|
||||
new File(AbstractForeignAnnotationsTestKt.getFOREIGN_ANNOTATIONS_SOURCES_PATH()).getPath()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
protected void doJvmTest(@NotNull String fileName) {
|
||||
doTest(fileName, new K2JVMCompiler());
|
||||
}
|
||||
|
||||
protected void doJsTest(@NotNull String fileName) {
|
||||
doTest(fileName, new K2JSCompiler());
|
||||
}
|
||||
|
||||
protected void doJsDceTest(@NotNull String fileName) {
|
||||
doTest(fileName, new K2JSDce());
|
||||
}
|
||||
|
||||
public static String removePerfOutput(String output) {
|
||||
String[] lines = StringUtil.splitByLinesKeepSeparators(output);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (String line : lines) {
|
||||
if (!line.contains("PERF:")) {
|
||||
result.append(line);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
+40
@@ -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.codegen
|
||||
|
||||
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractBlackBoxAgainstJavaCodegenTest : AbstractBlackBoxCodegenTest() {
|
||||
override fun doMultiFileTest(wholeFile: File, files: MutableList<TestFile>, javaFilesDir: File?) {
|
||||
javaClassesOutputDirectory = javaFilesDir!!.let { directory ->
|
||||
CodegenTestUtil.compileJava(CodegenTestUtil.findJavaSourcesInDirectory(directory), emptyList(), extractJavacOptions(files))
|
||||
}
|
||||
|
||||
super.doMultiFileTest(wholeFile, files, null)
|
||||
}
|
||||
|
||||
override fun updateConfiguration(configuration: CompilerConfiguration) {
|
||||
configuration.addJvmClasspathRoot(javaClassesOutputDirectory)
|
||||
}
|
||||
|
||||
override fun extractConfigurationKind(files: MutableList<TestFile>): ConfigurationKind {
|
||||
return ConfigurationKind.ALL
|
||||
}
|
||||
}
|
||||
+33
@@ -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 org.jetbrains.kotlin.codegen
|
||||
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractBlackBoxInlineCodegenTest : AbstractBlackBoxCodegenTest() {
|
||||
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
|
||||
super.doMultiFileTest(wholeFile, files, javaFilesDir)
|
||||
try {
|
||||
InlineTestUtil.checkNoCallsToInline(initializedClassLoader.allGeneratedFiles.filterClassFiles(), myFiles.psiFiles)
|
||||
SMAPTestUtil.checkSMAP(files, generateClassesInFile().getClassFiles())
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
println(generateToText())
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
|
||||
import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractKapt3BuilderModeBytecodeShapeTest : CodegenTestCase() {
|
||||
private companion object {
|
||||
var TEST_LIGHT_ANALYSIS: ClassBuilderFactory = object : ClassBuilderFactories.TestClassBuilderFactory(false) {
|
||||
override fun getClassBuilderMode() = ClassBuilderMode.KAPT3
|
||||
}
|
||||
}
|
||||
|
||||
override fun doMultiFileTest(wholeFile: File, files: MutableList<TestFile>, javaFilesDir: File?) {
|
||||
val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".txt")
|
||||
compile(files, javaFilesDir)
|
||||
KotlinTestUtils.assertEqualsToFile(txtFile, BytecodeListingTextCollectingVisitor.getText(classFileFactory))
|
||||
}
|
||||
|
||||
override fun setupEnvironment(environment: KotlinCoreEnvironment) {
|
||||
AnalysisHandlerExtension.registerExtension(environment.project, PartialAnalysisHandlerExtension())
|
||||
}
|
||||
|
||||
override fun getClassBuilderFactory(): ClassBuilderFactory {
|
||||
return TEST_LIGHT_ANALYSIS
|
||||
}
|
||||
|
||||
override fun verifyWithDex(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 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.CompilerTestUtil;
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind;
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||
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);
|
||||
List<String> sourceFiles = new ArrayList<>(2);
|
||||
|
||||
FileUtil.processFilesRecursively(root, file -> {
|
||||
if (file.getName().endsWith(".kt")) {
|
||||
sourceFiles.add(relativePath(file));
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}, file -> !LIBRARY.equals(file.getName()));
|
||||
|
||||
File library = new File(root, LIBRARY);
|
||||
List<File> classPath =
|
||||
library.exists()
|
||||
? Collections.singletonList(CompilerTestUtil.compileJvmLibrary(library))
|
||||
: Collections.emptyList();
|
||||
|
||||
assert !sourceFiles.isEmpty() : getTestName(true) + " should contain at least one .kt file";
|
||||
Collections.sort(sourceFiles);
|
||||
|
||||
myEnvironment = KotlinCoreEnvironment.createForTests(
|
||||
getTestRootDisposable(),
|
||||
KotlinTestUtils.newConfiguration(
|
||||
ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK,
|
||||
CollectionsKt.plus(classPath, KotlinTestUtils.getAnnotationsJar()), Collections.emptyList()
|
||||
),
|
||||
EnvironmentConfigFiles.JVM_CONFIG_FILES);
|
||||
|
||||
loadFiles(ArrayUtil.toStringArray(sourceFiles));
|
||||
|
||||
List<OccurrenceInfo> expected = readExpectedOccurrences(KotlinTestUtils.getTestDataPathBase() + "/codegen/" + sourceFiles.get(0));
|
||||
String actual = generateToText();
|
||||
Companion.checkGeneratedTextAgainstExpectedOccurrences(actual, expected);
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.ir
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.backend.common.AbstractClosureAnnotator
|
||||
import org.jetbrains.kotlin.backend.common.Closure
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
abstract class AbstractClosureAnnotatorTestCase : AbstractIrGeneratorTestCase() {
|
||||
override fun doTest(wholeFile: File, testFiles: List<TestFile>) {
|
||||
val dir = wholeFile.parentFile
|
||||
val ignoreErrors = shouldIgnoreErrors(wholeFile)
|
||||
for ((testFile, irFile) in generateIrFilesAsSingleModule(testFiles, ignoreErrors)) {
|
||||
doTestIrFileAgainstExpectations(dir, testFile, irFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTestIrFileAgainstExpectations(dir: File, testFile: TestFile, irFile: IrFile) {
|
||||
val expectedFile = File(dir, testFile.name.replace(".kt", ".closure"))
|
||||
val actualClosures = renderClosures(irFile)
|
||||
KotlinTestUtils.assertEqualsToFile(expectedFile, actualClosures)
|
||||
}
|
||||
|
||||
private fun renderClosures(irFile: IrFile): String {
|
||||
val actualStringWriter = StringWriter()
|
||||
val actualOut = PrintWriter(actualStringWriter)
|
||||
|
||||
irFile.acceptChildrenVoid(object : AbstractClosureAnnotator() {
|
||||
override fun recordClassClosure(classDescriptor: ClassDescriptor, closure: Closure) {
|
||||
actualOut.println("Closure for class ${classDescriptor.name}:")
|
||||
printClosure(closure)
|
||||
actualOut.println()
|
||||
}
|
||||
|
||||
override fun recordFunctionClosure(functionDescriptor: FunctionDescriptor, closure: Closure) {
|
||||
if (functionDescriptor is ConstructorDescriptor) {
|
||||
actualOut.println("Closure for constructor ${functionDescriptor.containingDeclaration.name}:")
|
||||
}
|
||||
else {
|
||||
actualOut.println("Closure for function ${functionDescriptor.name}:")
|
||||
}
|
||||
printClosure(closure)
|
||||
actualOut.println()
|
||||
}
|
||||
|
||||
private fun printClosure(closure: Closure) {
|
||||
closure.capturedValues.forEach {
|
||||
actualOut.println(" variable ${it.name}")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return actualStringWriter.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.ir
|
||||
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.name
|
||||
import org.jetbrains.kotlin.ir2cfg.generators.FunctionGenerator
|
||||
import org.jetbrains.kotlin.ir2cfg.util.dump
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractIrCfgTestCase : AbstractIrGeneratorTestCase() {
|
||||
|
||||
private val IrFunction.name: String get() = this.descriptor.name.asString()
|
||||
|
||||
private fun IrFile.cfgDump(): String {
|
||||
val builder = StringBuilder()
|
||||
for (declaration in this.declarations) {
|
||||
if (declaration is IrFunction) {
|
||||
builder.appendln("// FUN: ${declaration.name}")
|
||||
val cfg = FunctionGenerator(declaration).generate()
|
||||
builder.appendln(cfg.dump())
|
||||
builder.appendln("// END FUN: ${declaration.name}")
|
||||
}
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
private fun IrModuleFragment.cfgDump(): String {
|
||||
val builder = StringBuilder()
|
||||
for (file in this.files) {
|
||||
builder.appendln("// FILE: ${file.name}")
|
||||
builder.appendln(file.cfgDump())
|
||||
builder.appendln("// END FILE: ${file.name}")
|
||||
builder.appendln()
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
override fun doTest(wholeFile: File, testFiles: List<TestFile>) {
|
||||
val irModule = generateIrModule(false)
|
||||
val irModuleDump = irModule.cfgDump()
|
||||
val expectedPath = wholeFile.canonicalPath.replace(".kt", ".txt")
|
||||
val expectedFile = File(expectedPath)
|
||||
KotlinTestUtils.assertEqualsToFile(expectedFile, irModuleDump)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.ir
|
||||
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.codegen.CodegenTestCase
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi2ir.Psi2IrConfiguration
|
||||
import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils.getAnnotationsJar
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractIrGeneratorTestCase : CodegenTestCase() {
|
||||
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
|
||||
setupEnvironment(files, javaFilesDir)
|
||||
|
||||
loadMultiFiles(files)
|
||||
doTest(wholeFile, files)
|
||||
}
|
||||
|
||||
private fun setupEnvironment(files: List<TestFile>, javaFilesDir: File?) {
|
||||
val jdkKind = getJdkKind(files)
|
||||
|
||||
val javacOptions = ArrayList<String>(0)
|
||||
var addRuntime = false
|
||||
var addReflect = false
|
||||
for (file in 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:"))
|
||||
}
|
||||
|
||||
val configurationKind = when {
|
||||
addReflect -> ConfigurationKind.ALL
|
||||
addRuntime -> ConfigurationKind.NO_KOTLIN_REFLECT
|
||||
else -> ConfigurationKind.JDK_ONLY
|
||||
}
|
||||
|
||||
val configuration = createConfiguration(
|
||||
configurationKind, jdkKind,
|
||||
listOf<File>(getAnnotationsJar()),
|
||||
arrayOf(javaFilesDir).filterNotNull(),
|
||||
files
|
||||
)
|
||||
|
||||
myEnvironment = KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||
}
|
||||
|
||||
protected abstract fun doTest(wholeFile: File, testFiles: List<TestFile>)
|
||||
|
||||
protected fun generateIrModule(ignoreErrors: Boolean = false, shouldGenerate: (KtFile) -> Boolean = { true }): IrModuleFragment {
|
||||
assert(myFiles != null) { "myFiles not initialized" }
|
||||
assert(myEnvironment != null) { "myEnvironment not initialized" }
|
||||
return generateIrModule(myFiles.psiFiles, myEnvironment, Psi2IrTranslator(Psi2IrConfiguration(ignoreErrors)), shouldGenerate)
|
||||
}
|
||||
|
||||
protected fun generateIrFilesAsSingleModule(testFiles: List<TestFile>, ignoreErrors: Boolean = false): Map<TestFile, IrFile> {
|
||||
val irModule = generateIrModule(ignoreErrors)
|
||||
val ktFiles = testFiles.filter { it.name.endsWith(".kt") }
|
||||
return ktFiles.zip(irModule.files).toMap()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IGNORE_ERRORS_PATTERN = Regex("""// !IGNORE_ERRORS""")
|
||||
|
||||
internal fun shouldIgnoreErrors(wholeFile: File): Boolean =
|
||||
IGNORE_ERRORS_PATTERN.containsMatchIn(wholeFile.readText())
|
||||
|
||||
internal fun createExpectedTextFile(testFile: TestFile, dir: File, fileName: String): File {
|
||||
val textFile = File(dir, fileName)
|
||||
if (!textFile.exists()) {
|
||||
TestCase.assertTrue("Can't create an expected text containingFile: ${textFile.absolutePath}", textFile.createNewFile())
|
||||
PrintWriter(FileWriter(textFile)).use {
|
||||
it.println("$fileName: new expected text containingFile for ${testFile.name}")
|
||||
}
|
||||
}
|
||||
return textFile
|
||||
}
|
||||
|
||||
fun generateIrModule(
|
||||
ktFilesToAnalyze: List<KtFile>,
|
||||
environment: KotlinCoreEnvironment,
|
||||
psi2ir: Psi2IrTranslator,
|
||||
shouldGenerate: (KtFile) -> Boolean
|
||||
): IrModuleFragment {
|
||||
val analysisResult = JvmResolveUtil.analyze(ktFilesToAnalyze, environment)
|
||||
if (!psi2ir.configuration.ignoreErrors) {
|
||||
analysisResult.throwIfError()
|
||||
AnalyzingUtils.throwExceptionOnErrors(analysisResult.bindingContext)
|
||||
}
|
||||
val fileToGenerate = ktFilesToAnalyze.filter { shouldGenerate(it) }
|
||||
return generateIrModule(fileToGenerate, analysisResult.moduleDescriptor, analysisResult.bindingContext, psi2ir)
|
||||
}
|
||||
|
||||
fun generateIrModule(ktFiles: List<KtFile>, moduleDescriptor: ModuleDescriptor, bindingContext: BindingContext, ignoreErrors: Boolean = false) =
|
||||
generateIrModule(ktFiles, moduleDescriptor, bindingContext, Psi2IrTranslator(Psi2IrConfiguration(ignoreErrors)))
|
||||
|
||||
fun generateIrModule(ktFiles: List<KtFile>, moduleDescriptor: ModuleDescriptor, bindingContext: BindingContext, psi2ir: Psi2IrTranslator) =
|
||||
psi2ir.generateModule(moduleDescriptor, ktFiles, bindingContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.ir
|
||||
|
||||
import org.jetbrains.kotlin.ir.util.RenderIrElementVisitor
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractIrSourceRangesTestCase : AbstractIrGeneratorTestCase() {
|
||||
override fun doTest(wholeFile: File, testFiles: List<TestFile>) {
|
||||
val dir = wholeFile.parentFile
|
||||
val testFileToIrFile = generateIrFilesAsSingleModule(testFiles)
|
||||
for ((testFile, irFile) in testFileToIrFile) {
|
||||
val irFileDump = irFile.dumpWithSourceLocations(irFile.fileEntry)
|
||||
val expectedSourceLocations = File(dir, testFile.name.replace(".kt", ".txt"))
|
||||
KotlinTestUtils.assertEqualsToFile(expectedSourceLocations, irFileDump)
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrElement.dumpWithSourceLocations(fileEntry: SourceManager.FileEntry): String =
|
||||
StringBuilder().also {
|
||||
acceptVoid(DumpSourceLocations(it, fileEntry))
|
||||
}.toString()
|
||||
|
||||
private class DumpSourceLocations(
|
||||
out: Appendable,
|
||||
val fileEntry: SourceManager.FileEntry
|
||||
) : IrElementVisitorVoid {
|
||||
val printer = Printer(out, " ")
|
||||
val elementRenderer = RenderIrElementVisitor()
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
val sourceRangeInfo = fileEntry.getSourceRangeInfo(element.startOffset, element.endOffset)
|
||||
printer.println("@${sourceRangeInfo.render()} ${element.accept(elementRenderer, null)}")
|
||||
printer.pushIndent()
|
||||
element.acceptChildrenVoid(this)
|
||||
printer.popIndent()
|
||||
}
|
||||
|
||||
private fun SourceRangeInfo.render() =
|
||||
if (startLineNumber == endLineNumber)
|
||||
"$startLineNumber:$startColumnNumber..$endColumnNumber"
|
||||
else
|
||||
"$startLineNumber:$startColumnNumber..$endLineNumber:$endColumnNumber"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.ir
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrDeclarationReference
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
|
||||
import org.jetbrains.kotlin.ir.expressions.IrLocalDelegatedPropertyReference
|
||||
import org.jetbrains.kotlin.ir.expressions.IrPropertyReference
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.utils.rethrow
|
||||
import java.io.File
|
||||
import java.util.regex.Pattern
|
||||
|
||||
abstract class AbstractIrTextTestCase : AbstractIrGeneratorTestCase() {
|
||||
override fun doTest(wholeFile: File, testFiles: List<TestFile>) {
|
||||
val dir = wholeFile.parentFile
|
||||
val ignoreErrors = shouldIgnoreErrors(wholeFile)
|
||||
val irModule = generateIrModule(ignoreErrors)
|
||||
|
||||
val ktFiles = testFiles.filter { it.name.endsWith(".kt") }
|
||||
for ((testFile, irFile) in ktFiles.zip(irModule.files)) {
|
||||
doTestIrFileAgainstExpectations(dir, testFile, irFile)
|
||||
}
|
||||
|
||||
if (shouldDumpDependencies(wholeFile)) {
|
||||
doTestIrModuleDependencies(wholeFile, irModule)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doTestIrModuleDependencies(wholeFile: File, irModule: IrModuleFragment) {
|
||||
irModule.dependencyModules.forEach { irDependencyModule ->
|
||||
val actual = irDependencyModule.dump()
|
||||
val sanitizedModuleName = StringUtil.sanitizeJavaIdentifier(irDependencyModule.descriptor.name.asString())
|
||||
val expectedFileName = wholeFile.absolutePath.replace(".kt", "__$sanitizedModuleName.txt")
|
||||
KotlinTestUtils.assertEqualsToFile(File(expectedFileName), actual)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doTestIrFileAgainstExpectations(dir: File, testFile: TestFile, irFile: IrFile) {
|
||||
if (testFile.isExternalFile()) return
|
||||
|
||||
val expectations = parseExpectations(dir, testFile)
|
||||
val irFileDump = irFile.dump()
|
||||
|
||||
val expected = StringBuilder()
|
||||
val actual = StringBuilder()
|
||||
for (expectation in expectations.regexps) {
|
||||
expected.append(expectation.numberOfOccurrences).append(" ").append(expectation.needle).append("\n")
|
||||
val actualCount = StringUtil.findMatches(irFileDump, Pattern.compile("(" + expectation.needle + ")")).size
|
||||
actual.append(actualCount).append(" ").append(expectation.needle).append("\n")
|
||||
}
|
||||
|
||||
for (irTreeFileLabel in expectations.irTreeFileLabels) {
|
||||
val actualTrees = irFile.dumpTreesFromLineNumber(irTreeFileLabel.lineNumber)
|
||||
KotlinTestUtils.assertEqualsToFile(irTreeFileLabel.expectedTextFile, actualTrees)
|
||||
verify(irFile)
|
||||
|
||||
// Check that deep copy produces an equivalent result
|
||||
val irFileCopy = irFile.deepCopyWithSymbols()
|
||||
val copiedTrees = irFileCopy.dumpTreesFromLineNumber(irTreeFileLabel.lineNumber)
|
||||
TestCase.assertEquals("IR dump mismatch after deep copy with symbols", actualTrees, copiedTrees)
|
||||
verify(irFileCopy)
|
||||
|
||||
val irFileCopyOld = irFile.deepCopyOld()
|
||||
val copiedTreesOld = irFileCopyOld.dumpTreesFromLineNumber(irTreeFileLabel.lineNumber)
|
||||
TestCase.assertEquals("IR dump mismatch after old deep copy", actualTrees, copiedTreesOld)
|
||||
}
|
||||
|
||||
try {
|
||||
TestCase.assertEquals(irFileDump, expected.toString(), actual.toString())
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
println(irFileDump)
|
||||
throw rethrow(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun verify(irFile: IrFile) {
|
||||
IrVerifier().verifyWithAssert(irFile)
|
||||
}
|
||||
|
||||
private class IrVerifier : IrElementVisitorVoid {
|
||||
private val errors = ArrayList<String>()
|
||||
|
||||
private val symbolForDeclaration = HashMap<IrElement, IrSymbol>()
|
||||
|
||||
val hasErrors get() = errors.isNotEmpty()
|
||||
|
||||
val errorsAsMessage get() = errors.joinToString(prefix = "IR verifier errors:\n", separator = "\n")
|
||||
|
||||
private fun error(message: String) {
|
||||
errors.add(message)
|
||||
}
|
||||
|
||||
fun verifyWithAssert(irFile: IrFile) {
|
||||
irFile.acceptChildrenVoid(this)
|
||||
TestCase.assertFalse(errorsAsMessage + "\n\n\n" + irFile.dump(), hasErrors)
|
||||
}
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
|
||||
override fun visitDeclaration(declaration: IrDeclaration) {
|
||||
if (declaration is IrSymbolOwner) {
|
||||
declaration.symbol.checkBinding("decl", declaration)
|
||||
|
||||
if (declaration.symbol.owner != declaration) {
|
||||
error("Symbol is not bound to declaration: ${declaration.render()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitFunction(declaration: IrFunction) {
|
||||
visitDeclaration(declaration)
|
||||
|
||||
val functionDescriptor = declaration.descriptor
|
||||
|
||||
checkTypeParameters(functionDescriptor, declaration, functionDescriptor.typeParameters)
|
||||
|
||||
val expectedDispatchReceiver = functionDescriptor.dispatchReceiverParameter
|
||||
val actualDispatchReceiver = declaration.dispatchReceiverParameter?.descriptor
|
||||
if (expectedDispatchReceiver != actualDispatchReceiver) {
|
||||
error("$functionDescriptor: Dispatch receiver parameter mismatch: " +
|
||||
"expected $expectedDispatchReceiver, actual $actualDispatchReceiver")
|
||||
}
|
||||
|
||||
val expectedExtensionReceiver = functionDescriptor.extensionReceiverParameter
|
||||
val actualExtensionReceiver = declaration.extensionReceiverParameter?.descriptor
|
||||
if (expectedExtensionReceiver != actualExtensionReceiver) {
|
||||
error("$functionDescriptor: Extension receiver parameter mismatch: " +
|
||||
"expected $expectedExtensionReceiver, actual $actualExtensionReceiver")
|
||||
}
|
||||
|
||||
val declaredValueParameters = declaration.valueParameters.map { it.descriptor }
|
||||
val actualValueParameters = functionDescriptor.valueParameters
|
||||
if (declaredValueParameters.size != actualValueParameters.size) {
|
||||
error("$functionDescriptor: Value parameters mismatch: $declaredValueParameters != $actualValueParameters")
|
||||
}
|
||||
else {
|
||||
declaredValueParameters.zip(actualValueParameters).forEach { (declaredValueParameter, actualValueParameter) ->
|
||||
if (declaredValueParameter != actualValueParameter) {
|
||||
error("$functionDescriptor: Value parameters mismatch: $declaredValueParameter != $actualValueParameter")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitDeclarationReference(expression: IrDeclarationReference) {
|
||||
expression.symbol.checkBinding("ref", expression)
|
||||
}
|
||||
|
||||
override fun visitFunctionReference(expression: IrFunctionReference) {
|
||||
expression.symbol.checkBinding("ref", expression)
|
||||
}
|
||||
|
||||
override fun visitPropertyReference(expression: IrPropertyReference) {
|
||||
expression.field?.checkBinding("field", expression)
|
||||
expression.getter?.checkBinding("getter", expression)
|
||||
expression.setter?.checkBinding("setter", expression)
|
||||
}
|
||||
|
||||
override fun visitLocalDelegatedPropertyReference(expression: IrLocalDelegatedPropertyReference) {
|
||||
expression.delegate.checkBinding("delegate", expression)
|
||||
expression.getter.checkBinding("getter", expression)
|
||||
expression.setter?.checkBinding("setter", expression)
|
||||
}
|
||||
|
||||
private fun IrSymbol.checkBinding(kind: String, irElement: IrElement) {
|
||||
if (!isBound) {
|
||||
error("${javaClass.simpleName} $descriptor is unbound @$kind ${irElement.render()}")
|
||||
}
|
||||
|
||||
val otherSymbol = symbolForDeclaration.getOrPut(owner) { this }
|
||||
if (this != otherSymbol) {
|
||||
error("Multiple symbols for $descriptor @$kind ${irElement.render()}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitClass(declaration: IrClass) {
|
||||
visitDeclaration(declaration)
|
||||
|
||||
checkTypeParameters(declaration.descriptor, declaration, declaration.descriptor.declaredTypeParameters)
|
||||
}
|
||||
|
||||
private fun checkTypeParameters(descriptor: DeclarationDescriptor, declaration: IrTypeParametersContainer, expectedTypeParameters: List<TypeParameterDescriptor>) {
|
||||
val declaredTypeParameters = declaration.typeParameters.map { it.descriptor }
|
||||
|
||||
if (declaredTypeParameters.size != expectedTypeParameters.size) {
|
||||
error("$descriptor: Type parameters mismatch: $declaredTypeParameters != $expectedTypeParameters")
|
||||
}
|
||||
else {
|
||||
declaredTypeParameters.zip(expectedTypeParameters).forEach { (declaredTypeParameter, expectedTypeParameter) ->
|
||||
if (declaredTypeParameter != expectedTypeParameter) {
|
||||
error("$descriptor: Type parameters mismatch: $declaredTypeParameter != $expectedTypeParameter")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class Expectations(val regexps: List<RegexpInText>, val irTreeFileLabels: List<IrTreeFileLabel>)
|
||||
|
||||
internal class RegexpInText(val numberOfOccurrences: Int, val needle: String) {
|
||||
constructor(countStr: String, needle: String) : this(Integer.valueOf(countStr), needle)
|
||||
}
|
||||
|
||||
internal class IrTreeFileLabel(val expectedTextFile: File, val lineNumber: Int)
|
||||
|
||||
companion object {
|
||||
private val EXPECTED_OCCURRENCES_PATTERN = Regex("""^\s*//\s*(\d+)\s*(.*)$""")
|
||||
private val IR_FILE_TXT_PATTERN = Regex("""// IR_FILE: (.*)$""")
|
||||
|
||||
private val DUMP_DEPENDENCIES_PATTERN = Regex("""// !DUMP_DEPENDENCIES""")
|
||||
|
||||
private val EXTERNAL_FILE_PATTERN = Regex("""// EXTERNAL_FILE""")
|
||||
|
||||
internal fun shouldDumpDependencies(wholeFile: File): Boolean =
|
||||
DUMP_DEPENDENCIES_PATTERN.containsMatchIn(wholeFile.readText())
|
||||
|
||||
internal fun TestFile.isExternalFile() =
|
||||
EXTERNAL_FILE_PATTERN.containsMatchIn(content)
|
||||
|
||||
internal fun KtFile.isExternalFile() =
|
||||
EXTERNAL_FILE_PATTERN.containsMatchIn(text)
|
||||
|
||||
internal fun parseExpectations(dir: File, testFile: TestFile): Expectations {
|
||||
val regexps = ArrayList<RegexpInText>()
|
||||
val treeFiles = ArrayList<IrTreeFileLabel>()
|
||||
|
||||
for (line in testFile.content.split("\n")) {
|
||||
EXPECTED_OCCURRENCES_PATTERN.matchEntire(line)?.let { matchResult ->
|
||||
regexps.add(RegexpInText(matchResult.groupValues[1], matchResult.groupValues[2].trim()))
|
||||
}
|
||||
?: IR_FILE_TXT_PATTERN.find(line)?.let { matchResult ->
|
||||
val fileName = matchResult.groupValues[1].trim()
|
||||
val file = createExpectedTextFile(testFile, dir, fileName)
|
||||
treeFiles.add(IrTreeFileLabel(file, 0))
|
||||
}
|
||||
}
|
||||
|
||||
if (treeFiles.isEmpty()) {
|
||||
val file = createExpectedTextFile(testFile, dir, testFile.name.replace(".kt", ".txt"))
|
||||
treeFiles.add(IrTreeFileLabel(file, 0))
|
||||
}
|
||||
|
||||
return Expectations(regexps, treeFiles)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.ir
|
||||
|
||||
import org.jetbrains.kotlin.ir.util.dump
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractPsi2IrBoxTestCase : AbstractIrGeneratorTestCase() {
|
||||
override fun doTest(wholeFile: File, testFiles: List<TestFile>) {
|
||||
val irModule = generateIrModule(false)
|
||||
val irModuleDump = irModule.dump()
|
||||
val expectedPath = wholeFile.canonicalPath
|
||||
// .replace("/codegen/box/", "/ir/irTextBox/")
|
||||
.replace(".kt", ".txt")
|
||||
val expectedFile = File(expectedPath)
|
||||
KotlinTestUtils.assertEqualsToFile(expectedFile, irModuleDump)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.ir
|
||||
|
||||
import org.jetbrains.kotlin.checkers.AbstractDiagnosticsTest
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
||||
import org.jetbrains.kotlin.ir.util.dump
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractPsi2IrDiagnosticsTest : AbstractDiagnosticsTest() {
|
||||
override fun performAdditionalChecksAfterDiagnostics(
|
||||
testDataFile: File,
|
||||
testFiles: List<TestFile>,
|
||||
moduleFiles: Map<TestModule?, List<TestFile>>,
|
||||
moduleDescriptors: Map<TestModule?, ModuleDescriptorImpl>,
|
||||
moduleBindings: Map<TestModule?, BindingContext>
|
||||
) {
|
||||
val actualIrDump = StringBuilder()
|
||||
for (module in moduleFiles.keys) {
|
||||
val ktFiles = moduleFiles[module]?.mapNotNull { it.ktFile } ?: continue
|
||||
val moduleDescriptor = moduleDescriptors[module] ?: continue
|
||||
val moduleBindingContext = moduleBindings[module] ?: continue
|
||||
val irModule = AbstractIrGeneratorTestCase.generateIrModule(ktFiles, moduleDescriptor, moduleBindingContext, true)
|
||||
actualIrDump.append(irModule.dump())
|
||||
}
|
||||
|
||||
val expectedIrFile = File(testDataFile.canonicalPath.replace(".kt", ".ir.txt"))
|
||||
KotlinTestUtils.assertEqualsToFile(expectedIrFile, actualIrDump.toString())
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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 org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.javac.JavacWrapper
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.renderer.*
|
||||
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
|
||||
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.junit.Assert
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.annotation.Retention
|
||||
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils.*
|
||||
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile
|
||||
|
||||
abstract class AbstractCompileJavaAgainstKotlinTest : TestCaseWithTmpdir() {
|
||||
|
||||
@Throws(IOException::class)
|
||||
protected fun doTestWithJavac(ktFilePath: String) {
|
||||
doTest(ktFilePath, true)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
protected fun doTestWithoutJavac(ktFilePath: String) {
|
||||
doTest(ktFilePath, false)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
protected fun doTest(ktFilePath: String, useJavac: Boolean) {
|
||||
Assert.assertTrue(ktFilePath.endsWith(".kt"))
|
||||
val ktFile = File(ktFilePath)
|
||||
val javaFile = File(ktFilePath.replaceFirst("\\.kt$".toRegex(), ".java"))
|
||||
|
||||
val javaErrorFile = File(ktFilePath.replaceFirst("\\.kt$".toRegex(), ".javaerr.txt"))
|
||||
|
||||
val out = File(tmpdir, "out")
|
||||
|
||||
val compiledSuccessfully = if (useJavac) {
|
||||
compileKotlinWithJava(listOf(javaFile),
|
||||
listOf(ktFile),
|
||||
out, testRootDisposable)
|
||||
} else {
|
||||
KotlinTestUtils.compileKotlinWithJava(listOf(javaFile),
|
||||
listOf(ktFile),
|
||||
out, testRootDisposable, javaErrorFile)
|
||||
}
|
||||
|
||||
if (!compiledSuccessfully) return
|
||||
|
||||
val environment = KotlinCoreEnvironment.createForTests(
|
||||
testRootDisposable,
|
||||
newConfiguration(ConfigurationKind.ALL, TestJdkKind.FULL_JDK, getAnnotationsJar(), out),
|
||||
EnvironmentConfigFiles.JVM_CONFIG_FILES
|
||||
)
|
||||
|
||||
val analysisResult = JvmResolveUtil.analyze(environment)
|
||||
val packageView = analysisResult.moduleDescriptor.getPackage(LoadDescriptorUtil.TEST_PACKAGE_FQNAME)
|
||||
assertFalse("Nothing found in package ${LoadDescriptorUtil.TEST_PACKAGE_FQNAME}", packageView.isEmpty())
|
||||
|
||||
val expectedFile = File(ktFilePath.replaceFirst("\\.kt$".toRegex(), ".txt"))
|
||||
validateAndCompareDescriptorWithFile(packageView, CONFIGURATION, expectedFile)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun compileKotlinWithJava(
|
||||
javaFiles: List<File>,
|
||||
ktFiles: List<File>,
|
||||
outDir: File,
|
||||
disposable: Disposable
|
||||
): Boolean {
|
||||
val environment = createEnvironmentWithMockJdkAndIdeaAnnotations(disposable)
|
||||
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
|
||||
environment.configuration.put(JVMConfigurationKeys.COMPILE_JAVA, true)
|
||||
environment.configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, outDir)
|
||||
environment.configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
|
||||
environment.registerJavac(javaFiles = javaFiles,
|
||||
kotlinFiles = listOf(KotlinTestUtils.loadJetFile(environment.project, ktFiles.first())))
|
||||
if (!ktFiles.isEmpty()) {
|
||||
LoadDescriptorUtil.compileKotlinToDirAndGetModule(ktFiles, outDir, environment)
|
||||
}
|
||||
else {
|
||||
val mkdirs = outDir.mkdirs()
|
||||
assert(mkdirs) { "Not created: $outDir" }
|
||||
}
|
||||
|
||||
return JavacWrapper.getInstance(environment.project).use { it.compile() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Do not render parameter names because there are test cases where classes inherit from JDK collections,
|
||||
// and some versions of JDK have debug information in the class files (including parameter names), and some don't
|
||||
private val CONFIGURATION = AbstractLoadJavaTest.COMPARATOR_CONFIGURATION.withRenderer(
|
||||
DescriptorRenderer.withOptions {
|
||||
withDefinedIn = false
|
||||
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
|
||||
verbose = true
|
||||
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
|
||||
excludedAnnotationClasses = setOf(FqName(Retention::class.java.name))
|
||||
modifiers = DescriptorRendererModifier.ALL
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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 org.jetbrains.kotlin.javac.JavacWrapper
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.renderer.*
|
||||
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
|
||||
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.junit.Assert
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.annotation.Retention
|
||||
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils.*
|
||||
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile
|
||||
|
||||
abstract class AbstractCompileKotlinAgainstJavaTest : TestCaseWithTmpdir() {
|
||||
|
||||
@Throws(IOException::class)
|
||||
protected fun doTest(ktFilePath: String) {
|
||||
Assert.assertTrue(ktFilePath.endsWith(".kt"))
|
||||
val ktFile = File(ktFilePath)
|
||||
val javaFile = File(ktFilePath.replaceFirst("\\.kt$".toRegex(), ".java"))
|
||||
val out = File(tmpdir, "out")
|
||||
|
||||
val compiledSuccessfully = compileKotlinWithJava(listOf(javaFile),
|
||||
listOf(ktFile),
|
||||
out, testRootDisposable)
|
||||
if (!compiledSuccessfully) return
|
||||
|
||||
val environment = KotlinCoreEnvironment.createForTests(
|
||||
testRootDisposable,
|
||||
newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, getAnnotationsJar(), out),
|
||||
EnvironmentConfigFiles.JVM_CONFIG_FILES
|
||||
)
|
||||
|
||||
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
|
||||
environment.configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, out)
|
||||
environment.registerJavac(emptyList<File>())
|
||||
|
||||
val analysisResult = JvmResolveUtil.analyze(environment)
|
||||
val packageView = analysisResult.moduleDescriptor.getPackage(LoadDescriptorUtil.TEST_PACKAGE_FQNAME)
|
||||
assertFalse("Nothing found in package ${LoadDescriptorUtil.TEST_PACKAGE_FQNAME}", packageView.isEmpty())
|
||||
|
||||
val expectedFile = File(ktFilePath.replaceFirst("\\.kt$".toRegex(), ".txt"))
|
||||
validateAndCompareDescriptorWithFile(packageView, CONFIGURATION, expectedFile)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun compileKotlinWithJava(
|
||||
javaFiles: List<File>,
|
||||
ktFiles: List<File>,
|
||||
outDir: File,
|
||||
disposable: Disposable
|
||||
): Boolean {
|
||||
val environment = createEnvironmentWithMockJdkAndIdeaAnnotations(disposable)
|
||||
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
|
||||
environment.configuration.put(JVMConfigurationKeys.COMPILE_JAVA, true)
|
||||
environment.configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, outDir)
|
||||
environment.registerJavac(javaFiles = javaFiles,
|
||||
kotlinFiles = listOf(KotlinTestUtils.loadJetFile(environment.project, ktFiles.first())),
|
||||
arguments = arrayOf("-proc:none"))
|
||||
if (!ktFiles.isEmpty()) {
|
||||
LoadDescriptorUtil.compileKotlinToDirAndGetModule(ktFiles, outDir, environment)
|
||||
}
|
||||
else {
|
||||
val mkdirs = outDir.mkdirs()
|
||||
assert(mkdirs) { "Not created: $outDir" }
|
||||
}
|
||||
|
||||
return JavacWrapper.getInstance(environment.project).use { it.compile() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Do not render parameter names because there are test cases where classes inherit from JDK collections,
|
||||
// and some versions of JDK have debug information in the class files (including parameter names), and some don't
|
||||
private val CONFIGURATION = AbstractLoadJavaTest.COMPARATOR_CONFIGURATION.withRenderer(
|
||||
DescriptorRenderer.withOptions {
|
||||
withDefinedIn = false
|
||||
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
|
||||
verbose = true
|
||||
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
|
||||
excludedAnnotationClasses = setOf(FqName(Retention::class.java.name))
|
||||
modifiers = DescriptorRendererModifier.ALL
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.io.FileUtil
|
||||
import com.intellij.util.io.ZipUtil
|
||||
import org.jetbrains.kotlin.cli.AbstractCliTest
|
||||
import org.jetbrains.kotlin.cli.common.CLICompiler
|
||||
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.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.TestCaseWithTmpdir
|
||||
import java.io.File
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Manifest
|
||||
import java.util.regex.Pattern
|
||||
|
||||
abstract class AbstractKotlinCompilerIntegrationTest : TestCaseWithTmpdir() {
|
||||
protected abstract val testDataPath: String
|
||||
|
||||
protected val testDataDirectory: File
|
||||
get() = File(testDataPath, getTestName(true))
|
||||
|
||||
protected fun getTestDataFileWithExtension(extension: String): File {
|
||||
return File(testDataDirectory, "${getTestName(true)}.$extension")
|
||||
}
|
||||
|
||||
class JavaCompilationError : AssertionError("Java files are not compiled successfully")
|
||||
|
||||
/**
|
||||
* Compiles all sources (.java and .kt) under the directory named [libraryName] to [destination].
|
||||
* [destination] should be either a path to the directory under [tmpdir], or a path to the resulting .jar file (also under [tmpdir]).
|
||||
* Kotlin sources are compiled first, and there should be no errors or warnings. Java sources are compiled next.
|
||||
*
|
||||
* @return [destination]
|
||||
*/
|
||||
protected fun compileLibrary(
|
||||
libraryName: String,
|
||||
destination: File = File(tmpdir, "$libraryName.jar"),
|
||||
additionalOptions: List<String> = emptyList(),
|
||||
compileJava: (sourceDir: File, javaFiles: List<File>, outputDir: File) -> Boolean = { _, javaFiles, outputDir ->
|
||||
KotlinTestUtils.compileJavaFiles(javaFiles, listOf("-d", outputDir.path))
|
||||
},
|
||||
checkKotlinOutput: (String) -> Unit = { actual -> assertEquals(normalizeOutput("" to ExitCode.OK), actual) },
|
||||
manifest: Manifest? = null,
|
||||
vararg extraClassPath: File
|
||||
): File {
|
||||
val sourceDir = File(testDataDirectory, libraryName)
|
||||
val javaFiles = FileUtil.findFilesByMask(JAVA_FILES, sourceDir)
|
||||
val kotlinFiles = FileUtil.findFilesByMask(KOTLIN_FILES, sourceDir)
|
||||
assert(javaFiles.isNotEmpty() || kotlinFiles.isNotEmpty()) { "There should be either .kt or .java files in the directory" }
|
||||
|
||||
val isJar = destination.name.endsWith(".jar")
|
||||
|
||||
val outputDir = if (isJar) File(tmpdir, "output-$libraryName") else destination
|
||||
if (kotlinFiles.isNotEmpty()) {
|
||||
val output = compileKotlin(libraryName, outputDir, extraClassPath.toList(), K2JVMCompiler(), additionalOptions, expectedFileName = null)
|
||||
checkKotlinOutput(normalizeOutput(output))
|
||||
}
|
||||
|
||||
if (javaFiles.isNotEmpty()) {
|
||||
outputDir.mkdirs()
|
||||
if (!compileJava(sourceDir, javaFiles, outputDir)) {
|
||||
throw JavaCompilationError()
|
||||
}
|
||||
}
|
||||
|
||||
if (isJar) {
|
||||
destination.delete()
|
||||
val stream =
|
||||
if (manifest != null) JarOutputStream(destination.outputStream(), manifest)
|
||||
else JarOutputStream(destination.outputStream())
|
||||
stream.use { jar ->
|
||||
ZipUtil.addDirToZipRecursively(jar, destination, outputDir, "", null, null)
|
||||
}
|
||||
}
|
||||
else assertNull("Manifest is ignored if destination is not a .jar file", manifest)
|
||||
|
||||
return destination
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles all .kt sources under the directory named [libraryName] to a file named "[libraryName].js" in [tmpdir]
|
||||
*
|
||||
* @return the path to the corresponding .meta.js file, i.e. "[libraryName].meta.js"
|
||||
*/
|
||||
protected fun compileJsLibrary(libraryName: String): File {
|
||||
val destination = File(tmpdir, "$libraryName.js")
|
||||
val output = compileKotlin(libraryName, destination, compiler = K2JSCompiler(), expectedFileName = null)
|
||||
assertEquals(normalizeOutput("" to ExitCode.OK), normalizeOutput(output))
|
||||
return File(tmpdir, "$libraryName.meta.js")
|
||||
}
|
||||
|
||||
private fun normalizeOutput(output: Pair<String, ExitCode>): String {
|
||||
return AbstractCliTest.getNormalizedCompilerOutput(output.first, output.second, testDataDirectory.path)
|
||||
.replace(FileUtil.toSystemIndependentName(tmpdir.absolutePath), "\$TMP_DIR\$")
|
||||
}
|
||||
|
||||
protected fun compileKotlin(
|
||||
fileName: String,
|
||||
output: File,
|
||||
classpath: List<File> = emptyList(),
|
||||
compiler: CLICompiler<*> = K2JVMCompiler(),
|
||||
additionalOptions: List<String> = emptyList(),
|
||||
expectedFileName: String? = "output.txt"
|
||||
): Pair<String, ExitCode> {
|
||||
val args = mutableListOf<String>()
|
||||
val sourceFile = File(testDataDirectory, fileName)
|
||||
assert(sourceFile.exists()) { "Source file does not exist: ${sourceFile.absolutePath}" }
|
||||
args.add(sourceFile.path)
|
||||
|
||||
if (compiler is K2JSCompiler) {
|
||||
if (classpath.isNotEmpty()) {
|
||||
args.add("-libraries")
|
||||
args.add(classpath.joinToString(File.pathSeparator))
|
||||
}
|
||||
args.add("-output")
|
||||
args.add(output.path)
|
||||
args.add("-meta-info")
|
||||
}
|
||||
else if (compiler is K2JVMCompiler) {
|
||||
if (classpath.isNotEmpty()) {
|
||||
args.add("-classpath")
|
||||
args.add(classpath.joinToString(File.pathSeparator))
|
||||
}
|
||||
args.add("-d")
|
||||
args.add(output.path)
|
||||
}
|
||||
else {
|
||||
throw UnsupportedOperationException(compiler.toString())
|
||||
}
|
||||
|
||||
args.addAll(additionalOptions)
|
||||
|
||||
val result = AbstractCliTest.executeCompilerGrabOutput(compiler, args)
|
||||
if (expectedFileName != null) {
|
||||
KotlinTestUtils.assertEqualsToFile(File(testDataDirectory, expectedFileName), normalizeOutput(result))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KOTLIN_FILES = Pattern.compile(".*\\.kt$")
|
||||
|
||||
@JvmStatic
|
||||
protected val JAVA_FILES = Pattern.compile(".*\\.java$")!!
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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
|
||||
|
||||
abstract class AbstractLoadKotlinWithTypeTableTest : AbstractLoadJavaTest() {
|
||||
protected fun doTest(fileName: String) {
|
||||
doTestCompiledKotlinWithTypeTable(fileName)
|
||||
}
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.google.common.io.Closeables
|
||||
import com.google.common.io.Files
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.codegen.GenerationUtils
|
||||
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.sure
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.nio.charset.Charset
|
||||
import java.util.*
|
||||
import java.util.regex.MatchResult
|
||||
|
||||
abstract class AbstractWriteSignatureTest : TestCaseWithTmpdir() {
|
||||
private var environment: KotlinCoreEnvironment? = null
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
environment = KotlinTestUtils.createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(
|
||||
myTestRootDisposable, ConfigurationKind.ALL, jdkKind
|
||||
)
|
||||
}
|
||||
|
||||
protected open val jdkKind: TestJdkKind
|
||||
get() = TestJdkKind.MOCK_JDK
|
||||
|
||||
override fun tearDown() {
|
||||
environment = null
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
protected fun doTest(ktFileName: String) {
|
||||
val ktFile = File(ktFileName)
|
||||
val text = FileUtil.loadFile(ktFile, true)
|
||||
|
||||
val psiFile = KotlinTestUtils.createFile(ktFile.name, text, environment!!.project)
|
||||
|
||||
val fileFactory = GenerationUtils.compileFileTo(psiFile, environment!!, tmpdir)
|
||||
|
||||
Disposer.dispose(myTestRootDisposable)
|
||||
|
||||
val expectations = parseExpectations(ktFile)
|
||||
try {
|
||||
expectations.check()
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
println(fileFactory.createText())
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private class SignatureExpectation(val header: String, val name: String, val expectedJvmSignature: String?, expectedGenericSignature: String) {
|
||||
private val expectedFormattedSignature = formatSignature(header, expectedJvmSignature, expectedGenericSignature)
|
||||
private val jvmDescriptorToFormattedSignature = mutableMapOf<String, String>()
|
||||
|
||||
fun accept(name: String, actualJvmSignature: String, actualGenericSignature: String) {
|
||||
if (this.name == name) {
|
||||
Assert.assertFalse(jvmDescriptorToFormattedSignature.containsKey(actualJvmSignature))
|
||||
|
||||
jvmDescriptorToFormattedSignature[actualJvmSignature] =
|
||||
formatSignature(header, expectedJvmSignature?.let { actualJvmSignature }, actualGenericSignature)
|
||||
}
|
||||
}
|
||||
|
||||
fun check() {
|
||||
val formattedActualSignature =
|
||||
if (expectedJvmSignature == null) {
|
||||
Assert.assertTrue(
|
||||
"Expected single declaration, but ${jvmDescriptorToFormattedSignature.keys} found",
|
||||
jvmDescriptorToFormattedSignature.size == 1)
|
||||
|
||||
jvmDescriptorToFormattedSignature.values.single()
|
||||
}
|
||||
else {
|
||||
jvmDescriptorToFormattedSignature[expectedJvmSignature].sure {
|
||||
"Expected $expectedJvmSignature but only ${jvmDescriptorToFormattedSignature.keys} found for $name"
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertEquals(expectedFormattedSignature, formattedActualSignature)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PackageExpectationsSuite() {
|
||||
private val classSuitesByClassName = LinkedHashMap<String, ClassExpectationsSuite>()
|
||||
|
||||
fun getOrCreateClassSuite(className: String): ClassExpectationsSuite =
|
||||
classSuitesByClassName.getOrPut(className) { ClassExpectationsSuite(className) }
|
||||
|
||||
fun check() {
|
||||
Assert.assertTrue(classSuitesByClassName.isNotEmpty())
|
||||
classSuitesByClassName.values.forEach { it.check() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private inner class ClassExpectationsSuite(val className: String) {
|
||||
val classExpectations = ArrayList<SignatureExpectation>()
|
||||
val methodExpectations = ArrayList<SignatureExpectation>()
|
||||
val fieldExpectations = ArrayList<SignatureExpectation>()
|
||||
|
||||
fun check() {
|
||||
val checker = Checker()
|
||||
val classFileName = "$tmpdir/${className.replace('.', '/')}.class"
|
||||
val classFile = File(classFileName)
|
||||
|
||||
processClassFile(checker, classFile)
|
||||
|
||||
if (className.endsWith("Package")) {
|
||||
// This class is a package facade. We should also check package parts.
|
||||
processPackageParts(checker, classFile)
|
||||
}
|
||||
|
||||
checkCollectedSignatures()
|
||||
}
|
||||
|
||||
private fun processPackageParts(checker: Checker, classFile: File) {
|
||||
// Look for package parts in the same directory.
|
||||
// Package part file names for package SomePackage look like SomePackage$<hash>.class.
|
||||
val classDir = classFile.parentFile
|
||||
val classLastName = classFile.name
|
||||
val packageFacadePrefix = classLastName.replace(".class", "\$")
|
||||
classDir.listFiles { _, lastName ->
|
||||
lastName.startsWith(packageFacadePrefix) && lastName.endsWith(".class")
|
||||
}.forEach { packageFacadeFile ->
|
||||
processClassFile(checker, packageFacadeFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCollectedSignatures() {
|
||||
(classExpectations + methodExpectations + fieldExpectations).forEach(SignatureExpectation::check)
|
||||
}
|
||||
|
||||
private fun processClassFile(checker: Checker, classFile: File) {
|
||||
val classInputStream = FileInputStream(classFile)
|
||||
try {
|
||||
ClassReader(classInputStream).accept(checker,
|
||||
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
}
|
||||
finally {
|
||||
Closeables.closeQuietly(classInputStream)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Checker : ClassVisitor(Opcodes.ASM5) {
|
||||
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<out String>?) {
|
||||
classExpectations.forEach { it.accept(name, name, signature ?: "null") }
|
||||
super.visit(version, access, name, signature, superName, interfaces)
|
||||
}
|
||||
|
||||
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
|
||||
methodExpectations.forEach { it.accept(name, desc, signature ?: "null") }
|
||||
return super.visitMethod(access, name, desc, signature, exceptions)
|
||||
}
|
||||
|
||||
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
|
||||
fieldExpectations.forEach { it.accept(name, desc, signature ?: "null") }
|
||||
return super.visitField(access, name, desc, signature, value)
|
||||
}
|
||||
}
|
||||
|
||||
fun addClassExpectation(name: String, jvmSignature: String?, genericSignature: String) {
|
||||
classExpectations.add(SignatureExpectation("class: $name", name, jvmSignature, genericSignature))
|
||||
}
|
||||
|
||||
fun addFieldExpectation(className: String, memberName: String, jvmSignature: String?, genericSignature: String) {
|
||||
fieldExpectations.add(SignatureExpectation("field: $className::$memberName", memberName, jvmSignature, genericSignature))
|
||||
}
|
||||
|
||||
fun addMethodExpectation(className: String, memberName: String, jvmSignature: String?, genericSignature: String) {
|
||||
methodExpectations.add(SignatureExpectation("method: $className::$memberName", memberName, jvmSignature, genericSignature))
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseExpectations(ktFile: File): PackageExpectationsSuite {
|
||||
val expectations = PackageExpectationsSuite()
|
||||
|
||||
val lines = Files.readLines(ktFile, Charset.forName("utf-8"))
|
||||
var lineNo = 0
|
||||
while (lineNo < lines.size) {
|
||||
val line = lines[lineNo]
|
||||
val expectationMatch = expectationRegex.matchExact(line)
|
||||
|
||||
if (expectationMatch != null) {
|
||||
val kind = expectationMatch.group(1)!!
|
||||
val className = expectationMatch.group(2)!!
|
||||
val memberName = expectationMatch.group(4)
|
||||
|
||||
if (kind == "class" && memberName != null) {
|
||||
throw AssertionError("$ktFile:${lineNo+1}: use $className\$$memberName to denote inner class")
|
||||
}
|
||||
|
||||
val jvmSignatureMatch = jvmSignatureRegex.matchExact(lines[lineNo + 1])
|
||||
val genericSignatureMatch = genericSignatureRegex.matchExact(lines[lineNo + 1])
|
||||
?: genericSignatureRegex.matchExact(lines[lineNo + 2])
|
||||
|
||||
if (genericSignatureMatch != null) {
|
||||
val jvmSignature = jvmSignatureMatch?.group(1)
|
||||
val genericSignature = genericSignatureMatch.group(1)
|
||||
|
||||
val classSuite = expectations.getOrCreateClassSuite(className)
|
||||
|
||||
when (kind) {
|
||||
"class" -> classSuite.addClassExpectation(className, jvmSignature, genericSignature)
|
||||
"field" -> classSuite.addFieldExpectation(className, memberName, jvmSignature, genericSignature)
|
||||
"method" -> classSuite.addMethodExpectation(className, memberName, jvmSignature, genericSignature)
|
||||
else -> throw AssertionError("$ktFile:${lineNo+1}: unsupported expectation kind: $kind")
|
||||
}
|
||||
|
||||
// Expectation, skip the following 'jvm signature' and 'generic signature' lines
|
||||
lineNo += 3
|
||||
}
|
||||
else {
|
||||
throw AssertionError("$ktFile:${lineNo+1}: '$kind' should be followed by 'jvm signature' and 'generic signature'")
|
||||
}
|
||||
}
|
||||
else {
|
||||
++lineNo
|
||||
}
|
||||
}
|
||||
|
||||
return expectations
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun formatSignature(header: String, jvmSignature: String?, genericSignature: String): String {
|
||||
return listOf(
|
||||
header,
|
||||
jvmSignature?.let { "jvm signature: $it" },
|
||||
"generic signature: $genericSignature"
|
||||
).filterNotNull().joinToString("\n") { "// $it" }
|
||||
}
|
||||
|
||||
val expectationRegex = Regex("^// (class|method|field): *([^:]+)(::(.+))? *(//.*)?")
|
||||
val jvmSignatureRegex = Regex("^// jvm signature: *(.+) *(//.*)?")
|
||||
val genericSignatureRegex = Regex("^// generic signature: *(.+) *(//.*)?")
|
||||
|
||||
fun Regex.matchExact(input: String): MatchResult? {
|
||||
val matcher = this.toPattern().matcher(input)
|
||||
if (matcher.matches()) {
|
||||
return matcher.toMatchResult()
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.serialization
|
||||
|
||||
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.JVMConfigurationKeys
|
||||
import org.jetbrains.kotlin.container.get
|
||||
import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.load.kotlin.DeserializationComponentsForJava
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
|
||||
import org.jetbrains.kotlin.test.*
|
||||
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
|
||||
abstract class AbstractLocalClassProtoTest : TestCaseWithTmpdir() {
|
||||
protected fun doTest(filename: String) {
|
||||
val source = File(filename)
|
||||
|
||||
KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(testRootDisposable).let { environment ->
|
||||
LoadDescriptorUtil.compileKotlinToDirAndGetModule(listOf(source), tmpdir, environment)
|
||||
}
|
||||
|
||||
val classNameSuffix = InTextDirectivesUtils.findStringWithPrefixes(source.readText(), "// CLASS_NAME_SUFFIX: ")
|
||||
?: error("CLASS_NAME_SUFFIX directive not found in test data")
|
||||
|
||||
val classLoader = URLClassLoader(arrayOf(tmpdir.toURI().toURL()), ForTestCompileRuntime.runtimeAndReflectJarClassLoader())
|
||||
|
||||
val classFile = tmpdir.walkTopDown().singleOrNull { it.path.endsWith("$classNameSuffix.class") }
|
||||
?: error("Local class with suffix `$classNameSuffix` is not found in: ${tmpdir.listFiles().toList()}")
|
||||
val clazz = classLoader.loadClass(classFile.toRelativeString(tmpdir).substringBeforeLast(".class").replace('/', '.').replace('\\', '.'))
|
||||
assertHasAnnotationData(clazz)
|
||||
|
||||
val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK, tmpdir).apply {
|
||||
// This is needed because otherwise local classes, being binary classes, are not resolved via source module's container.
|
||||
// They could only be resolved via the container of the _dependency_ module.
|
||||
// This can be improved as soon as there's an API to get the container of the dependency module.
|
||||
put(JVMConfigurationKeys.USE_SINGLE_MODULE, true)
|
||||
}
|
||||
val environment = KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
|
||||
|
||||
val components = JvmResolveUtil.createContainer(environment).get<DeserializationComponentsForJava>().components
|
||||
|
||||
val classDescriptor = components.classDeserializer.deserializeClass(clazz.classId)
|
||||
?: error("Class is not resolved: $clazz (classId = ${clazz.classId})")
|
||||
|
||||
RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile(
|
||||
classDescriptor,
|
||||
RecursiveDescriptorComparator.DONT_INCLUDE_METHODS_OF_OBJECT,
|
||||
KotlinTestUtils.replaceExtension(source, "txt")
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun assertHasAnnotationData(clazz: Class<*>) {
|
||||
checkNotNull(clazz.getAnnotation(
|
||||
clazz.classLoader.loadClass(JvmAnnotationNames.METADATA_FQ_NAME.asString()) as Class<Annotation>
|
||||
)) { "Metadata annotation is not found for class $clazz" }
|
||||
}
|
||||
|
||||
private val Class<*>.classId: ClassId
|
||||
get() = when {
|
||||
enclosingMethod != null || enclosingConstructor != null || simpleName.isEmpty() -> {
|
||||
val fqName = FqName(name)
|
||||
ClassId(fqName.parent(), FqName.topLevel(fqName.shortName()), true)
|
||||
}
|
||||
else -> declaringClass?.classId?.createNestedClassId(Name.identifier(simpleName)) ?: ClassId.topLevel(FqName(name))
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.serialization.builtins
|
||||
|
||||
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns.*
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
|
||||
import org.jetbrains.kotlin.renderer.AnnotationArgumentsRenderingPolicy
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
|
||||
import org.jetbrains.kotlin.renderer.OverrideRenderingPolicy
|
||||
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
|
||||
import org.jetbrains.kotlin.test.ConfigurationKind
|
||||
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
|
||||
import org.jetbrains.kotlin.test.TestJdkKind
|
||||
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractBuiltInsWithJDKMembersTest : KotlinTestWithEnvironment() {
|
||||
private val configuration = RecursiveDescriptorComparator.RECURSIVE_ALL.includeMethodsOfKotlinAny(false).withRenderer(
|
||||
DescriptorRenderer.withOptions {
|
||||
withDefinedIn = false
|
||||
overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OPEN_OVERRIDE
|
||||
verbose = true
|
||||
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
|
||||
modifiers = DescriptorRendererModifier.ALL
|
||||
})
|
||||
|
||||
override fun createEnvironment(): KotlinCoreEnvironment =
|
||||
createEnvironmentWithJdk(ConfigurationKind.JDK_ONLY, testJdkKind)
|
||||
|
||||
protected open val testJdkKind: TestJdkKind
|
||||
get() = TestJdkKind.FULL_JDK
|
||||
|
||||
protected fun doTest(builtinVersionName: String) {
|
||||
val module = JvmResolveUtil.analyze(environment).moduleDescriptor as ModuleDescriptorImpl
|
||||
|
||||
for (packageFqName in listOf(BUILT_INS_PACKAGE_FQ_NAME, COLLECTIONS_PACKAGE_FQ_NAME, RANGES_PACKAGE_FQ_NAME)) {
|
||||
val loaded =
|
||||
module.packageFragmentProvider.getPackageFragments(packageFqName).filterIsInstance<BuiltInsPackageFragment>().single()
|
||||
RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile(
|
||||
loaded, configuration,
|
||||
File("compiler/testData/builtin-classes/$builtinVersionName/" + packageFqName.asString().replace('.', '-') + ".txt")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user