Skip to content

Commit

Permalink
Merge branch 'Feature/103-Error-message-if-companion-Gradle-plugin-fo…
Browse files Browse the repository at this point in the history
…r-MutationMate-is-missing' of github.com:amosproj/amos2023ws02-pitest-ide-plugin into Feature/103-Error-message-if-companion-Gradle-plugin-for-MutationMate-is-missing
  • Loading branch information
nikomall34 committed Dec 13, 2023
2 parents 4b011b6 + 34a57e1 commit 99d2b66
Show file tree
Hide file tree
Showing 19 changed files with 427 additions and 56 deletions.
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ assignees: ""
## Definition of done (DoD)

- All acceptance criteria are met.
- Work products are uploaded to the Github repository.
- Work products are uploaded to the GitHub repository.
- A pull request is created for each related branch.
- The work products in the pull requests are reviewed.
- Github CI Workflow passes for the branches
- GitHub CI Workflow passes for the branches
- The corresponding branches are merged and closed.
- The bill of materials section of the planning documents is updated.
- Tests are written for the added features, if suitable
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
# SPDX-License-Identifier: MIT

---
name: ktlint
name: super-linter

on:
pull_request:

jobs:
lint:
super-linter:
runs-on: ubuntu-latest

permissions:
Expand All @@ -31,3 +31,5 @@ jobs:
VALIDATE_ALL_CODEBASE: false
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VALIDATE_HTML: false
FILTER_REGEX_EXCLUDE: ".*src/test/resources.*"
Binary file added Deliverables/sprint-07/feature_board.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Deliverables/sprint-07/planning_documents.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package io.github.amosproj.pitmutationmate.override.communication

import io.github.amosproj.pitmutationmate.override.communicaiton.UdpMessagingService
import spock.lang.Specification
import spock.lang.Unroll

/**
* UdpMessagingServiceSpec
Expand All @@ -19,7 +18,7 @@ class UdpMessagingServiceSpec extends Specification {

def "Test UDP client sends messages correctly"() {
given:
def port = 50001
def port = findAvailablePort()
def client = new UdpMessagingService(port: port)
def mockServer = new MockUDPServer(port)
mockServer.startServer()
Expand Down Expand Up @@ -50,15 +49,32 @@ Duis eget erat ipsum. V""".trim()
when:
for (message in testMessages) {
client.sendMessage(message)
Thread.sleep(100)
}

then:
Thread.sleep(100)
mockServer.stopServer()
mockServer.thread.join()
for (message in expectedMessages) {
assert mockServer.receivedMessages.contains(message)
}
}

private int findAvailablePort() {
def minPortNumber = 49152
def maxPortNumber = 65535

for (port in minPortNumber..maxPortNumber) {
try {
def socket = new DatagramSocket(port)
socket.close()
return port
} catch (Exception ignored) {
// Port is not available, continue searching
}
}

throw new IllegalStateException("No available port found in the dynamic range")
}

}
6 changes: 4 additions & 2 deletions pitmutationmate/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ dependencies {
implementation("org.pitest:pitest-command-line:1.7.0")
implementation("org.junit.jupiter:junit-jupiter:5.8.1")
implementation("org.junit.jupiter:junit-jupiter:5.8.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
// https://mvnrepository.com/artifact/org.jfree/jfreechart
implementation("org.jfree:jfreechart:1.0.19")
// https://mvnrepository.com/artifact/jfree/jcommon
implementation("org.jfree:jcommon:1.0.24")
// https://mvnrepository.com/artifact/org.mockito/mockito-core
testImplementation("org.mockito:mockito-core:5.8.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
}

group = "com.amos.pitmutationmate"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: 2023
package com.amos.pitmutationmate.pitmutationmate

import com.amos.pitmutationmate.pitmutationmate.reporting.XMLParser
import com.amos.pitmutationmate.pitmutationmate.visualization.BarGraph
import com.amos.pitmutationmate.pitmutationmate.visualization.LatestPiTestReport
import com.amos.pitmutationmate.pitmutationmate.visualization.LineGraph
Expand All @@ -10,11 +11,19 @@ import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.JBLabel
import com.intellij.ui.content.ContentFactory
import javax.swing.JPanel

internal class MutationTestToolWindowFactory : ToolWindowFactory, DumbAware {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val latestPiTestReport = ContentFactory.getInstance().createContent(LatestPiTestReport(), "Latest Result", false)
// TODO: fetch most recent results to display (e.g. when opening up the editor and previous Pitest runs are saved)
val lastCoverageReport: XMLParser.CoverageReport? = null
val latestPiTestReport = if (lastCoverageReport == null) {
ContentFactory.getInstance().createContent(displayErrorMessage(), "Latest Result", false)
} else {
ContentFactory.getInstance().createContent(LatestPiTestReport(lastCoverageReport), "Latest Result", false)
}
val table = ContentFactory.getInstance().createContent(JTreeTable(), "Mutationtest Coverage", false)
val lineChart = ContentFactory.getInstance().createContent(LineGraph(), "Line Chart", false)
val barChart = ContentFactory.getInstance().createContent(BarGraph(), "Bar Chart", false)
Expand All @@ -24,4 +33,19 @@ internal class MutationTestToolWindowFactory : ToolWindowFactory, DumbAware {
toolWindow.contentManager.addContent(lineChart)
toolWindow.contentManager.addContent(barChart)
}

fun updateReport(toolWindow: ToolWindow, newCoverageReport: XMLParser.CoverageReport) {
toolWindow.contentManager.findContent("Latest Result").component = LatestPiTestReport(newCoverageReport)
}

private fun displayErrorMessage(): JPanel {
val panel = JPanel()

// Displaying an error message in the panel
val errorMessage = "No results to display yet."
val errorLabel = JBLabel(errorMessage)
panel.add(errorLabel)

return panel
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,82 @@

package com.amos.pitmutationmate.pitmutationmate.actions

import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassOwner
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtClass

class ContextMenuAction : RunConfigurationAction() {
private val logger = Logger.getInstance(ContextMenuAction::class.java)
override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(CommonDataKeys.EDITOR)
val psiFile = e.getData(CommonDataKeys.PSI_FILE)
println("ContextMenuAction: actionPerformed for file $psiFile")
val psiClasses = (psiFile as PsiClassOwner).classes
val fqns = mutableListOf<String>()
for (psiClass in psiClasses) {
val fqn = psiClass.qualifiedName
if (fqn != null) {
fqns.add(fqn)
println("ContextMenuAction: detected class '$fqn'")
if (e.place == "EditorPopup") {
logger.info("ContextMenuAction: actionPerformed in EditorPopup for file $psiFile")
val psiElement = psiFile?.findElementAt(editor?.caretModel!!.offset)
val selectedClass = findEnclosingClass(psiElement)
if (selectedClass != null) {
var classFQN = ""
if (selectedClass is PsiClass) {
classFQN = selectedClass.qualifiedName.toString()
}
if (selectedClass is KtClass) {
classFQN = selectedClass.fqName.toString()
}

logger.info("ContextMenuAction: selected class is $classFQN.")
updateAndExecuteRunConfig(classFQN, e.project!!, editor)
}
}
val editor = e.getData(CommonDataKeys.EDITOR)
updateAndExecuteRunConfig(fqns.first(), e.project!!, editor)
if (e.place == "ProjectViewPopup") {
logger.info("ContextMenuAction: actionPerformed in ProjectViewPopup for file $psiFile")
val psiClasses = (psiFile as PsiClassOwner).classes
var classFQNs: String = ""
for (psiClass in psiClasses) {
val fqn = psiClass.qualifiedName
if (fqn != null) {
classFQNs = if (classFQNs != "") {
"$classFQNs,$fqn"
} else {
fqn
}
}
}
logger.info("ContextMenuAction: selected classes are $classFQNs.")
updateAndExecuteRunConfig(classFQNs, e.project!!, editor)
}
}

override fun update(e: AnActionEvent) {
val file: VirtualFile? = e.dataContext.getData("virtualFile") as VirtualFile?
val shouldEnable: Boolean = checkCondition(file)
val shouldEnable: Boolean = checkCondition(e)
e.presentation.isEnabled = shouldEnable
}

private fun checkCondition(file: VirtualFile?): Boolean {
return file != null && (file.name.endsWith(".java") || file.name.endsWith(".kt"))
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}

private fun checkCondition(e: AnActionEvent): Boolean {
val psiFile = e.getData(CommonDataKeys.PSI_FILE)
val validFile = psiFile != null && (psiFile.name.endsWith(".java") || psiFile.name.endsWith(".kt"))
if (e.place == "EditorPopup") {
val editor = e.getData(CommonDataKeys.EDITOR)
val psiElement = psiFile?.findElementAt(editor?.caretModel!!.offset)
val validClass = (findEnclosingClass(psiElement) != null)
return validFile && validClass
}
return validFile
}

private fun findEnclosingClass(psiElement: PsiElement?): PsiElement? {
var currentElement: PsiElement? = psiElement
while (currentElement != null && currentElement !is PsiClass && currentElement !is KtClass) {
currentElement = currentElement.parent
}
return currentElement
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ import com.intellij.execution.lineMarker.RunLineMarkerContributor
import com.intellij.icons.AllIcons
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtClass
import javax.swing.Icon

class GutterMarker : RunLineMarkerContributor() {
override fun getInfo(psielement: PsiElement): Info? {
val gutterIcon: Icon = AllIcons.General.ArrowRight
if (psielement is PsiClass) {
val gutterIcon: Icon = AllIcons.General.ArrowRight
val toolTipProvider: (PsiElement) -> String = { _ -> "Run PIT MutationMate on '${psielement.name}'" }
val fqn = psielement.qualifiedName
val action: Array<GutterAction?> = arrayOf(fqn?.let { GutterAction(it) })
return Info(gutterIcon, action, toolTipProvider)
}
if (psielement is KtClass) {
val toolTipProvider: (PsiElement) -> String = { _ -> "Run PIT MutationMate on '${psielement.name}'" }
val fqn = psielement.fqName.toString()
val action: Array<GutterAction?> = arrayOf(GutterAction(fqn))
return Info(gutterIcon, action, toolTipProvider)
}
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2023 Tim Herzig <[email protected]>

package com.amos.pitmutationmate.pitmutationmate.actions

import com.amos.pitmutationmate.pitmutationmate.reporting.XMLParser
import com.intellij.codeInsight.hint.HintManager
import com.intellij.codeInsight.hint.HintManagerImpl
import com.intellij.codeInsight.hint.HintUtil
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.event.EditorMouseMotionListener
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.ui.LightweightHint
import com.intellij.util.ui.accessibility.AccessibleContextUtil
import java.awt.Point
import javax.swing.JComponent

class HoverAction(private val editor: Editor, private val result: XMLParser.ResultData) {
fun addHoverAction() {
this.editor.addEditorMouseListener(MouseClick())
// TODO: Decide on which action suits the plugin the best --> when choice is made refactor to only use one
// this.editor.addEditorMouseMotionListener(MouseMotion())
}

inner class MouseMotion : EditorMouseMotionListener {
override fun mouseMoved(event: EditorMouseEvent) {
showHoverMessage(event.mouseEvent.point)
}
}

inner class MouseClick : EditorMouseListener {
override fun mouseClicked(event: EditorMouseEvent) {
showHoverMessage(event.mouseEvent.point)
}
}

private fun buildHoverMessage(point: Point): String? {
val project: Project = this.editor.project ?: return null
val psiFile: PsiFile = PsiDocumentManager.getInstance(project).getPsiFile(this.editor.document) ?: return null

val offset: Int = this.editor.visualPositionToOffset(this.editor.xyToVisualPosition(point))
val line: Int = this.editor.yToVisualLine(point.y) + 1
PsiTreeUtil.findElementOfClassAtOffset(psiFile, offset, psiFile.javaClass, false)

for (r in this.result.mutationResults) {
if (r.lineNumber == line) {
val color: String = if (r.detected) "dark-green" else "dark-pink"
return "PiTest: selected offset: $offset, selected line: $line \n" +
"The color of this line is $color"
}
}

return null
}

fun showHoverMessage(point: Point) {
val message: String = buildHoverMessage(point) ?: return
val hintManager: HintManagerImpl = HintManagerImpl.getInstanceImpl()
val label: JComponent = HintUtil.createInformationLabel(message, null, null, null)
AccessibleContextUtil.setName(label, "PiTest")
val hint = LightweightHint(label)
val p: Point = HintManagerImpl.getHintPosition(hint, this.editor, this.editor.xyToVisualPosition(point), 1)
val flags: Int = HintManager.HIDE_BY_ANY_KEY or HintManager.HIDE_BY_TEXT_CHANGE or HintManager.HIDE_BY_SCROLLING
hintManager.showEditorHint(hint, this.editor, p, flags, 0, true, 1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@

package com.amos.pitmutationmate.pitmutationmate.actions

import com.amos.pitmutationmate.pitmutationmate.MutationTestToolWindowFactory
import com.amos.pitmutationmate.pitmutationmate.configuration.RunConfiguration
import com.amos.pitmutationmate.pitmutationmate.configuration.RunConfigurationType
import com.amos.pitmutationmate.pitmutationmate.reporting.XMLListener
import com.amos.pitmutationmate.pitmutationmate.reporting.XMLParser
import com.intellij.execution.ExecutorRegistry
import com.intellij.execution.ProgramRunnerUtil
import com.intellij.execution.RunManager
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowManager
import org.jetbrains.kotlin.idea.gradleTooling.get
import java.nio.file.Paths

abstract class RunConfigurationAction : AnAction() {
Expand All @@ -34,8 +39,26 @@ abstract class RunConfigurationAction : AnAction() {
if (editor != null) {
// TODO: use actual XML report directories. This currently uses a placeholder test folder
val dir = Paths.get("build", "reports", "pitest", "test", "mutations.xml")
var xmlListener = XMLListener(dir, editor)
val xmlListener = XMLListener(dir, editor)
xmlListener.listen()
val ha: HoverAction = HoverAction(editor, xmlListener.getResult())
ha.addHoverAction()
}

// Update visualisation with mock results
// TODO: replace this by real results extracted by the HTMLParser
val toolWindow: ToolWindow? = ToolWindowManager.getInstance(project).getToolWindow("Pitest")
val mutationTestToolWindowFactorySingleton = MutationTestToolWindowFactory()
val coverageReport: XMLParser.CoverageReport = XMLParser.CoverageReport(
lineCoveragePercentage = 80,
lineCoverageTextRatio = "160/200",
mutationCoveragePercentage = 50,
mutationCoverageTextRatio = "100/200",
testStrengthPercentage = 40,
testStrengthTextRatio = "80/200"
)
if (toolWindow != null) {
mutationTestToolWindowFactorySingleton.updateReport(toolWindow, coverageReport)
}
}
}
Loading

0 comments on commit 99d2b66

Please sign in to comment.