From a2b2eba1a1860377f1e590610ae35c8fd9323885 Mon Sep 17 00:00:00 2001 From: dnpetrov Date: Mon, 15 Jun 2015 12:10:35 +0300 Subject: [PATCH] Extended version of AbstractWriteSignatureTest: - support multiple expectations in file - support field signature expectations - report failures in a more usable format --- .../declarationSiteVariance/OutInField.kt | 10 + .../compiler/AbstractWriteSignatureTest.java | 209 -------------- .../compiler/AbstractWriteSignatureTest.kt | 254 ++++++++++++++++++ .../compiler/WriteSignatureTestGenerated.java | 6 + 4 files changed, 270 insertions(+), 209 deletions(-) create mode 100644 compiler/testData/writeSignature/declarationSiteVariance/OutInField.kt delete mode 100644 compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.java create mode 100644 compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.kt diff --git a/compiler/testData/writeSignature/declarationSiteVariance/OutInField.kt b/compiler/testData/writeSignature/declarationSiteVariance/OutInField.kt new file mode 100644 index 00000000000..5b7af727822 --- /dev/null +++ b/compiler/testData/writeSignature/declarationSiteVariance/OutInField.kt @@ -0,0 +1,10 @@ +public val list: List = throw Exception() +public val mutList: MutableList = throw Exception() + +// field: _DefaultPackage::list +// jvm signature: Ljava/util/List; +// generic signature: Ljava/util/List<+Ljava/lang/String;>; + +// field: _DefaultPackage::mutList +// jvm signature: Ljava/util/List; +// generic signature: Ljava/util/List; \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.java deleted file mode 100644 index c5d1748db2a..00000000000 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.jvm.compiler; - -import com.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.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.kotlin.backend.common.output.OutputFileCollection; -import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsPackage; -import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; -import org.jetbrains.kotlin.codegen.GenerationUtils; -import org.jetbrains.kotlin.psi.JetFile; -import org.jetbrains.kotlin.test.JetTestUtils; -import org.jetbrains.kotlin.test.TestCaseWithTmpdir; -import org.jetbrains.org.objectweb.asm.ClassReader; -import org.jetbrains.org.objectweb.asm.ClassVisitor; -import org.jetbrains.org.objectweb.asm.MethodVisitor; -import org.jetbrains.org.objectweb.asm.Opcodes; -import org.junit.Assert; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Test correctness of written JVM signature - * - * @see CompileJavaAgainstKotlinTestGenerated - */ -public abstract class AbstractWriteSignatureTest extends TestCaseWithTmpdir { - - private KotlinCoreEnvironment jetCoreEnvironment; - - @Override - protected void setUp() throws Exception { - super.setUp(); - jetCoreEnvironment = JetTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable); - } - - @Override - protected void tearDown() throws Exception { - jetCoreEnvironment = null; - super.tearDown(); - } - - protected void doTest(String ktFileName) throws Exception { - File ktFile = new File(ktFileName); - String text = FileUtil.loadFile(ktFile, true); - - JetFile psiFile = JetTestUtils.createFile(ktFile.getName(), text, jetCoreEnvironment.getProject()); - - OutputFileCollection outputFiles = GenerationUtils.compileFileGetClassFileFactoryForTest(psiFile); - - OutputUtilsPackage.writeAllTo(outputFiles, tmpdir); - - Disposer.dispose(myTestRootDisposable); - - Expectation expectation = parseExpectations(ktFile); - - ActualSignature actualSignature = readSignature(expectation.className, expectation.methodName); - - String template = - "jvm signature: %s\n" + - "generic signature: %s\n" + - ""; - - String expected = String.format(template, expectation.jvmSignature, expectation.genericSignature); - String actual = String.format(template, actualSignature.jvmSignature, actualSignature.genericSignature); - - Assert.assertEquals(expected, actual); - } - - private static class ActualSignature { - private final String jvmSignature; - private final String genericSignature; - - private ActualSignature(@NotNull String jvmSignature, @Nullable String genericSignature) { - this.jvmSignature = jvmSignature; - this.genericSignature = genericSignature; - } - } - - @NotNull - private ActualSignature readSignature(@NotNull String className, @Nullable final String methodName) throws Exception { - // ugly unreadable code begin - FileInputStream classInputStream = new FileInputStream(tmpdir + "/" + className.replace('.', '/') + ".class"); - try { - class Visitor extends ClassVisitor { - ActualSignature readSignature; - - public Visitor() { - super(Opcodes.ASM5); - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - if (methodName == null) { - Assert.assertNull(readSignature); - readSignature = new ActualSignature(name, signature); - } - super.visit(version, access, name, signature, superName, interfaces); - } - - @Override - public MethodVisitor visitMethod(int access, String name, final String desc, final String signature, String[] exceptions) { - if (name.equals(methodName)) { - - return new MethodVisitor(Opcodes.ASM5) { - @Override - public void visitEnd() { - Assert.assertNull(readSignature); - readSignature = new ActualSignature(desc, signature); - } - }; - } - return super.visitMethod(access, name, desc, signature, exceptions); - } - } - - Visitor visitor = new Visitor(); - - new ClassReader(classInputStream).accept(visitor, - ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - - Assert.assertNotNull("method not found: " + className + "::" + methodName, visitor.readSignature); - - return visitor.readSignature; - } - finally { - Closeables.closeQuietly(classInputStream); - } - // ugly unreadable code end - } - - private static class Expectation { - private final String className; - @Nullable - private final String methodName; - private final String jvmSignature; - private final String genericSignature; - - private Expectation( - @NotNull String className, @Nullable String methodName, - @NotNull String jvmSignature, @Nullable String genericSignature - ) { - this.className = className; - this.methodName = methodName; - this.jvmSignature = jvmSignature; - this.genericSignature = genericSignature; - } - } - - @NotNull - private static final Pattern classPattern = Pattern.compile("^// class: *(.*)"); - private static final Pattern methodPattern = Pattern.compile("^// method: *(.*)::(.*?) *(//.*)?"); - private static final Pattern jvmSignaturePattern = Pattern.compile("^// jvm signature: *(.+?) *(//.*)?"); - private static final Pattern genericSignaturePattern = Pattern.compile("^// generic signature: *(.+?) *(//.*)?"); - - private Expectation parseExpectations(File ktFile) throws IOException { - List lines = Files.readLines(ktFile, Charset.forName("utf-8")); - for (int i = 0; i < lines.size() - 2; ++i) { - String line = lines.get(i); - Matcher methodMatcher = methodPattern.matcher(line); - Matcher classMatcher = classPattern.matcher(line); - boolean isMethod = methodMatcher.matches(); - boolean isClass = classMatcher.matches(); - Matcher matcher = isMethod ? methodMatcher : classMatcher; - if (isMethod || isClass) { - Matcher jvmSignatureMatcher = jvmSignaturePattern.matcher(lines.get(i + 1)); - Matcher genericSignatureMatcher = genericSignaturePattern.matcher(lines.get(i + 2)); - if (!jvmSignatureMatcher.matches() || !genericSignatureMatcher.matches()) { - throw new AssertionError("'method:' must be followed ... bla bla ... use the source luke"); - } - - String className = matcher.group(1); - String methodName = isMethod ? matcher.group(2) : null; - - String jvmSignature = jvmSignatureMatcher.group(1); - String genericSignature = genericSignatureMatcher.group(1); - if (genericSignature.equals("null")) { - genericSignature = null; - } - return new Expectation(className, methodName, jvmSignature, genericSignature); - } - } - throw new AssertionError("test instructions not found in " + ktFile); - } -} diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.kt new file mode 100644 index 00000000000..1b8bdf5741b --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/AbstractWriteSignatureTest.kt @@ -0,0 +1,254 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.jvm.compiler + +import com.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.common.output.outputUtils.writeAllTo +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.codegen.GenerationUtils +import org.jetbrains.kotlin.test.JetTestUtils +import org.jetbrains.kotlin.test.TestCaseWithTmpdir +import org.jetbrains.kotlin.utils.join +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 +import java.util.regex.Pattern +import kotlin.text.Regex + + +public abstract class AbstractWriteSignatureTest : TestCaseWithTmpdir() { + private var jetCoreEnvironment: KotlinCoreEnvironment? = null + + override fun setUp() { + super.setUp() + jetCoreEnvironment = JetTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable) + } + + override fun tearDown() { + jetCoreEnvironment = null + super.tearDown() + } + + protected fun doTest(ktFileName: String) { + val ktFile = File(ktFileName) + val text = FileUtil.loadFile(ktFile, true) + + val psiFile = JetTestUtils.createFile(ktFile.getName(), text, jetCoreEnvironment!!.project) + + val outputFiles = GenerationUtils.compileFileGetClassFileFactoryForTest(psiFile) + + outputFiles.writeAllTo(tmpdir) + + Disposer.dispose(myTestRootDisposable) + + val expectations = parseExpectations(ktFile) + expectations.check() + } + + private class SignatureExpectation(val header: String, val name: String, expectedJvmSignature: String, expectedGenericSignature: String) { + private var checked = false + private val expectedSignature = formatSignature(header, expectedJvmSignature, expectedGenericSignature) + + fun isChecked(): Boolean = checked + + fun check(name: String, actualJvmSignature: String, actualGenericSignature: String) { + if (this.name == name) { + checked = true + val actualSignature = formatSignature(header, actualJvmSignature, actualGenericSignature) + Assert.assertEquals(expectedSignature, actualSignature) + } + } + + } + + private inner class PackageExpectationsSuite() { + private val classSuitesByClassName = LinkedHashMap() + + fun getOrCreateClassSuite(className: String): ClassExpectationsSuite = + classSuitesByClassName.getOrPut(className) { ClassExpectationsSuite(className) } + + fun check() { + classSuitesByClassName.values().forEach { it.check() } + } + + } + + private inner class ClassExpectationsSuite(val className: String) { + val classExpectations = ArrayList() + val methodExpectations = ArrayList() + val fieldExpectations = ArrayList() + + fun check() { + val checker = Checker() + val classFileName = "$tmpdir/${className.replace('.', '/')}.class" + val classFile = File(classFileName) + + checkClassFile(checker, classFile) + + if (className.endsWith("Package")) { + // This class is a package facade. We should also check package parts. + checkPackageParts(checker, classFile) + } + + assertAllChecked() + } + + private fun checkPackageParts(checker: Checker, classFile: File) { + // Look for package parts in the same directory. + // Package part file names for package SomePackage look like SomePackage$.class. + val classDir = classFile.getParentFile() + val classLastName = classFile.getName() + val packageFacadePrefix = classLastName.replace(".class", "\$") + classDir.listFiles { dir, lastName -> + lastName.startsWith(packageFacadePrefix) && lastName.endsWith(".class") + } forEach { packageFacadeFile -> + checkClassFile(checker, packageFacadeFile) + } + } + + private fun assertAllChecked() { + val uncheckedExpectations = ArrayList() + classExpectations.filterNotTo(uncheckedExpectations) { it.isChecked() } + methodExpectations.filterNotTo(uncheckedExpectations) { it.isChecked() } + fieldExpectations.filterNotTo(uncheckedExpectations) { it.isChecked() } + Assert.assertTrue( + "Unchecked expectations (${uncheckedExpectations.size()} total):\n " + uncheckedExpectations.join("\n "), + uncheckedExpectations.isEmpty()) + } + + private fun checkClassFile(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?) { + classExpectations forEach { it.check(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?): MethodVisitor? { + methodExpectations forEach { it.check(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.check(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)) + } + } + + 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") + } + else if (memberName == null) { + throw AssertionError("$ktFile:${lineNo+1}: '$kind' requires member name (after $className::)") + } + + val jvmSignatureMatch = jvmSignatureRegex.matchExact(lines[lineNo+1]) + val genericSignatureMatch = genericSignatureRegex.matchExact(lines[lineNo+2]) + + if (jvmSignatureMatch != null && 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 = + """// $header +// jvm signature: $jvmSignature +// generic signature: $genericSignature""" + + 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 + } + } + } +} + diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java index 0aebb8380f8..0ea771a0433 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java @@ -259,6 +259,12 @@ public class WriteSignatureTestGenerated extends AbstractWriteSignatureTest { doTest(fileName); } + @TestMetadata("OutInField.kt") + public void testOutInField() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/writeSignature/declarationSiteVariance/OutInField.kt"); + doTest(fileName); + } + @TestMetadata("OutInInPosition.kt") public void testOutInInPosition() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/writeSignature/declarationSiteVariance/OutInInPosition.kt");