diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java index 8d3baf5bd09..4f471a4b69d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassFileFactory.java @@ -183,6 +183,16 @@ public class ClassFileFactory implements OutputFileCollection { return answer.toString(); } + @NotNull + @TestOnly + public Map createTextForEachFile() { + Map answer = new LinkedHashMap(); + for (OutputFile file : asList()) { + answer.put(file.getRelativePath(), file.asText()); + } + return answer; + } + @NotNull public PackageCodegen forPackage(@NotNull FqName fqName, @NotNull Collection files) { assert !isDone : "Already done!"; diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt index e4dad593df1..587ecab9815 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/fileClasses/JvmFileClassUtil.kt @@ -55,13 +55,6 @@ public object JvmFileClassUtil { public @JvmStatic fun manglePartName(facadeName: String, fileName: String): String = "${facadeName}__${PackagePartClassUtils.getFilePartShortName(fileName)}" - public @JvmStatic fun parseJvmFileClass(jvmName: AnnotationDescriptor, jvmMultifileClass: AnnotationDescriptor?): ParsedJmvFileClassAnnotations? { - val jvmNameArgument = jvmName.allValueArguments.values().singleOrNull() ?: return null - val name = (jvmNameArgument as? StringValue)?.value ?: return null - val isMultifileClassPart = jvmMultifileClass != null - return ParsedJmvFileClassAnnotations(name, isMultifileClassPart) - } - public @JvmStatic fun getFileClassInfoNoResolve(file: JetFile): JvmFileClassInfo = getFileClassInfo(file, parseJvmNameOnFileNoResolve(file)) diff --git a/compiler/testData/codegen/bytecodeTextMultifile/partMembersCall/thisFile.kt b/compiler/testData/codegen/bytecodeTextMultifile/partMembersCall/thisFile.kt index 24216cb85e5..0852178c725 100644 --- a/compiler/testData/codegen/bytecodeTextMultifile/partMembersCall/thisFile.kt +++ b/compiler/testData/codegen/bytecodeTextMultifile/partMembersCall/thisFile.kt @@ -15,9 +15,9 @@ internal fun internalInThisFile() {} public fun publicInThisFile() {} -// @test/1ThisFileKt.class: -// 1 INVOKESTATIC test/1ThisFileKt.privateInThisFile -// 1 INVOKESTATIC test/1ThisFileKt.internalInThisFile -// 1 INVOKESTATIC test/1ThisFileKt.publicInThisFile -// 1 INVOKESTATIC test/1OtherFileKt.internalInOtherFile -// 1 INVOKESTATIC test/1OtherFileKt.publicInOtherFile \ No newline at end of file +// @test/Util__ThisFileKt.class: +// 1 INVOKESTATIC test/Util__ThisFileKt.privateInThisFile +// 1 INVOKESTATIC test/Util.internalInThisFile +// 1 INVOKESTATIC test/Util.publicInThisFile +// 1 INVOKESTATIC test/Util.internalInOtherFile +// 1 INVOKESTATIC test/Util.publicInOtherFile \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java b/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java index 389b1c2b915..2a1edbd9715 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/AbstractBytecodeTextTest.java @@ -18,17 +18,19 @@ package org.jetbrains.kotlin.codegen; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Processor; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.test.ConfigurationKind; import org.jetbrains.kotlin.utils.UtilsPackage; import java.io.File; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public abstract class AbstractBytecodeTextTest extends CodegenTestCase { + private static final Pattern AT_OUTPUT_FILE_PATTERN = Pattern.compile("^\\s*//\\s*@(.*):$"); private static final Pattern EXPECTED_OCCURRENCES_PATTERN = Pattern.compile("^\\s*//\\s*(\\d+)\\s*(.*)$"); public void doTest(@NotNull String filename) throws Exception { @@ -39,10 +41,18 @@ public abstract class AbstractBytecodeTextTest extends CodegenTestCase { } protected void countAndCompareActualOccurrences(@NotNull List expectedOccurrences) { + String text = generateToText(); + + checkGeneratedTextAgainstExpectedOccurrences(text, expectedOccurrences); + } + + private static void checkGeneratedTextAgainstExpectedOccurrences( + @NotNull String text, + @NotNull List expectedOccurrences + ) { StringBuilder expected = new StringBuilder(); StringBuilder actual = new StringBuilder(); - String text = generateToText(); for (OccurrenceInfo info : expectedOccurrences) { expected.append(info.numberOfOccurrences).append(" ").append(info.needle).append("\n"); int actualCount = StringUtil.findMatches(text, Pattern.compile("(" + info.needle + ")")).size(); @@ -58,6 +68,52 @@ public abstract class AbstractBytecodeTextTest extends CodegenTestCase { } } + public void doTestMultiFile(@NotNull String folderName) throws Exception { + final List files = new ArrayList(2); + FileUtil.processFilesRecursively(new File(folderName), new Processor() { + @Override + public boolean process(File file) { + if (file.getName().endsWith(".kt")) { + files.add(relativePath(file)); + } + return true; + } + }); + + doTestMultiFile(files); + } + + public void doTestMultiFile(@NotNull List filenames) throws Exception { + createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL); + Collections.sort(filenames); + String[] sortedFilenames = ArrayUtil.toStringArray(filenames); + loadFiles(sortedFilenames); + + Map> expectedOccurrencesByOutputFile = new LinkedHashMap>(); + for (String filename : filenames) { + readExpectedOccurrencesForMultiFileTest(filename, expectedOccurrencesByOutputFile); + } + + Map generated = generateEachFileToText(); + for (String expectedOutputFile : expectedOccurrencesByOutputFile.keySet()) { + assertTextWasGenerated(expectedOutputFile, generated); + String generatedText = generated.get(expectedOutputFile); + List expectedOccurrences = expectedOccurrencesByOutputFile.get(expectedOutputFile); + checkGeneratedTextAgainstExpectedOccurrences(generatedText, expectedOccurrences); + } + } + + private static void assertTextWasGenerated(String expectedOutputFile, Map generated) { + if (!generated.containsKey(expectedOutputFile)) { + StringBuilder failMessage = new StringBuilder(); + failMessage.append("Missing output file ").append(expectedOutputFile).append(", got ").append(generated.size()).append(": "); + for (String generatedFile : generated.keySet()) { + failMessage.append(generatedFile).append(" "); + } + fail(failMessage.toString()); + } + } + @NotNull protected List readExpectedOccurrences(@NotNull String filename) throws Exception { List result = new ArrayList(); @@ -66,15 +122,45 @@ public abstract class AbstractBytecodeTextTest extends CodegenTestCase { for (String line : lines) { Matcher matcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line); if (matcher.matches()) { - int numberOfOccurrences = Integer.parseInt(matcher.group(1)); - String needle = matcher.group(2); - result.add(new OccurrenceInfo(numberOfOccurrences, needle)); + result.add(parseOccurrenceInfo(matcher)); } } return result; } + protected void readExpectedOccurrencesForMultiFileTest(@NotNull String filename, @NotNull Map> occurrenceMap) throws Exception { + String[] lines = FileUtil.loadFile(new File(codegenTestBasePath() + filename), true).split("\n"); + List currentOccurrenceInfos = null; + for (String line : lines) { + Matcher atOutputFileMatcher = AT_OUTPUT_FILE_PATTERN.matcher(line); + if (atOutputFileMatcher.matches()) { + String outputFileName = atOutputFileMatcher.group(1); + if (occurrenceMap.containsKey(outputFileName)) { + throw new AssertionError(filename + ": Expected occurrences for output file " + outputFileName + " were already provided"); + } + currentOccurrenceInfos = new ArrayList(); + occurrenceMap.put(outputFileName, currentOccurrenceInfos); + } + + Matcher expectedOccurrencesMatcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line); + if (expectedOccurrencesMatcher.matches()) { + if (currentOccurrenceInfos == null) { + throw new AssertionError(filename + ": Should specify output file with '// @:' before expectations"); + } + OccurrenceInfo occurrenceInfo = parseOccurrenceInfo(expectedOccurrencesMatcher); + currentOccurrenceInfos.add(occurrenceInfo); + } + } + } + + @NotNull + private static OccurrenceInfo parseOccurrenceInfo(Matcher matcher) { + int numberOfOccurrences = Integer.parseInt(matcher.group(1)); + String needle = matcher.group(2); + return new OccurrenceInfo(numberOfOccurrences, needle); + } + protected static class OccurrenceInfo { private final int numberOfOccurrences; private final String needle; diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/AbstractTopLevelMembersInvocationTest.java b/compiler/tests/org/jetbrains/kotlin/codegen/AbstractTopLevelMembersInvocationTest.java index 5cf12145ff5..4492f29a7f6 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/AbstractTopLevelMembersInvocationTest.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/AbstractTopLevelMembersInvocationTest.java @@ -16,10 +16,10 @@ package org.jetbrains.kotlin.codegen; +import kotlin.KotlinPackage; import com.intellij.openapi.util.io.FileUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; -import kotlin.KotlinPackage; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java index 52dd4c7530e..3301127fccb 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -47,6 +47,7 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.List; +import java.util.Map; import static org.jetbrains.kotlin.cli.jvm.config.ConfigPackage.getJvmClasspathRoots; import static org.jetbrains.kotlin.codegen.CodegenTestUtil.*; @@ -179,6 +180,14 @@ public abstract class CodegenTestCase extends UsefulTestCase { return classFileFactory.createText(); } + @NotNull + protected Map generateEachFileToText() { + if (classFileFactory == null) { + classFileFactory = generateFiles(myEnvironment, myFiles); + } + return classFileFactory.createTextForEachFile(); + } + @NotNull protected Class generatePackageClass() { FqName packageFqName = myFiles.getPsiFile().getPackageFqName(); diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 01f1c196df6..b24d00af56a 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -203,10 +203,14 @@ fun main(args: Array) { model("codegen/script", extension = "kts") } - testClass(javaClass()) { + testClass(AbstractBytecodeTextTest::class.java) { model("codegen/bytecodeText") } + testClass(AbstractBytecodeTextTest::class.java, "BytecodeTextMultifileTestGenerated") { + model("codegen/bytecodeTextMultifile", extension = null, recursive = false, testMethod = "doTestMultiFile") + } + testClass(javaClass()) { model("codegen/bytecodeListing") }