Diagnostic tests are logging their lazy activity

This commit is contained in:
Andrey Breslav
2013-11-21 16:04:48 +04:00
parent 8436147cec
commit 4892369cf4
1659 changed files with 173986 additions and 5 deletions
@@ -41,6 +41,7 @@ import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import junit.framework.TestCase;
import kotlin.Function1;
import kotlin.KotlinPackage;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -438,6 +439,15 @@ public class JetTestUtils {
}
public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull String actual) {
assertEqualsToFile(expectedFile, actual, new Function1<String, String>() {
@Override
public String invoke(String s) {
return s;
}
});
}
public static void assertEqualsToFile(@NotNull File expectedFile, @NotNull String actual, @NotNull Function1<String, String> sanitizer) {
try {
String actualText = UtilPackage.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(actual.trim()));
@@ -449,7 +459,7 @@ public class JetTestUtils {
String expectedText = UtilPackage.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(expected.trim()));
if (!Comparing.equal(expectedText, actualText)) {
if (!Comparing.equal(sanitizer.invoke(expectedText), sanitizer.invoke(actualText))) {
throw new FileComparisonFailure("Actual data differs from file content: " + expectedFile.getName(),
expected, actual, expectedFile.getAbsolutePath());
}
@@ -26,8 +26,11 @@ import com.intellij.psi.PsiFile;
import kotlin.Function1;
import kotlin.KotlinPackage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.cli.jvm.compiler.CliLightClassGenerationSupport;
import org.jetbrains.jet.context.GlobalContext;
import org.jetbrains.jet.context.SimpleGlobalContext;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.PackageFragmentDescriptor;
import org.jetbrains.jet.lang.descriptors.PackageFragmentProvider;
@@ -46,6 +49,8 @@ import org.jetbrains.jet.lang.resolve.lazy.LazyResolveTestUtil;
import org.jetbrains.jet.lang.resolve.name.FqName;
import org.jetbrains.jet.lang.resolve.name.Name;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.storage.ExceptionTracker;
import org.jetbrains.jet.storage.LockBasedStorageManager;
import org.jetbrains.jet.test.util.DescriptorValidator;
import org.jetbrains.jet.test.util.RecursiveDescriptorComparator;
import org.jetbrains.jet.utils.UtilsPackage;
@@ -57,6 +62,14 @@ import static org.jetbrains.jet.lang.diagnostics.Errors.*;
import static org.jetbrains.jet.test.util.RecursiveDescriptorComparator.RECURSIVE;
public abstract class AbstractJetDiagnosticsTest extends BaseDiagnosticsTest {
public static final Function1<String, String> HASH_SANITIZER = new Function1<String, String>() {
@Override
public String invoke(String s) {
return s.replaceAll("@(\\d)+", "");
}
};
@Override
protected void analyzeAndCheck(File testDataFile, List<TestFile> testFiles) {
Map<TestModule, List<TestFile>> groupedByModule = KotlinPackage.groupByTo(
@@ -77,6 +90,8 @@ public abstract class AbstractJetDiagnosticsTest extends BaseDiagnosticsTest {
Map<TestModule, ModuleDescriptorImpl> modules = createModules(groupedByModule);
Map<TestModule, BindingContext> moduleBindings = new HashMap<TestModule, BindingContext>();
LazyOperationsLog lazyOperationsLog = new LazyOperationsLog(HASH_SANITIZER);
for (Map.Entry<TestModule, List<TestFile>> entry : groupedByModule.entrySet()) {
TestModule testModule = entry.getKey();
List<? extends TestFile> testFilesInModule = entry.getValue();
@@ -101,8 +116,18 @@ public abstract class AbstractJetDiagnosticsTest extends BaseDiagnosticsTest {
// New JavaDescriptorResolver is created for each module, which is good because it emulates different Java libraries for each module,
// albeit with same class names
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
ExceptionTracker tracker = new ExceptionTracker();
GlobalContext context = new SimpleGlobalContext(
new LoggingStorageManager(
LockBasedStorageManager.createWithExceptionHandling(tracker),
lazyOperationsLog.getAddRecordFunction()
),
tracker
);
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationWithCustomContext(
getProject(),
context,
jetFiles,
moduleTrace,
Predicates.<PsiFile>alwaysTrue(),
@@ -116,6 +141,8 @@ public abstract class AbstractJetDiagnosticsTest extends BaseDiagnosticsTest {
// We want to always create a test data file (txt) if it was missing,
// but don't want to skip the following checks in case this one fails
Throwable exceptionFromLazyResolveLogValidation = checkLazyResolveLog(lazyOperationsLog, testDataFile);
Throwable exceptionFromDescriptorValidation = null;
try {
File expectedFile = new File(FileUtil.getNameWithoutExtension(testDataFile.getAbsolutePath()) + ".txt");
@@ -143,6 +170,27 @@ public abstract class AbstractJetDiagnosticsTest extends BaseDiagnosticsTest {
if (exceptionFromDescriptorValidation != null) {
throw UtilsPackage.rethrow(exceptionFromDescriptorValidation);
}
if (exceptionFromLazyResolveLogValidation != null) {
throw UtilsPackage.rethrow(exceptionFromLazyResolveLogValidation);
}
}
@Nullable
private static Throwable checkLazyResolveLog(LazyOperationsLog lazyOperationsLog, File testDataFile) {
Throwable exceptionFromLazyResolveLogValidation = null;
try {
File expectedFile = new File(FileUtil.getNameWithoutExtension(testDataFile.getAbsolutePath()) + ".lazy.log");
JetTestUtils.assertEqualsToFile(
expectedFile,
lazyOperationsLog.getText(),
HASH_SANITIZER
);
}
catch (Throwable e) {
exceptionFromLazyResolveLogValidation = e;
}
return exceptionFromLazyResolveLogValidation;
}
private void validateAndCompareDescriptorWithFile(
@@ -0,0 +1,199 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.checkers
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.lang.resolve.name.Name
import org.jetbrains.jet.lang.descriptors.Named
import java.util.IdentityHashMap
import org.jetbrains.jet.lang.resolve.scopes.JetScope
import org.jetbrains.jet.lang.resolve.java.structure.impl.JavaTypeImpl
import org.jetbrains.jet.lang.resolve.java.structure.impl.JavaClassImpl
import java.util.ArrayList
import org.jetbrains.jet.utils.Printer
import org.jetbrains.jet.lang.resolve.java.structure.JavaNamedElement
import org.jetbrains.jet.descriptors.serialization.ProtoBuf
import org.jetbrains.jet.descriptors.serialization.TypeDeserializer
import org.jetbrains.jet.descriptors.serialization.context.DeserializationContext
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.resolve.DescriptorUtils
import java.util.HashMap
import org.jetbrains.jet.lang.types.JetTypeImpl
import java.util.regex.Pattern
class LazyOperationsLog(
val stringSanitizer: (String) -> String
) {
val ids = IdentityHashMap<Any, Int>()
private fun objectId(o: Any): Int = ids.getOrPut(o, { ids.size() })
private class Record(
val lambda: Any,
val data: LoggingStorageManager.CallData
)
private val records = ArrayList<Record>()
public val addRecordFunction: (lambda: Any, LoggingStorageManager.CallData) -> Unit = {
lambda, data ->
records.add(Record(lambda, data))
}
public fun getText(): String {
val groupedByOwner = records.groupByTo(IdentityHashMap()) {
val owner = it.data.fieldOwner
if (owner is JetScope) owner.getContainingDeclaration() else owner
}.map { Pair(it.getKey(), it.getValue()) }
return groupedByOwner.map {
val (owner, records) = it
renderOwner(owner, records)
}.sortBy(stringSanitizer).join("\n").renumberObjects()
}
/**
* Replaces ids in the given string so that they increase
* Example:
* input = "A@21 B@6"
* output = "A@0 B@1"
*/
private fun String.renumberObjects(): String {
val ids = HashMap<String, String>()
fun newId(objectId: String): String {
return ids.getOrPut(objectId, { "@" + ids.size() })
}
val m = Pattern.compile("@\\d+").matcher(this)
val sb = StringBuffer()
while (m.find()) {
m.appendReplacement(sb, newId(m.group(0)))
}
m.appendTail(sb)
return sb.toString()
}
private fun renderOwner(owner: Any?, records: List<Record>): String {
val sb = StringBuilder()
with (Printer(sb)) {
println(render(owner), " {")
indent {
records.map { renderRecord(it) }.sortBy(stringSanitizer).forEach {
println(it)
}
}
println("}")
}
return sb.toString()
}
private fun renderRecord(record: Record): String {
val data = record.data
val sb = StringBuilder()
sb.append(data.field?.getName() ?: "<name not found>")
if (!data.arguments.isEmpty()) {
sb.append(data.arguments.map { render(it) }.join(", ", "(", ")"))
}
sb.append(" = ${render(data.result)}")
if (data.fieldOwner is JetScope) {
sb.append(" // through ${render(data.fieldOwner)}")
}
return sb.toString()
}
private fun render(o: Any?): String {
if (o == null) return "null"
val sb = StringBuilder()
if (o is FqName || o is Name || o is String || o is Number || o is Boolean) {
sb.append("'$o': ")
}
val id = objectId(o)
val aClass = o.javaClass
sb.append(if (aClass.isAnonymousClass()) aClass.getName().substringAfterLast('.') else aClass.getSimpleName()).append("@$id")
when {
o is Named -> sb.append("['${o.getName()}']")
o.javaClass.getSimpleName() == "LazyJavaClassifierType" -> {
val javaType = o.field<JavaTypeImpl<*>>("javaType")
sb.append("['${javaType.getPsi().getPresentableText()}']")
}
o.javaClass.getSimpleName() == "LazyJavaClassTypeConstructor" -> {
val javaClass = o.field<Any>("this\$0").field<JavaClassImpl>("jClass")
sb.append("['${javaClass.getPsi().getName()}']")
}
o.javaClass.getSimpleName() == "DeserializedType" -> {
val typeDeserializer = o.field<TypeDeserializer>("this\$0")
val context = typeDeserializer.field<DeserializationContext>("context")
val typeProto = o.field<ProtoBuf.Type>("typeProto")
val text = when (typeProto.getConstructor().getKind()) {
ProtoBuf.Type.Constructor.Kind.CLASS -> context.nameResolver.getFqName(typeProto.getConstructor().getId()).asString()
ProtoBuf.Type.Constructor.Kind.TYPE_PARAMETER -> {
val classifier = (o as JetType).getConstructor().getDeclarationDescriptor()
"" + classifier.getName() + " in " + DescriptorUtils.getFqName(classifier.getContainingDeclaration())
}
else -> "???"
}
sb.append("['$text']")
}
o is JavaNamedElement -> {
sb.append("['${o.getName()}']")
}
o is JavaTypeImpl<*> -> {
sb.append("['${o.getPsi().getPresentableText()}']")
}
o is Collection<*> -> {
if (o.isEmpty()) {
sb.append("[empty]")
}
else {
val size = o.size()
sb.append("[$size] { ").append(o.take(3).map { render(it) }.join(", "))
if (o.size() > 3) sb.append(", ...")
sb.append(" }")
}
}
o is JetTypeImpl -> {
sb.append("['").append(o.getConstructor())
if (!o.getArguments().isEmpty()) {
sb.append("<${o.getArguments().size()}>")
}
sb.append("']")
}
}
return sb.toString()
}
}
private fun <T> Any.field(name: String): T {
val field = this.javaClass.getDeclaredField(name)
field.setAccessible(true)
[suppress("UNCHECKED_CAST")]
return field.get(this) as T
}
private fun Printer.indent(body: Printer.() -> Unit): Printer {
pushIndent()
body()
popIndent()
return this
}
@@ -0,0 +1,151 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.checkers
import org.jetbrains.jet.checkers.LoggingStorageManager.CallData
import org.jetbrains.jet.storage.StorageManager
import java.lang.reflect.*
import org.jetbrains.jet.storage.MemoizedFunctionToNotNull
import org.jetbrains.jet.storage.MemoizedFunctionToNullable
import org.jetbrains.jet.storage.NotNullLazyValue
import org.jetbrains.jet.storage.NullableLazyValue
public class LoggingStorageManager(
private val delegate: StorageManager,
private val callHandler: (lambda: Any, call: LoggingStorageManager.CallData?) -> Unit
) : StorageManager {
public class CallData(
val fieldOwner: Any?,
val field: Field?,
val lambdaCreatedIn: GenericDeclaration?,
val arguments: List<Any?>,
val result: Any?
)
// Creating objects here because we need a reference to it
private val <T> (() -> T).logged: () -> T
get() = object : () -> T {
override fun invoke(): T {
val result = this@logged()
callHandler(this@logged, computeCallerData(this@logged, this, listOf(), result))
return result
}
}
// Creating objects here because we need a reference to it
private val <K, V> ((K) -> V).logged: (K) -> V
get() = object : (K) -> V {
override fun invoke(p1: K): V {
val result = this@logged(p1)
callHandler(this@logged, computeCallerData(this@logged, this, listOf(p1), result))
return result
}
}
private fun computeCallerData(lambda: Any, wrapper: Any, arguments: List<Any?>, result: Any?): CallData {
val jClass = lambda.javaClass
val outerClass: Class<out Any?>? = jClass.getEnclosingClass()
// fields named "this" or "this$0"
val referenceToOuter = jClass.getAllDeclaredFields().firstOrNull {
field ->
field.getType() == outerClass && field.getName()!!.contains("this")
}
referenceToOuter?.setAccessible(true)
val outerInstance = referenceToOuter?.get(lambda)
val containingField = if (outerInstance == null) null
else outerClass?.getAllDeclaredFields()?.firstOrNull {
(field): Boolean ->
field.setAccessible(true)
val value = field.get(outerInstance)
if (value == null) return@firstOrNull false
val valueClass = value.javaClass
val functionField = valueClass.getAllDeclaredFields().firstOrNull {
it.getType()?.getName()?.startsWith("kotlin.Function") ?: false
}
if (functionField == null) return@firstOrNull false
functionField.setAccessible(true)
val functionValue = functionField.get(value)
functionValue == wrapper
}
val enclosingEntity = jClass.getEnclosingConstructor()
?: jClass.getEnclosingMethod()
?: jClass.getEnclosingClass()
return CallData(outerInstance, containingField, enclosingEntity as GenericDeclaration?, arguments, result)
}
private fun Class<*>.getAllDeclaredFields(): List<Field> {
val result = arrayListOf<Field>()
var c = this
while (true) {
result.addAll(c.getDeclaredFields().toList())
[suppress("UNCHECKED_CAST")]
val superClass = (c as Class<Any>).getSuperclass() as Class<Any>?
if (superClass == null) break
if (c == superClass) break
c = superClass
}
return result
}
override fun createMemoizedFunction<K, V: Any>(compute: (K) -> V): MemoizedFunctionToNotNull<K, V> {
return delegate.createMemoizedFunction(compute.logged)
}
override fun createMemoizedFunctionWithNullableValues<K, V: Any>(compute: (K) -> V?): MemoizedFunctionToNullable<K, V> {
return delegate.createMemoizedFunctionWithNullableValues(compute.logged)
}
override fun createLazyValue<T: Any>(computable: () -> T): NotNullLazyValue<T> {
return delegate.createLazyValue(computable.logged)
}
override fun createRecursionTolerantLazyValue<T: Any>(computable: () -> T, onRecursiveCall: T): NotNullLazyValue<T> {
return delegate.createRecursionTolerantLazyValue(computable.logged, onRecursiveCall)
}
override fun createLazyValueWithPostCompute<T: Any>(computable: () -> T, onRecursiveCall: ((Boolean) -> T)?, postCompute: (T) -> Unit): NotNullLazyValue<T> {
return delegate.createLazyValueWithPostCompute(computable.logged, onRecursiveCall, postCompute)
}
override fun createNullableLazyValue<T: Any>(computable: () -> T?): NullableLazyValue<T> {
return delegate.createNullableLazyValue(computable.logged)
}
override fun createRecursionTolerantNullableLazyValue<T: Any>(computable: () -> T?, onRecursiveCall: T?): NullableLazyValue<T> {
return delegate.createRecursionTolerantNullableLazyValue(computable.logged, onRecursiveCall)
}
override fun createNullableLazyValueWithPostCompute<T: Any>(computable: () -> T?, postCompute: (T?) -> Unit): NullableLazyValue<T> {
return delegate.createNullableLazyValueWithPostCompute(computable.logged, postCompute)
}
override fun compute<T>(computable: () -> T): T {
return delegate.compute(computable.logged)
}
}