a property test for psi-document consistency, failing miserably so far

This commit is contained in:
peter
2017-10-19 21:04:05 +02:00
parent 936fecbe41
commit 6b412f4c48
2 changed files with 126 additions and 0 deletions
+4
View File
@@ -212,6 +212,10 @@ public class Generator<T> {
return from(data -> data.drawInt());
}
public static Generator<Integer> naturals() {
return integers(0, Integer.MAX_VALUE);
}
/** Generates integers in the given range (both ends inclusive) */
public static Generator<Integer> integers(int min, int max) {
IntDistribution distribution = IntDistribution.uniform(min, max);
@@ -0,0 +1,122 @@
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.impl
import com.intellij.lang.ASTFactory
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileTypes.PlainTextLanguage
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.source.CharTableImpl
import com.intellij.psi.impl.source.DummyHolder
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil
import com.intellij.psi.impl.source.tree.*
import com.intellij.psi.tree.IElementType
import com.intellij.testFramework.LightPlatformTestCase
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
import jetCheck.Generator
import jetCheck.IntDistribution
import jetCheck.PropertyChecker
import one.util.streamex.IntStreamEx
/**
* @author peter
*/
abstract class PsiEventConsistencyTest : LightPlatformCodeInsightFixtureTestCase() {
fun testPsiDocSynchronization() {
PropertyChecker.forAll(commands()).shouldHold { cmd ->
val file = PsiFileFactory.getInstance(project).createFileFromText("a.txt", PlainTextLanguage.INSTANCE, "", true, false)
val document = file.viewProvider.document!!
WriteCommandAction.runWriteCommandAction(project) {
cmd.performChange(file)
assertEquals(document.text, file.text)
assertEquals(document.text, file.node.text)
}
true
}
}
private interface AstCommand {
fun performChange(file: PsiFile)
}
private data class CommandGroup(val inside: List<AstCommand>): AstCommand {
override fun performChange(file: PsiFile) {
ChangeUtil.prepareAndRunChangeAction(ChangeUtil.ChangeAction {
inside.forEach { it.performChange(file) }
}, file.node as FileElement)
}
}
private data class DeleteElement(val coord: NodeCoordinates): AstCommand {
override fun performChange(file: PsiFile) {
val node = coord.findNode(file)
node.treeParent?.deleteChildInternal(node)
}
}
private data class ReplaceElement(val coord: NodeCoordinates, val replacement: TreeElement): AstCommand {
override fun performChange(file: PsiFile) {
val node = coord.findNode(file)
node.treeParent?.replaceChild(node, replacement)
}
}
private data class AddElement(val coord: NodeCoordinates, val indexHint: Int, val toAdd : TreeElement): AstCommand {
override fun performChange(file: PsiFile) {
val composite = coord.findNode(file).let { it as? CompositeElement ?: it.treeParent }
val children = composite.getChildren(null)
val anchorIndex = indexHint % (children.size + 1)
composite.addChild(toAdd, if (anchorIndex == 0) null else children[anchorIndex - 1])
}
}
private data class NodeCoordinates(val offsetHint: Int, val depth: Int) {
fun findNode(file: PsiFile): TreeElement {
val node = file.node as FileElement
var elem : TreeElement = node.findLeafElementAt(offsetHint % (file.textLength + 1)) ?: return node
for (i in 0..depth) {
elem = elem.treeParent ?: break
}
return elem
}
}
private val genCoords = Generator.zipWith(Generator.naturals(), Generator.integers(0, 5), ::NodeCoordinates)
private fun commands(): Generator<AstCommand> = Generator.frequency(
1, Generator.from { CommandGroup(Generator.listsOf(IntDistribution.uniform(1, 5), commands()).generateValue(it)) },
5, genCoords.flatMap { coords ->
Generator.anyOf(
Generator.constant(DeleteElement(coords)),
nodes.map { ReplaceElement(coords, it) },
Generator.zipWith(nodes, Generator.naturals()) { n, i -> AddElement(coords, i, n) }
) })
private val leafTypes = IntStreamEx.range(1, 5).mapToObj { i -> IElementType("Leaf" + i, null) }.toList()
private val compositeTypes = IntStreamEx.range(1, 5).mapToObj { i -> IElementType("Composite" + i, null) }.toList()
private val leaves = Generator.zipWith(Generator.sampledFrom(leafTypes), Generator.asciiLetters()) { type, c ->
withDummyHolder(ASTFactory.leaf(type, c.toString())) }
private val composites : Generator<TreeElement> = Generator.sampledFrom(compositeTypes).flatMap { type ->
Generator.listsOf(IntDistribution.uniform(0, 5), nodes).map { children ->
val composite = withDummyHolder(object: CompositePsiElement(type) {
override fun toString(): String {
return super.toString() + children.toString()
}
})
children.forEach(composite::addChild)
composite
}
}
private val nodes = Generator.frequency(4, leaves, 2, composites)
private fun withDummyHolder(e: TreeElement): TreeElement {
DummyHolder(LightPlatformTestCase.getPsiManager(), e, null, CharTableImpl())
CodeEditUtil.setNodeGenerated(e, true)
return e
}
}