Skip to content

Commit bd65789

Browse files
committed
Improve test navigation
This change adds support for: * nesting tests (deep hierarchy) * direct navigation to failed block (cypher or cypher params) * let augmentation-tests provides a diff on failure that can be viewed in IntelliJ
1 parent 9187d22 commit bd65789

File tree

6 files changed

+308
-271
lines changed

6 files changed

+308
-271
lines changed

src/test/kotlin/org/neo4j/graphql/TranslatorExceptionTest.kt

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/test/kotlin/org/neo4j/graphql/TranslatorExceptionTests.kt

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,33 @@ package org.neo4j.graphql
22

33
import graphql.parser.InvalidSyntaxException
44
import org.junit.jupiter.api.Assertions
5-
import org.junit.jupiter.api.Test
6-
import org.neo4j.graphql.utils.CypherTestSuite
5+
import org.junit.jupiter.api.DynamicNode
6+
import org.junit.jupiter.api.DynamicTest
7+
import org.junit.jupiter.api.TestFactory
8+
import org.neo4j.graphql.utils.AsciiDocTestSuite
9+
import java.util.stream.Stream
710

8-
class TranslatorExceptionTests {
11+
class TranslatorExceptionTests : AsciiDocTestSuite("translator-tests1.adoc") {
912

10-
private val testSuite = CypherTestSuite("translator-tests1.adoc")
11-
12-
@Test
13-
fun unknownType() {
14-
// todo better test
15-
Assertions.assertThrows(IllegalArgumentException::class.java) {
16-
testSuite.translate(" { company { name } } ")
17-
}
13+
@TestFactory
14+
fun createTests(): Stream<DynamicNode> {
15+
return parse(linkedSetOf())
1816
}
1917

20-
@Test
21-
fun mutation() {
22-
Assertions.assertThrows(InvalidSyntaxException::class.java) {
23-
testSuite.translate(" { createPerson() } ")
24-
}
18+
override fun schemaTestFactory(schema: String): List<DynamicNode> {
19+
val translator = Translator(SchemaBuilder.buildSchema(schema));
20+
return listOf(
21+
DynamicTest.dynamicTest("unknownType") {
22+
Assertions.assertThrows(IllegalArgumentException::class.java) {
23+
translator.translate(" { company { name } } ")
24+
}
25+
},
26+
DynamicTest.dynamicTest("mutation") {
27+
Assertions.assertThrows(InvalidSyntaxException::class.java) {
28+
translator.translate(" { createPerson() } ")
29+
}
30+
}
31+
32+
)
2533
}
26-
}
34+
}

src/test/kotlin/org/neo4j/graphql/utils/AsciiDocTestSuite.kt

Lines changed: 124 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,145 @@
11
package org.neo4j.graphql.utils
22

33
import org.codehaus.jackson.map.ObjectMapper
4+
import org.junit.jupiter.api.DynamicContainer
5+
import org.junit.jupiter.api.DynamicNode
46
import java.io.File
7+
import java.net.URI
8+
import java.util.regex.Pattern
9+
import java.util.stream.Stream
10+
import javax.ws.rs.core.UriBuilder
511

6-
open class AsciiDocTestSuite {
7-
class ParsedFile(
8-
var schema: String,
9-
var file: File,
10-
val tests: MutableList<ParsedBlock>
11-
)
12+
open class AsciiDocTestSuite(private val fileName: String) {
1213

1314
class ParsedBlock(
14-
var title: String? = null,
15-
var line: Int = 0,
16-
var ignore: Boolean = false,
17-
val codeBlocks: MutableMap<String, StringBuilder> = mutableMapOf()
15+
val uri: URI,
16+
val code: StringBuilder
1817
)
1918

20-
companion object {
21-
val MAPPER = ObjectMapper()
19+
fun parse(blocks: LinkedHashSet<String>): Stream<DynamicNode> {
20+
val file = File(AsciiDocTestSuite::class.java.getResource("/$fileName").toURI())
21+
val srcLocation = File("src/test/resources/", fileName).toURI()
22+
var root: DocumentLevel? = null
23+
var currentDocumentLevel: DocumentLevel? = null
24+
25+
val lines = file.readLines()
26+
val terminatorElement = blocks.lastOrNull()
27+
28+
var schema: String? = null
29+
var title: String? = null
30+
var current: StringBuilder? = null
31+
32+
var codeBlocks = mutableMapOf<String, ParsedBlock>()
33+
var ignore = false
34+
var inside = false
35+
36+
var currentDepth = 0
2237

23-
fun parse(fileName: String, blocks: LinkedHashSet<String>): ParsedFile {
24-
val file = File(AsciiDocTestSuite::class.java.getResource("/$fileName").toURI())
25-
val lines = file.readLines()
26-
val terminatorElement = blocks.last()
27-
val tests: MutableList<ParsedBlock> = mutableListOf()
28-
29-
var schema: String? = null
30-
var lastTitle: String? = null
31-
var titleCount = 1
32-
var current: StringBuilder? = null
33-
34-
var testSet = ParsedBlock()
35-
var inside = false
36-
for ((lineNr, line) in lines.withIndex()) {
37-
if (line.startsWith("#") || line.startsWith("//")) {
38-
continue
38+
loop@ for ((lineNr, line) in lines.withIndex()) {
39+
if (line.startsWith("#") || line.startsWith("//")) {
40+
continue
41+
}
42+
val headlineMatcher = HEADLINE_PATTERN.matcher(line)
43+
when {
44+
line == "[source,graphql,schema=true]" -> schema = ""
45+
blocks.contains(line) -> {
46+
current = StringBuilder()
47+
codeBlocks[line] = ParsedBlock(
48+
UriBuilder.fromUri(srcLocation).queryParam("line", lineNr + 1).build(),
49+
current
50+
)
3951
}
40-
when {
41-
line == "[source,graphql,schema=true]" -> schema = ""
42-
blocks.contains(line) -> {
43-
current = StringBuilder()
44-
testSet.codeBlocks[line] = current
52+
line == "----" -> {
53+
if (schema?.isNotBlank() == true && current == null) {
54+
val tests = schemaTestFactory(schema)
55+
currentDocumentLevel?.tests?.add(tests)
56+
if (terminatorElement == null) {
57+
break@loop
58+
}
4559
}
46-
line == "----" -> {
47-
if (testSet.codeBlocks[terminatorElement]?.isNotEmpty() == true) {
48-
tests.add(testSet)
49-
if (testSet.title == null) {
50-
testSet.title = lastTitle + " " + ++titleCount
51-
} else {
52-
titleCount = 1
53-
lastTitle = testSet.title
54-
}
55-
testSet = ParsedBlock()
60+
if (codeBlocks[terminatorElement]?.code?.isNotEmpty() == true) {
61+
val tests = testFactory(
62+
title ?: throw IllegalStateException("Title should be defined (line $lineNr)"),
63+
schema ?: throw IllegalStateException("Schema should be defined"),
64+
codeBlocks,
65+
ignore)
66+
currentDocumentLevel?.tests?.add(tests)
67+
codeBlocks = mutableMapOf()
68+
}
69+
inside = !inside
70+
}
71+
headlineMatcher.matches() -> {
72+
val uri = UriBuilder.fromUri(srcLocation).queryParam("line", lineNr + 1).build()
73+
val depth = headlineMatcher.group(1).length
74+
title = headlineMatcher.group(2)
75+
if (root == null) {
76+
root = DocumentLevel(null, title, uri)
77+
currentDocumentLevel = root
78+
} else {
79+
val parent = when {
80+
depth > currentDepth -> currentDocumentLevel
81+
depth == currentDepth -> currentDocumentLevel?.parent
82+
?: throw IllegalStateException("cannot create sub-level on null")
83+
else -> currentDocumentLevel?.parent?.parent
84+
?: throw IllegalStateException("cannot create sub-level on null")
5685
}
57-
inside = !inside
86+
currentDocumentLevel = DocumentLevel(parent, title, uri)
5887
}
59-
line.startsWith("=== ") -> {
60-
testSet.title = line.substring(4)
61-
testSet.line = lineNr + 1
88+
currentDepth = depth
89+
}
90+
line.startsWith("CAUTION:") -> ignore = true
91+
inside -> when {
92+
current != null -> current.append(line).append("\n")
93+
schema != null -> schema += line + "\n"
94+
}
95+
}
96+
}
97+
98+
return root?.generateTests() ?: Stream.empty()
99+
}
100+
101+
open fun testFactory(title: String, schema: String, codeBlocks: Map<String, ParsedBlock>, ignore: Boolean): List<DynamicNode> {
102+
return emptyList()
103+
}
104+
105+
open fun schemaTestFactory(schema: String): List<DynamicNode> {
106+
return emptyList()
107+
}
108+
109+
companion object {
110+
val MAPPER = ObjectMapper()
111+
val HEADLINE_PATTERN: Pattern = Pattern.compile("^(=+) (.*)$")
112+
113+
class DocumentLevel(
114+
val parent: DocumentLevel?,
115+
val name: String,
116+
private val testSourceUri: URI
117+
) {
118+
private val children = mutableListOf<DocumentLevel>()
119+
val tests = mutableListOf<List<DynamicNode>>()
120+
121+
init {
122+
parent?.children?.add(this)
123+
}
124+
125+
fun generateTests(): Stream<DynamicNode> {
126+
val streamBuilder = Stream.builder<DynamicNode>()
127+
if (tests.size > 0) {
128+
if (children.isNotEmpty()) {
129+
streamBuilder.add(DynamicContainer.dynamicContainer(name, testSourceUri, children.stream().flatMap { it.generateTests() }))
62130
}
63-
line.startsWith("CAUTION:") -> testSet.ignore = true
64-
inside -> when {
65-
current != null -> current.append(line).append("\n")
66-
schema != null -> schema += line + "\n"
131+
for ((index, test) in tests.withIndex()) {
132+
streamBuilder.add(DynamicContainer.dynamicContainer(name + " " + (index + 1), testSourceUri, test.stream()))
67133
}
134+
} else {
135+
val nodes = Stream.concat(
136+
tests.stream().flatMap { it.stream() },
137+
children.stream().flatMap { it.generateTests() }
138+
)
139+
streamBuilder.add(DynamicContainer.dynamicContainer(name, testSourceUri, nodes))
68140
}
141+
return streamBuilder.build()
69142
}
70-
return ParsedFile(schema ?: throw IllegalStateException("no schema found"),
71-
File("src/test/resources/$fileName").absoluteFile, tests)
72143
}
73144

74145
private fun fixNumber(v: Any?): Any? = when (v) {

0 commit comments

Comments
 (0)