Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add unittest management context #29

Merged
merged 24 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@

## 1.0.2 - 2024-10-12
- Bugfix: change plugin name introduce a bug in the code.

## 1.0.3 - 2024-11-14
- Add unit test annotation generation action.
- Add unit test annotation inspection.
9 changes: 7 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
alias(libs.plugins.changelog) // Gradle Changelog Plugin
alias(libs.plugins.qodana) // Gradle Qodana Plugin
alias(libs.plugins.kover) // Gradle Kover Plugin
alias(libs.plugins.serialize) // Gradle Serializer Plugin
}

group = providers.gradleProperty("pluginGroup").get()
Expand All @@ -29,7 +30,10 @@ intellij {
type.set(providers.gradleProperty("platformType"))

// Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file.
plugins.set(providers.gradleProperty("platformBundledPlugins").map { it.split(',').map(String::trim).filter(String::isNotEmpty) })
plugins.set(providers.gradleProperty("platformBundledPlugins")
.map { it.split(',').map(String::trim).filter(String::isNotEmpty) }
.map { it + listOf("git4idea") }
)
}

// Dependencies are managed with Gradle version catalog - read more: https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog
Expand All @@ -41,6 +45,7 @@ dependencies {
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.18.0")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.18.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") // Use latest version
}

// Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin
Expand Down Expand Up @@ -124,6 +129,6 @@ tasks {
// The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3
// Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more:
// https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel
channels = providers.gradleProperty("pluginVersion").map { listOf(it.split('-').getOrElse(1) { "default" }.split('.').first()) }
channels = providers.gradleProperty("pluginVersion").map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) }
}
}
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup = com.github.jaksonlin.pitestintellij
pluginName = pitest-gradle
pluginRepositoryUrl = https://github.com/jaksonlin/pitest-gradle
# SemVer format -> https://semver.org
pluginVersion = 1.0.2
pluginVersion = 1.0.3

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 222
Expand All @@ -18,7 +18,7 @@ platformVersion = 2022.2
# Example: platformPlugins = com.jetbrains.php:203.4449.22, org.intellij.scala:2023.3.27@EAP
platformPlugins =
# Example: platformBundledPlugins = com.intellij.java
platformBundledPlugins = com.intellij.java, com.intellij.gradle
platformBundledPlugins = com.intellij.java, com.intellij.gradle,com.intellij.java,git4idea

# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 8.9
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleInt
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" }
serialize = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.jaksonlin.pitestintellij.actions

import com.github.jaksonlin.pitestintellij.commands.unittestannotations.GenerateAnnotationCommand
import com.github.jaksonlin.pitestintellij.context.CaseCheckContext
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil


class GenerateAnnotationCommandAction : AnAction() {

override fun actionPerformed(e: AnActionEvent) {
val psiMethodInfo = findMethodAtCaret(e) ?: return
val context = CaseCheckContext.create(psiMethodInfo.first, psiMethodInfo.second)
GenerateAnnotationCommand(e.project!!, context).execute()
}

private fun findMethodAtCaret(e: AnActionEvent): Pair<PsiMethod, PsiClass>? {
val project = e.project ?: return null
val editor = e.dataContext.getData(CommonDataKeys.EDITOR) ?: return null
val caret = e.dataContext.getData(CommonDataKeys.CARET) ?: return null
val elementAtCaret = PsiDocumentManager.getInstance(project)
.getPsiFile(editor.document)?.findElementAt(caret.offset) ?: return null
val method = PsiTreeUtil.getParentOfType(elementAtCaret, PsiMethod::class.java) ?: return null
val containingClass = PsiTreeUtil.getParentOfType(method, PsiClass::class.java) ?: return null
return method to containingClass
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.github.jaksonlin.pitestintellij.actions

import com.github.jaksonlin.pitestintellij.commands.unittestannotations.CheckAnnotationCommand
import com.github.jaksonlin.pitestintellij.context.CaseCheckContext
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil

class RunCaseAnnoationCheckAction : AnAction() {

override fun actionPerformed(e: AnActionEvent) {
val psiMethodInfo = findMethodAtCaret(e) ?: return
val context = CaseCheckContext.create(psiMethodInfo.first, psiMethodInfo.second)
CheckAnnotationCommand(e.project!!, context).execute()
}

private fun findMethodAtCaret(e: AnActionEvent): Pair<PsiMethod, PsiClass>? {
val project = e.project ?: return null
val editor = e.dataContext.getData(CommonDataKeys.EDITOR) ?: return null
val caret = e.dataContext.getData(CommonDataKeys.CARET) ?: return null
val elementAtCaret = PsiDocumentManager.getInstance(project)
.getPsiFile(editor.document)?.findElementAt(caret.offset) ?: return null
val method = PsiTreeUtil.getParentOfType(elementAtCaret, PsiMethod::class.java) ?: return null
val containingClass = PsiTreeUtil.getParentOfType(method, PsiClass::class.java) ?: return null
return method to containingClass
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.github.jaksonlin.pitestintellij.actions

import com.github.jaksonlin.pitestintellij.commands.unittestannotations.CheckAnnotationCommand
import com.github.jaksonlin.pitestintellij.context.CaseCheckContext
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.psi.JavaRecursiveElementVisitor
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil

class RunTestFileAnnoationCheckAction: AnAction() {

override fun actionPerformed(e: AnActionEvent) {
batchCheckAnnotation(e)
}

private fun batchCheckAnnotation(e: AnActionEvent){
val psiFile = e.dataContext.getData(CommonDataKeys.PSI_FILE)
val psiJavaFile = psiFile as PsiJavaFile

psiJavaFile.accept(object : JavaRecursiveElementVisitor() {
override fun visitMethod(method: PsiMethod) {
super.visitMethod(method)
// inspect the method annotations
val annotations = method.annotations
for (annotation in annotations) {
// inspect the annotation
val annotationName = annotation.qualifiedName
if (annotationName!=null && // to support junit 4 & 5, do not use regexp, as it will also match some beforeTest/afterTest annotations
(annotationName == "org.junit.Test" || annotationName == "org.junit.jupiter.api.Test" || annotationName == "Test")) {
val psiClass = PsiTreeUtil.getParentOfType(method, PsiClass::class.java) ?: return
val context = CaseCheckContext.create(method, psiClass)
CheckAnnotationCommand(e.project!!, context).execute()
break
}
}
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.jaksonlin.pitestintellij.annotations

import kotlinx.serialization.Contextual
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonClassDiscriminator

@Serializable
sealed class DefaultValue {
@Serializable
@SerialName("StringValue")
data class StringValue(val value: String) : DefaultValue()

@Serializable
@SerialName("StringListValue")
data class StringListValue(val value: List<String>) : DefaultValue()

@Serializable
@SerialName("NullValue")
object NullValue : DefaultValue()
}


@Serializable
data class AnnotationFieldConfig(
val name: String,
val type: AnnotationFieldType,
val required: Boolean = false,
val defaultValue: DefaultValue = DefaultValue.NullValue,
val validation: FieldValidation? = null,
val valueProvider: ValueProvider? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.github.jaksonlin.pitestintellij.annotations

import kotlinx.serialization.Serializable

@Serializable
enum class AnnotationFieldType {
STRING,
STRING_LIST
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.github.jaksonlin.pitestintellij.annotations

import com.github.jaksonlin.pitestintellij.context.UnittestCase


class AnnotationParser(private val schema: AnnotationSchema) {
private val validator = AnnotationValidator(schema)

fun parseAnnotation(annotationValues: Map<String, Any>): UnittestCase {
when (val result = validator.validate(annotationValues)) {
is AnnotationValidator.ValidationResult.Valid -> {
val parsedValues = schema.fields.associate { field ->
if (field.required) {
if (!annotationValues.containsKey(field.name)) {
throw IllegalArgumentException("Missing required field: ${field.name}")
}
if (annotationValues[field.name] == null) {
throw IllegalArgumentException("Required field cannot be null: ${field.name}")
}
} else {
if (!annotationValues.containsKey(field.name) || annotationValues[field.name] == null) {
return@associate field.name to field.defaultValue
}
}
val rawValue = annotationValues[field.name]
field.name to convertValue(rawValue, field)
}
return UnittestCase(parsedValues)
}
is AnnotationValidator.ValidationResult.Invalid -> {
throw IllegalArgumentException(
"Invalid annotation values:\n${result.errors.joinToString("\n")}"
)
}
}
}

private fun convertValue(value: Any?, field: AnnotationFieldConfig): Any? {
if (value == null) {
return when (val defaultValue = field.defaultValue) {
is DefaultValue.StringValue -> defaultValue.value
is DefaultValue.StringListValue -> defaultValue.value
DefaultValue.NullValue -> null
}
}

return when (field.type) {
AnnotationFieldType.STRING -> value as? String ?: field.defaultValue
AnnotationFieldType.STRING_LIST -> (value as? List<*>)?.mapNotNull { it as? String } ?: emptyList<String>()
}
}
}
Loading
Loading