Skip to content

Commit 7c95c43

Browse files
authored
Introduce custom JavaDoc tags #565 (#585)
* Introduce custom plugin's JavaDoc tags #565 * Render UtBot custom JavaDoc tags correctly #565 * Add an option to generate summaries using custom JavaDoc tags #565 * Fill value of utbot.iterates tag #565 * Collect info about Invoke, Iterate, and Return sections #565 * Review fixes * Add unit tests for summaries with custom JavaDoc tags #565 * Fix after rebasing * Add summary tests for MinStack #565 * Fix broken tests * Add <pre> tag only in case when custom javadoc tags are not used * Use a full exception name instead of simple name to build inline link correctly * Minor refactoring * Minor refactoring: avoid code duplication * Add DocCustomTagStatement and CgCustomTagStatement * Refactored code to avoid code duplication * Fix tests: add full name for classes * Add JUnit extension to control USE_CUSTOM_TAGS setting * Move useCustomJavaDocTags to UtSettings, make useFuzzing true * Remove unused import and fix broken tests * Fix broken tests * Add comments, remove unused method * Review fixes: fixed formatting, removed redundant types * Review fixes: fixed formatting, removed useless overriding methods * Review fixes: extracted method, polished code * fix after rebasing * fix after rebasing * review fixes * fix rendering after updating to idea 2022.1. now we don't need to generate comment content with HTML tags, we need only replace custom tags' names with their messages to make it look nice
1 parent 2f8b870 commit 7c95c43

File tree

20 files changed

+683
-30
lines changed

20 files changed

+683
-30
lines changed

utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ object UtSettings {
183183
var testName by getBooleanProperty(true)
184184
var testDisplayName by getBooleanProperty(true)
185185

186+
/**
187+
* Generate summaries using plugin's custom JavaDoc tags.
188+
*/
189+
var useCustomJavaDocTags by getBooleanProperty(false)
190+
186191
/**
187192
* Enable the machine learning module to generate summaries for methods under test.
188193
* True by default.

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,9 @@ class DocPreTagStatement(content: List<DocStatement>) : DocTagStatement(content)
12761276
override fun hashCode(): Int = content.hashCode()
12771277
}
12781278

1279+
data class DocCustomTagStatement(val statements: List<DocStatement>) : DocTagStatement(statements) {
1280+
override fun toString(): String = content.joinToString(separator = "")
1281+
}
12791282

12801283
open class DocClassLinkStmt(val className: String) : DocStatement() {
12811284
override fun toString(): String = className

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.utbot.framework.plugin.api.ClassId
1111
import org.utbot.framework.plugin.api.ConstructorId
1212
import org.utbot.framework.plugin.api.DocClassLinkStmt
1313
import org.utbot.framework.plugin.api.DocCodeStmt
14+
import org.utbot.framework.plugin.api.DocCustomTagStatement
1415
import org.utbot.framework.plugin.api.DocMethodLinkStmt
1516
import org.utbot.framework.plugin.api.DocPreTagStatement
1617
import org.utbot.framework.plugin.api.DocRegularStmt
@@ -52,6 +53,7 @@ interface CgElement {
5253
is CgMultilineComment -> visit(element)
5354
is CgDocumentationComment -> visit(element)
5455
is CgDocPreTagStatement -> visit(element)
56+
is CgCustomTagStatement -> visit(element)
5557
is CgDocCodeStmt -> visit(element)
5658
is CgDocRegularStmt -> visit(element)
5759
is CgDocClassLinkStmt -> visit(element)
@@ -335,6 +337,11 @@ class CgDocPreTagStatement(content: List<CgDocStatement>) : CgDocTagStatement(co
335337
override fun hashCode(): Int = content.hashCode()
336338
}
337339

340+
/**
341+
* Represents a type for statements containing custom JavaDoc tags.
342+
*/
343+
data class CgCustomTagStatement(val statements: List<CgDocStatement>) : CgDocTagStatement(statements)
344+
338345
class CgDocCodeStmt(val stmt: String) : CgDocStatement() {
339346
override fun isEmpty(): Boolean = stmt.isEmpty()
340347

@@ -379,6 +386,10 @@ fun convertDocToCg(stmt: DocStatement): CgDocStatement {
379386
val stmts = stmt.content.map { convertDocToCg(it) }
380387
CgDocPreTagStatement(content = stmts)
381388
}
389+
is DocCustomTagStatement -> {
390+
val stmts = stmt.content.map { convertDocToCg(it) }
391+
CgCustomTagStatement(statements = stmts)
392+
}
382393
is DocRegularStmt -> CgDocRegularStmt(stmt = stmt.stmt)
383394
is DocClassLinkStmt -> CgDocClassLinkStmt(className = stmt.className)
384395
is DocMethodLinkStmt -> CgDocMethodLinkStmt(methodName = stmt.methodName, stmt = stmt.className)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.utbot.framework.codegen.model.tree.CgComment
1616
import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation
1717
import org.utbot.framework.codegen.model.tree.CgComparison
1818
import org.utbot.framework.codegen.model.tree.CgContinueStatement
19+
import org.utbot.framework.codegen.model.tree.CgCustomTagStatement
1920
import org.utbot.framework.codegen.model.tree.CgDeclaration
2021
import org.utbot.framework.codegen.model.tree.CgDecrement
2122
import org.utbot.framework.codegen.model.tree.CgDoWhileLoop
@@ -309,11 +310,19 @@ internal abstract class CgAbstractRenderer(val context: CgContext, val printer:
309310
}
310311
override fun visit(element: CgDocPreTagStatement) {
311312
if (element.content.all { it.isEmpty() }) return
312-
313313
println("<pre>")
314314
for (stmt in element.content) stmt.accept(this)
315315
println("</pre>")
316316
}
317+
318+
override fun visit(element: CgCustomTagStatement) {
319+
if (element.statements.all { it.isEmpty() }) return
320+
321+
for (stmt in element.statements) {
322+
stmt.accept(this)
323+
}
324+
}
325+
317326
override fun visit(element: CgDocCodeStmt) {
318327
if (element.isEmpty()) return
319328

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.utbot.framework.codegen.model.tree.CgDocClassLinkStmt
2222
import org.utbot.framework.codegen.model.tree.CgDocCodeStmt
2323
import org.utbot.framework.codegen.model.tree.CgDocMethodLinkStmt
2424
import org.utbot.framework.codegen.model.tree.CgDocPreTagStatement
25+
import org.utbot.framework.codegen.model.tree.CgCustomTagStatement
2526
import org.utbot.framework.codegen.model.tree.CgDocRegularStmt
2627
import org.utbot.framework.codegen.model.tree.CgDocumentationComment
2728
import org.utbot.framework.codegen.model.tree.CgElement
@@ -122,6 +123,7 @@ interface CgVisitor<R> {
122123

123124
// Comment statements
124125
fun visit(element: CgDocPreTagStatement): R
126+
fun visit(element: CgCustomTagStatement): R
125127
fun visit(element: CgDocCodeStmt): R
126128
fun visit(element: CgDocRegularStmt): R
127129
fun visit(element: CgDocClassLinkStmt): R

utbot-framework/src/test/kotlin/org/utbot/examples/UtValueTestCaseChecker.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.utbot.framework.coverage.toAtLeast
3131
import org.utbot.framework.plugin.api.CodegenLanguage
3232
import org.utbot.framework.plugin.api.DocClassLinkStmt
3333
import org.utbot.framework.plugin.api.DocCodeStmt
34+
import org.utbot.framework.plugin.api.DocCustomTagStatement
3435
import org.utbot.framework.plugin.api.DocMethodLinkStmt
3536
import org.utbot.framework.plugin.api.DocPreTagStatement
3637
import org.utbot.framework.plugin.api.DocRegularStmt
@@ -2746,6 +2747,7 @@ private fun flattenDocStatements(summary: List<DocStatement>): List<DocStatement
27462747
is DocMethodLinkStmt -> flatten.add(s)
27472748
is DocCodeStmt -> flatten.add(s)
27482749
is DocRegularStmt -> flatten.add(s)
2750+
is DocCustomTagStatement -> flatten.add(s)
27492751
}
27502752
}
27512753
return flatten
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.utbot.intellij.plugin.javadoc
2+
3+
import com.intellij.psi.PsiElement
4+
import com.intellij.psi.PsiMethod
5+
import com.intellij.psi.PsiReference
6+
import com.intellij.psi.javadoc.CustomJavadocTagProvider
7+
import com.intellij.psi.javadoc.JavadocTagInfo
8+
import com.intellij.psi.javadoc.PsiDocTagValue
9+
import org.utbot.summary.comment.CustomJavaDocTag
10+
import org.utbot.summary.comment.CustomJavaDocTagProvider
11+
12+
/**
13+
* Provides plugin's custom JavaDoc tags to make test summaries structured.
14+
*/
15+
class UtCustomJavaDocTagProvider : CustomJavadocTagProvider {
16+
override fun getSupportedTags(): List<UtCustomTagInfo> =
17+
CustomJavaDocTagProvider().getPluginCustomTags().map { UtCustomTagInfo(it) }
18+
19+
class UtCustomTagInfo(private val tag: CustomJavaDocTag) : JavadocTagInfo {
20+
override fun getName(): String = tag.name
21+
22+
fun getMessage(): String = tag.message
23+
24+
override fun isInline() = false
25+
26+
override fun checkTagValue(value: PsiDocTagValue?): String? = null
27+
28+
override fun getReference(value: PsiDocTagValue?): PsiReference? = null
29+
30+
override fun isValidInContext(element: PsiElement?): Boolean = element is PsiMethod
31+
}
32+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.utbot.intellij.plugin.javadoc
2+
3+
import com.intellij.codeInsight.javadoc.JavaDocExternalFilter
4+
import com.intellij.codeInsight.javadoc.JavaDocInfoGeneratorFactory
5+
import com.intellij.lang.java.JavaDocumentationProvider
6+
import com.intellij.psi.PsiDocCommentBase
7+
import com.intellij.psi.PsiJavaDocumentedElement
8+
9+
/**
10+
* To render UtBot custom JavaDoc tags messages, we need to override basic behaviour of [JavaDocumentationProvider].
11+
* The IJ platform knows only custom tag names, so we need to add their messages in rendered comments to make it look nice.
12+
*/
13+
class UtDocumentationProvider : JavaDocumentationProvider() {
14+
override fun generateRenderedDoc(comment: PsiDocCommentBase): String? {
15+
val target = comment.owner ?: comment
16+
17+
if (target !is PsiJavaDocumentedElement) {
18+
return ""
19+
}
20+
21+
val baseJavaDocInfoGenerator = JavaDocInfoGeneratorFactory.getBuilder(target.getProject())
22+
.setPsiElement(target)
23+
.setIsGenerationForRenderedDoc(true)
24+
.create()
25+
26+
val finalDocContent = replaceTagNamesWithMessages(baseJavaDocInfoGenerator.generateRenderedDocInfo())
27+
28+
return JavaDocExternalFilter.filterInternalDocInfo(finalDocContent)
29+
}
30+
31+
/**
32+
* Replaces names of plugin's custom JavaDoc tags with their messages in the comment generated by the IJ platform.
33+
* Example: utbot.MethodUnderTest -> Method under test.
34+
*/
35+
private fun replaceTagNamesWithMessages(comment: String?) =
36+
comment?.let {
37+
val docTagProvider = UtCustomJavaDocTagProvider()
38+
docTagProvider.supportedTags.fold(it) { result, tag ->
39+
if (result.contains(tag.name)) {
40+
result.replace(tag.name, "${tag.getMessage()}:")
41+
} else {
42+
result
43+
}
44+
}
45+
} ?: ""
46+
}

utbot-intellij/src/main/resources/META-INF/plugin.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
<registryKey defaultValue="false" description="Enable editing Kotlin test files" key="kotlin.ultra.light.classes.empty.text.range"/>
3535
<postStartupActivity implementation="org.utbot.intellij.plugin.ui.GotItTooltipActivity"/>
3636
<projectModelModifier implementation="org.utbot.intellij.plugin.util.UtProjectModelModifier"/>
37+
<!--Documentation-->
38+
<customJavadocTagProvider implementation="org.utbot.intellij.plugin.javadoc.UtCustomJavaDocTagProvider"/>
39+
<lang.documentationProvider language="JAVA" order="first" implementationClass="org.utbot.intellij.plugin.javadoc.UtDocumentationProvider"/>
3740
</extensions>
3841

3942
<!-- Minimum and maximum build of IDE compatible with the plugin -->
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package examples
2+
3+
import org.junit.jupiter.api.extension.AfterEachCallback
4+
import org.junit.jupiter.api.extension.BeforeEachCallback
5+
import org.junit.jupiter.api.extension.ExtensionContext
6+
import org.utbot.framework.UtSettings
7+
8+
/**
9+
* Controls the value of useCustomJavaDocTags global variable.
10+
*
11+
* Should be used in summary tests containing custom JavaDoc tags.
12+
* To use it, add an annotation @ExtendWith(CustomJavaDocTagsEnabler::class) under test class.
13+
*/
14+
class CustomJavaDocTagsEnabler(private val enable: Boolean = true) : BeforeEachCallback, AfterEachCallback {
15+
private var previousValue = false
16+
17+
override fun beforeEach(context: ExtensionContext?) {
18+
previousValue = UtSettings.useCustomJavaDocTags
19+
UtSettings.useCustomJavaDocTags = enable
20+
}
21+
22+
override fun afterEach(context: ExtensionContext?) {
23+
UtSettings.useCustomJavaDocTags = previousValue
24+
}
25+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package examples.controlflow
2+
3+
import examples.CustomJavaDocTagsEnabler
4+
import examples.SummaryTestCaseGeneratorTest
5+
import org.junit.jupiter.api.Test
6+
import org.junit.jupiter.api.extension.ExtendWith
7+
import org.utbot.examples.DoNotCalculate
8+
import org.utbot.examples.controlflow.Conditions
9+
import org.utbot.framework.plugin.api.MockStrategyApi
10+
11+
@ExtendWith(CustomJavaDocTagsEnabler::class)
12+
class SummaryConditionsTest : SummaryTestCaseGeneratorTest(
13+
Conditions::class
14+
) {
15+
@Test
16+
fun testSimpleCondition() {
17+
val summary1 = "@utbot.classUnderTest {@link Conditions}\n" +
18+
"@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#simpleCondition(boolean)}\n" +
19+
"@utbot.executesCondition {@code (condition): False}\n" +
20+
"@utbot.returnsFrom {@code return 0;}"
21+
22+
val summary2 = "@utbot.classUnderTest {@link Conditions}\n" +
23+
"@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#simpleCondition(boolean)}\n" +
24+
"@utbot.executesCondition {@code (condition): True}\n" +
25+
"@utbot.returnsFrom {@code return 1;}"
26+
27+
val methodName1 = "testSimpleCondition_NotCondition"
28+
val methodName2 = "testSimpleCondition_Condition"
29+
30+
val displayName1 = "condition : False -> return 0"
31+
val displayName2 = "condition : True -> return 1"
32+
33+
val summaryKeys = listOf(
34+
summary1,
35+
summary2
36+
)
37+
38+
val displayNames = listOf(
39+
displayName1,
40+
displayName2
41+
)
42+
43+
val methodNames = listOf(
44+
methodName1,
45+
methodName2
46+
)
47+
48+
val method = Conditions::simpleCondition
49+
val mockStrategy = MockStrategyApi.NO_MOCKS
50+
val coverage = DoNotCalculate
51+
52+
summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames)
53+
}
54+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package examples.exceptions
2+
3+
import examples.CustomJavaDocTagsEnabler
4+
import examples.SummaryTestCaseGeneratorTest
5+
import org.junit.jupiter.api.Test
6+
import org.junit.jupiter.api.extension.ExtendWith
7+
import org.utbot.examples.DoNotCalculate
8+
import org.utbot.examples.exceptions.ExceptionClusteringExamples
9+
import org.utbot.framework.plugin.api.MockStrategyApi
10+
11+
@ExtendWith(CustomJavaDocTagsEnabler::class)
12+
class SummaryExceptionClusteringExamplesTest : SummaryTestCaseGeneratorTest(
13+
ExceptionClusteringExamples::class
14+
) {
15+
@Test
16+
fun testDifferentExceptions() {
17+
val summary1 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" +
18+
"@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" +
19+
"@utbot.executesCondition {@code (i == 0): True}\n" +
20+
"@utbot.throwsException {@link java.lang.ArithmeticException} in: return 100 / i;"
21+
22+
val summary2 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" +
23+
"@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" +
24+
"@utbot.executesCondition {@code (i == 0): False},\n" +
25+
"{@code (i == 1): True}\n" +
26+
"@utbot.throwsException {@link org.utbot.examples.exceptions.MyCheckedException} after condition: i == 1"
27+
val summary3 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" +
28+
"@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" +
29+
"@utbot.executesCondition {@code (i == 0): False},\n" +
30+
"{@code (i == 1): False},\n" +
31+
"{@code (i == 2): True}\n" +
32+
"@utbot.throwsException {@link java.lang.IllegalArgumentException} after condition: i == 2"
33+
val summary4 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" +
34+
"@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" +
35+
"@utbot.executesCondition {@code (i == 0): False},\n" +
36+
"{@code (i == 1): False},\n" +
37+
"{@code (i == 2): False}\n" +
38+
"@utbot.returnsFrom {@code return i * 2;}\n"
39+
40+
val methodName1 = "testDifferentExceptions_IEqualsZero"
41+
val methodName2 = "testDifferentExceptions_IEquals1"
42+
val methodName3 = "testDifferentExceptions_IEquals2"
43+
val methodName4 = "testDifferentExceptions_INotEquals2"
44+
45+
val displayName1 = "return 100 / i : True -> ThrowArithmeticException"
46+
val displayName2 = "i == 1 -> ThrowMyCheckedException"
47+
val displayName3 = "i == 2 -> ThrowIllegalArgumentException"
48+
val displayName4 = "i == 0 : False -> return i * 2"
49+
50+
val summaryKeys = listOf(
51+
summary1,
52+
summary2,
53+
summary3,
54+
summary4
55+
)
56+
57+
val displayNames = listOf(
58+
displayName1,
59+
displayName2,
60+
displayName3,
61+
displayName4
62+
)
63+
64+
val methodNames = listOf(
65+
methodName1,
66+
methodName2,
67+
methodName3,
68+
methodName4
69+
)
70+
71+
val method = ExceptionClusteringExamples::differentExceptions
72+
val mockStrategy = MockStrategyApi.NO_MOCKS
73+
val coverage = DoNotCalculate
74+
75+
summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames)
76+
}
77+
}

0 commit comments

Comments
 (0)