├── plugin ├── testdata │ ├── EmptyFile.java │ ├── EmptyClass.java │ ├── traceFiles │ │ ├── PointTest,test.tr │ │ └── BadPointTest,test.tr │ ├── TwoTestClassesTest.java │ ├── EmptyTest.java │ ├── Math.java │ ├── Point.java │ ├── BrokenTernary.java │ ├── Math2.java │ ├── MixedTestAndNoTestMethods.java │ ├── expected │ │ ├── AssertionSuggestionServiceTest.testGetAssertionsReturnsNothing.java │ │ ├── AssertionSuggestionServiceTest.testGetAssertionsOnReturnType.java │ │ ├── AssertionSuggestionServiceTest.testGetAssertionsWithSideEffects.java │ │ ├── Dijkstra.testDijkstraTest.java │ │ ├── DuplicateTestsServiceTest.testDuplicateMethod.java │ │ └── DuplicateTestsServiceTest.testDuplicateMethodUnderCaret.java │ ├── BrokenClass.java │ ├── TestDuplication.java │ ├── Tests.java │ ├── Dijkstra.java │ ├── PointTest.java │ ├── PointTestFullAnnotations.java │ └── AnimalTest.java ├── src │ ├── main │ │ ├── resources │ │ │ ├── intentionDescriptions │ │ │ │ ├── Trace test │ │ │ │ │ ├── after.java.template │ │ │ │ │ ├── before.java.template │ │ │ │ │ └── description.html │ │ │ │ └── Assertion suggestions │ │ │ │ │ ├── before.java.template │ │ │ │ │ ├── after.java.template │ │ │ │ │ └── description.html │ │ │ ├── messages │ │ │ │ ├── ServerBundle.properties │ │ │ │ ├── TestMethodGenerationMessageBundle.properties │ │ │ │ ├── AssertionSuggestionMessageBundle.properties │ │ │ │ └── TestingChecklistMessageBundle.properties │ │ │ ├── icons │ │ │ │ └── pluginIcon.svg │ │ │ └── META-INF │ │ │ │ └── pluginIcon.svg │ │ └── kotlin │ │ │ └── com │ │ │ └── testknight │ │ │ ├── models │ │ │ ├── AssertionSuggestion.kt │ │ │ ├── TestCoverageData.kt │ │ │ ├── CoverageStatsObject.kt │ │ │ ├── testingChecklist │ │ │ │ ├── TestingChecklist.kt │ │ │ │ └── leafNodes │ │ │ │ │ ├── CustomChecklistNode.kt │ │ │ │ │ ├── loopStatements │ │ │ │ │ ├── DoWhileStatementChecklistNode.kt │ │ │ │ │ ├── ForLoopStatementChecklistNode.kt │ │ │ │ │ ├── WhileStatementChecklistNode.kt │ │ │ │ │ └── ForEachStatementChecklistNode.kt │ │ │ │ │ ├── ConditionChecklistNode.kt │ │ │ │ │ ├── ThrowStatementChecklistNode.kt │ │ │ │ │ ├── ParameterChecklistNode.kt │ │ │ │ │ └── branchingStatements │ │ │ │ │ ├── TryStatementChecklistNode.kt │ │ │ │ │ └── SwitchStatementChecklistNode.kt │ │ │ ├── ChecklistUserObject.kt │ │ │ ├── TestClassData.kt │ │ │ ├── TestMethodData.kt │ │ │ ├── TestMethodUserObject.kt │ │ │ ├── ActionData.kt │ │ │ ├── CoverageDiffObject.kt │ │ │ ├── UsageData.kt │ │ │ ├── sideEffectAnalysis │ │ │ │ └── Class.kt │ │ │ ├── TruthTable.kt │ │ │ └── HighlightedTextData.kt │ │ │ ├── exceptions │ │ │ ├── ProjectNotFoundException.kt │ │ │ ├── TestKnightException.kt │ │ │ ├── NoTestCoverageDataException.kt │ │ │ ├── DocumentNotFoundException.kt │ │ │ ├── TraceFileNotFoundException.kt │ │ │ ├── CorruptedTraceFileException.kt │ │ │ ├── InvalidActionIdException.kt │ │ │ ├── InvalidVirtualFileException.kt │ │ │ ├── InvalidConfigurationException.kt │ │ │ └── InvalidTreePathException.kt │ │ │ ├── listeners │ │ │ ├── AppClosedListener.kt │ │ │ ├── TestLoggingListener.kt │ │ │ ├── TestKnightExecutionListener.kt │ │ │ ├── InitializationListener.kt │ │ │ ├── FileEditorListener.kt │ │ │ ├── checklist │ │ │ │ ├── ClassChecklistIconHandler.kt │ │ │ │ ├── MethodChecklistIconHandler.kt │ │ │ │ ├── CheckListKeyboardListener.kt │ │ │ │ ├── CheckedNodeListener.kt │ │ │ │ └── ChecklistMouseListener.kt │ │ │ └── testlist │ │ │ │ └── TestListSelectionListener.kt │ │ │ ├── utilities │ │ │ ├── PsiConverter.kt │ │ │ └── StringFormatter.kt │ │ │ ├── checklistGenerationStrategies │ │ │ ├── leafStrategies │ │ │ │ ├── LeafChecklistGeneratorStrategy.kt │ │ │ │ └── ThrowStatementChecklistGenerationStrategy.kt │ │ │ ├── parentStrategies │ │ │ │ ├── ParentChecklistGeneratorStrategy.kt │ │ │ │ └── ClassChecklistGenerationStrategy.kt │ │ │ └── ChecklistGeneratorStrategy.kt │ │ │ ├── actions │ │ │ ├── diffcoverage │ │ │ │ ├── HideDiffCovHighlights.kt │ │ │ │ └── RefreshDiffHighlights.kt │ │ │ ├── HideTracedTestHighlightsAction.kt │ │ │ ├── settings │ │ │ │ ├── EditElementAction.kt │ │ │ │ ├── DeleteElementAction.kt │ │ │ │ ├── AddClassAction.kt │ │ │ │ └── AddTypeAction.kt │ │ │ ├── checklist │ │ │ │ ├── ClearChecklistAction.kt │ │ │ │ ├── ChecklistClassLineMarkerProvider.kt │ │ │ │ ├── ChecklistMethodLineMarkerProvider.kt │ │ │ │ ├── DeleteElementChecklistAction.kt │ │ │ │ └── GenerateChecklistUnderCaretAction.kt │ │ │ └── testlist │ │ │ │ ├── ClearTestAction.kt │ │ │ │ └── DuplicateTestUnderCaretAction.kt │ │ │ ├── messageBundleHandlers │ │ │ ├── ServerMessageBundleHandler.kt │ │ │ ├── TestingChecklistMessageBundleHandler.kt │ │ │ ├── AssertionSuggestionMessageBundleHandler.kt │ │ │ └── TestMethodGenerationMessageBundleHandler.kt │ │ │ ├── highlightResolutionStrategies │ │ │ ├── MagicNumberStrategy.kt │ │ │ ├── ConstructorArgsStrategy.kt │ │ │ ├── HighlightResolutionStrategy.kt │ │ │ └── AssertionArgsStrategy.kt │ │ │ ├── services │ │ │ ├── GotoTestService.kt │ │ │ ├── TestAnalyzerService.kt │ │ │ ├── checklist │ │ │ │ └── ChecklistTreePersistent.kt │ │ │ ├── ExceptionHandlerService.kt │ │ │ ├── LoadTestsService.kt │ │ │ ├── TestMethodGenerationService.kt │ │ │ └── GlobalHighlighter.kt │ │ │ ├── views │ │ │ ├── UserInterfaceFactory.kt │ │ │ └── coverage │ │ │ │ └── CoverageStatsCellRenderer.kt │ │ │ └── extensions │ │ │ ├── DiffCoverageLineMarkerRenderer.kt │ │ │ ├── TestKnightTestCase.kt │ │ │ └── DiffCoverageListener.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── testknight │ │ ├── models │ │ ├── ActionDataTest.kt │ │ ├── UsageDataTest.kt │ │ └── TruthTableTest.kt │ │ ├── highlightResolutionStrategies │ │ ├── MagicNumberStrategyTest.kt │ │ ├── AssertionArgsStrategyTest.kt │ │ └── ConstructorArgsStrategyTest.kt │ │ ├── services │ │ ├── TestAnalyzerServiceTest.kt │ │ ├── TestTracingServiceTest.kt │ │ └── UsageDataServiceTest.kt │ │ └── checklistGenerationStrategies │ │ ├── TestKnightJavaPsiVisitorTest.kt │ │ └── leafStrategies │ │ ├── ThrowStatementChecklistGenerationStrategyTest.kt │ │ ├── loopStatements │ │ └── ForEachStatementChecklistGenerationStrategyTest.kt │ │ └── branchingStatements │ │ └── IfStatementChecklistGenerationStrategyTest.kt ├── CHANGELOG.md └── gradle.properties ├── .gitignore ├── server ├── config │ ├── checkstyle │ │ └── checkstyle-suppressions.xml │ ├── spotbugs │ │ └── excludeFilter.xml │ └── pmd │ │ └── pmd.xml ├── lombok.config ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── testknight │ │ │ │ └── TestKnightTelemetryServer │ │ │ │ ├── security │ │ │ │ ├── Hasher.java │ │ │ │ └── Md5Hasher.java │ │ │ │ ├── repositories │ │ │ │ ├── ActionRepository.java │ │ │ │ └── UsageRecordRepository.java │ │ │ │ ├── dataTransferObjects │ │ │ │ ├── responses │ │ │ │ │ ├── UsageDataAddedDto.java │ │ │ │ │ └── ExceptionDto.java │ │ │ │ └── requests │ │ │ │ │ ├── RequestDto.java │ │ │ │ │ ├── ActionEventDto.java │ │ │ │ │ └── UsageDataDto.java │ │ │ │ ├── exceptions │ │ │ │ ├── ValidationException.java │ │ │ │ ├── InvalidActionIdException.java │ │ │ │ ├── InvalidHashException.java │ │ │ │ └── NullFieldException.java │ │ │ │ ├── domain │ │ │ │ ├── model │ │ │ │ │ ├── Action.java │ │ │ │ │ └── UsageRecord.java │ │ │ │ └── factories │ │ │ │ │ └── UsageRecordFactory.java │ │ │ │ ├── TestKnightTelemetryServerApplication.java │ │ │ │ ├── validation │ │ │ │ ├── BaseValidator.java │ │ │ │ ├── RequestValidator.java │ │ │ │ ├── HashValidator.java │ │ │ │ └── ContentValidator.java │ │ │ │ └── controllers │ │ │ │ └── UsageDataController.java │ │ └── resources │ │ │ ├── application.properties │ │ │ └── populate_database.sql │ └── test │ │ ├── java │ │ └── com │ │ │ └── testknight │ │ │ └── TestKnightTelemetryServer │ │ │ ├── SmokeTest.java │ │ │ ├── domain │ │ │ ├── model │ │ │ │ └── ActionTest.java │ │ │ └── factories │ │ │ │ └── UsageRecordFactoryTest.java │ │ │ └── validation │ │ │ └── ContentValidatorTest.java │ │ └── resources │ │ ├── application.properties │ │ └── import.sql ├── .gitignore └── build.gradle ├── screenshots ├── diff-cov.png ├── test-list.gif ├── diff-window.png ├── traceability.gif ├── duplicate-test.gif ├── testing-checklist.png └── assertion-suggestions.gif ├── settings.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── documentation ├── design │ ├── detailedSystemArchitecture.jpg │ └── telemetryDesign │ │ ├── clientStates.png │ │ ├── databaseDesign.jpg │ │ ├── ActionIds.md │ │ └── testKnightTelemtryApi.yaml └── mockups │ ├── Checklist │ ├── ChecklistEditor.png │ ├── ChecklistSideBar.png │ └── DetailedChecklist.md │ ├── CodeCoverage │ ├── CoverageDiffView.png │ ├── CoverageFullWindowView.png │ └── DetailedCodeCoverage.md │ └── AssertionSuggestion │ ├── AssertionSuggestionOverallUI.png │ └── DetailedAssertionSuggestionMockup.md ├── .github └── workflows │ ├── server.yml │ └── plugin.yml ├── .run ├── Run IDE with Plugin.run.xml ├── Run Plugin Tests.run.xml └── Run Plugin Verification.run.xml └── LICENSE /plugin/testdata/EmptyFile.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | out 5 | -------------------------------------------------------------------------------- /server/config/checkstyle/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/lombok.config: -------------------------------------------------------------------------------- 1 | config.stopbubbling = true 2 | lombok.addLombokGeneratedAnnotation = true -------------------------------------------------------------------------------- /plugin/testdata/EmptyClass.java: -------------------------------------------------------------------------------- 1 | public class EmptyClass { 2 | //There is nothing here! 3 | } -------------------------------------------------------------------------------- /screenshots/diff-cov.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/screenshots/diff-cov.png -------------------------------------------------------------------------------- /screenshots/test-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/screenshots/test-list.gif -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "TestKnight" 2 | 3 | include("plugin") 4 | include("server") 5 | -------------------------------------------------------------------------------- /screenshots/diff-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/screenshots/diff-window.png -------------------------------------------------------------------------------- /screenshots/traceability.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/screenshots/traceability.gif -------------------------------------------------------------------------------- /screenshots/duplicate-test.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/screenshots/duplicate-test.gif -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /screenshots/testing-checklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/screenshots/testing-checklist.png -------------------------------------------------------------------------------- /server/config/spotbugs/excludeFilter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plugin/src/main/resources/intentionDescriptions/Trace test/after.java.template: -------------------------------------------------------------------------------- 1 | public void setY(int y) { 2 | this.y = y; 3 | } -------------------------------------------------------------------------------- /screenshots/assertion-suggestions.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/screenshots/assertion-suggestions.gif -------------------------------------------------------------------------------- /server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /plugin/testdata/traceFiles/PointTest,test.tr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/plugin/testdata/traceFiles/PointTest,test.tr -------------------------------------------------------------------------------- /plugin/testdata/traceFiles/BadPointTest,test.tr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/plugin/testdata/traceFiles/BadPointTest,test.tr -------------------------------------------------------------------------------- /plugin/src/main/resources/messages/ServerBundle.properties: -------------------------------------------------------------------------------- 1 | debugUrl=localhost:8080 2 | hashString=exampleMagicString 3 | usageData=http://{0}/usagedata 4 | -------------------------------------------------------------------------------- /documentation/design/detailedSystemArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/design/detailedSystemArchitecture.jpg -------------------------------------------------------------------------------- /documentation/design/telemetryDesign/clientStates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/design/telemetryDesign/clientStates.png -------------------------------------------------------------------------------- /documentation/mockups/Checklist/ChecklistEditor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/mockups/Checklist/ChecklistEditor.png -------------------------------------------------------------------------------- /documentation/mockups/Checklist/ChecklistSideBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/mockups/Checklist/ChecklistSideBar.png -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/AssertionSuggestion.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | data class AssertionSuggestion(var message: String) 4 | -------------------------------------------------------------------------------- /documentation/design/telemetryDesign/databaseDesign.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/design/telemetryDesign/databaseDesign.jpg -------------------------------------------------------------------------------- /documentation/mockups/CodeCoverage/CoverageDiffView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/mockups/CodeCoverage/CoverageDiffView.png -------------------------------------------------------------------------------- /documentation/mockups/CodeCoverage/CoverageFullWindowView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/mockups/CodeCoverage/CoverageFullWindowView.png -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/ProjectNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | class ProjectNotFoundException : Exception("Project not found") 4 | -------------------------------------------------------------------------------- /plugin/src/main/resources/intentionDescriptions/Assertion suggestions/before.java.template: -------------------------------------------------------------------------------- 1 | @Test 2 | void setXTest(){ 3 | Point p = new Point(0, 0); 4 | 5 | p.setX(5); 6 | } -------------------------------------------------------------------------------- /documentation/mockups/AssertionSuggestion/AssertionSuggestionOverallUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SERG-Delft/testknight/HEAD/documentation/mockups/AssertionSuggestion/AssertionSuggestionOverallUI.png -------------------------------------------------------------------------------- /plugin/src/main/resources/intentionDescriptions/Trace test/before.java.template: -------------------------------------------------------------------------------- 1 | @Test 2 | void setYTest2() { 3 | Point p = new Point(0, 0); 4 | 5 | p.setY(5); 6 | 7 | assertEquals(5, p.getY()); 8 | } 9 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/TestCoverageData.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | class TestCoverageData(val testName: String) { 4 | val classes: MutableMap> = mutableMapOf() 5 | } 6 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/security/Hasher.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.security; 2 | 3 | public interface Hasher { 4 | 5 | public String hash(String message); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/CoverageStatsObject.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | data class CoverageStatsObject( 4 | val coveredLines: Int, 5 | val allLines: Int, 6 | val percentageCovered: Int, 7 | val percentChange: Int 8 | ) 9 | -------------------------------------------------------------------------------- /server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /plugin/src/main/resources/intentionDescriptions/Assertion suggestions/after.java.template: -------------------------------------------------------------------------------- 1 | @Test 2 | void setXTest(){ 3 | Point p = new Point(0, 0); 4 | 5 | p.setX(5); 6 | 7 | /** 8 | * Assert that "this.x" is re-assigned properly. 9 | */ 10 | 11 | } -------------------------------------------------------------------------------- /plugin/testdata/TwoTestClassesTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | public class FirstTest { 4 | @Test 5 | public void firstTest() { 6 | assertEquals(1,1); 7 | } 8 | } 9 | 10 | public class SecondTest { 11 | @Test 12 | public void secondTest() { 13 | assertEquals(2,2); 14 | } 15 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/TestKnightException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | abstract class TestKnightException : Exception() { 6 | abstract var title: String 7 | abstract var content: String 8 | abstract var type: NotificationType 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/main/resources/intentionDescriptions/Assertion suggestions/description.html: -------------------------------------------------------------------------------- 1 | This intention action generates the assertion suggestions for the selected testing method.
2 | Assertion suggestions are generated as comments at the bottom of the test method.
3 | Assertion suggestions can only be generated in test methods. 4 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/repositories/ActionRepository.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.repositories; 2 | 3 | import com.testknight.TestKnightTelemetryServer.domain.model.*; 4 | import org.springframework.data.repository.*; 5 | 6 | public interface ActionRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/AppClosedListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners 2 | 3 | import com.intellij.ide.AppLifecycleListener 4 | import com.testknight.services.UsageDataService 5 | 6 | class AppClosedListener : AppLifecycleListener { 7 | 8 | override fun appWillBeClosed(isRestart: Boolean) = UsageDataService.instance.sendUserData() 9 | } 10 | -------------------------------------------------------------------------------- /plugin/testdata/EmptyTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class EmptyTest { 12 | 13 | } -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/security/Md5Hasher.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.security; 2 | 3 | 4 | import org.apache.commons.codec.digest.*; 5 | 6 | public class Md5Hasher implements Hasher { 7 | 8 | @Override 9 | public String hash(String message) { 10 | return DigestUtils.md5Hex(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/dataTransferObjects/responses/UsageDataAddedDto.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.dataTransferObjects.responses; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @AllArgsConstructor 7 | @NoArgsConstructor 8 | public class UsageDataAddedDto { 9 | private String message = "Successfully added usage data"; 10 | } 11 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/TestingChecklist.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist 2 | 3 | import com.testknight.models.testingChecklist.parentNodes.TestingChecklistClassNode 4 | 5 | data class TestingChecklist(var classChecklists: MutableList = mutableListOf()) 6 | 7 | open class TestingChecklistNode(open var checked: Int = 0) 8 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/models/ActionDataTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import org.junit.Test 4 | import kotlin.test.assertEquals 5 | 6 | class ActionDataTest { 7 | 8 | @Test 9 | fun testHashString() { 10 | val actionData = ActionData("actionId") 11 | assertEquals(actionData.toHashString(), "actionId${actionData.dateTime}") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/testdata/Math.java: -------------------------------------------------------------------------------- 1 | public class Math { 2 | 3 | public int add(int a, int b) { 4 | return a + b; 5 | } 6 | 7 | public int sub(int a, int b) { 8 | return a - b; 9 | } 10 | 11 | public int mult(int a, int b) { 12 | return a * b; 13 | } 14 | 15 | public double div(int a, int b) { 16 | return (double) a / b; 17 | } 18 | 19 | 20 | } -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/dataTransferObjects/responses/ExceptionDto.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.dataTransferObjects.responses; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @AllArgsConstructor 7 | @NoArgsConstructor 8 | public class ExceptionDto { 9 | 10 | private Integer errorCode; 11 | private String message; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /plugin/testdata/Point.java: -------------------------------------------------------------------------------- 1 | class Point { 2 | 3 | int x; 4 | int y; 5 | 6 | public Point(int x, int y) { 7 | this.x = x; 8 | this.y = y; 9 | } 10 | 11 | public void move(int x, int y) { 12 | this.x = x; 13 | this.y = y; 14 | } 15 | 16 | public void translate(int dx, int dy) { 17 | x += dx; 18 | y += dy; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/exceptions/ValidationException.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.exceptions; 2 | 3 | public class ValidationException extends RuntimeException { 4 | 5 | public static final long serialVersionUID = 2336261092312323L; 6 | 7 | public ValidationException(String message) { 8 | super(message); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/NoTestCoverageDataException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | class NoTestCoverageDataException : TestKnightException() { 6 | override var title: String = "No Test Coverage Data" 7 | override var content: String = "" 8 | override var type: NotificationType = NotificationType.ERROR 9 | } 10 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/domain/model/Action.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.domain.model; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | @Entity 11 | @Table(name = "ACTIONS") 12 | public class Action { 13 | @Id 14 | @Column(name = "action_Id") 15 | private String actionId; 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/DocumentNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | class DocumentNotFoundException : TestKnightException() { 6 | override var title: String = "Document not found" 7 | override var content: String = "Cannot found document for the file" 8 | override var type: NotificationType = NotificationType.ERROR 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/TraceFileNotFoundException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | class TraceFileNotFoundException : TestKnightException() { 6 | override var content: String = "Could not find coverage data" 7 | override var title: String = "Trace File not found:" 8 | override var type: NotificationType = NotificationType.ERROR 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/utilities/PsiConverter.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.utilities 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.util.xmlb.Converter 5 | 6 | class PsiConverter : Converter() { 7 | override fun toString(value: PsiElement): String? { 8 | return "null" 9 | } 10 | 11 | override fun fromString(value: String): PsiElement? { 12 | return null 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/ChecklistUserObject.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.testknight.models.testingChecklist.TestingChecklistNode 4 | 5 | /** 6 | * Custom user object which gets passed into checkListTree which contains useful information used by the renderer. 7 | * 8 | * @param checklistNode Information regarding the node. 9 | */ 10 | data class ChecklistUserObject(var checklistNode: TestingChecklistNode) 11 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/CorruptedTraceFileException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | class CorruptedTraceFileException(ex: Exception) : TestKnightException() { 6 | 7 | override var content: String = ex.message.toString() 8 | override var title: String = "Failed to read trace file" 9 | override var type: NotificationType = NotificationType.ERROR 10 | } 11 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/InvalidActionIdException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | class InvalidActionIdException(actionId: String) : TestKnightException() { 6 | override var title: String = "Invalid Action ID" 7 | override var content: String = "$actionId is not a valid action ID" 8 | override var type: NotificationType = NotificationType.ERROR 9 | } 10 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/exceptions/InvalidActionIdException.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.exceptions; 2 | 3 | public class InvalidActionIdException extends ValidationException { 4 | 5 | public static final long serialVersionUID = 927626852988215140L; 6 | 7 | 8 | public InvalidActionIdException(String actionId) { 9 | super(actionId + " is not a valid actionId"); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/exceptions/InvalidHashException.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.exceptions; 2 | 3 | public class InvalidHashException extends ValidationException { 4 | 5 | public static final long serialVersionUID = 12162635991005290L; 6 | 7 | public InvalidHashException() { 8 | super("The hash included in the request does not come from a verified client"); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/dataTransferObjects/requests/RequestDto.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests; 2 | 3 | public interface RequestDto { 4 | 5 | /** 6 | * Creates a string representation 7 | * of the DTO that is suitable for 8 | * hashing. 9 | * 10 | * @return the string that can be used 11 | * to generate a hash. 12 | */ 13 | public String toHashString(); 14 | } 15 | -------------------------------------------------------------------------------- /plugin/src/main/resources/messages/TestMethodGenerationMessageBundle.properties: -------------------------------------------------------------------------------- 1 | switchTestCaseName=testSwitchStatement 2 | tryTestCaseName=testTryStatement 3 | doWhileTestCaseName=testDoWhileLoop 4 | forEachTestCaseName=testForEachLoop 5 | forLoopTestCaseName=testForLoop 6 | whileLoopTestCaseName=testWhileLoop 7 | conditionTestCaseName=testCondition 8 | parameterTestCaseName=testParameterValue 9 | throwTestCaseName=testThrows 10 | customNodeTestCaseName=testCustomCase 11 | testMethodComment=Generated based on: {0} -------------------------------------------------------------------------------- /plugin/testdata/BrokenTernary.java: -------------------------------------------------------------------------------- 1 | //A class containing compilation errors 2 | // for testing incorrect ternary operator usage 3 | 4 | class BrokenClass { 5 | 6 | private int a = 24; 7 | private int b = 42; 8 | 9 | public int incompleteConditionalExpression(){ 10 | int a=5; 11 | int b=10; 12 | int c = null ? a:b; 13 | return c; 14 | } 15 | 16 | public int conditionalExpressionWithLiteral() { 17 | return true ? a : b; 18 | } 19 | 20 | 21 | 22 | } -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/repositories/UsageRecordRepository.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.repositories; 2 | 3 | import com.testknight.TestKnightTelemetryServer.domain.model.*; 4 | import org.springframework.data.repository.*; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import javax.transaction.*; 8 | 9 | @Transactional 10 | @Repository 11 | public interface UsageRecordRepository extends CrudRepository { 12 | } 13 | -------------------------------------------------------------------------------- /server/src/test/java/com/testknight/TestKnightTelemetryServer/SmokeTest.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SmokeTest { 8 | 9 | /** 10 | * This test ensures that the sprint context 11 | * starts without errors. 12 | */ 13 | @Test 14 | @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") 15 | void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/checklistGenerationStrategies/leafStrategies/LeafChecklistGeneratorStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies.leafStrategies 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.testknight.checklistGenerationStrategies.ChecklistGeneratorStrategy 5 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 6 | 7 | interface LeafChecklistGeneratorStrategy : ChecklistGeneratorStrategy> 8 | -------------------------------------------------------------------------------- /plugin/src/main/resources/messages/AssertionSuggestionMessageBundle.properties: -------------------------------------------------------------------------------- 1 | fieldReassignment=Assert that attribute "{0}" is re-assigned properly. 2 | classFieldModification=Assert that method "{0}" modifies field "{1}" properly. 3 | transitiveFieldReassignment=Assert that field "{0}" of the object at field "{1}" is re-assigned properly. 4 | methodParameterModification=Assert that method "{0}" modifies argument "{1}" properly. 5 | parameterFieldReassignment=Assert that field "{0}" of argument "{1}" is re-assigned properly. -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/InvalidVirtualFileException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | class InvalidVirtualFileException(className: String, invalidReason: String) : TestKnightException() { 6 | override var title: String = "Virtual file is invalid" 7 | override var content: String = "Invalid virtual file for class $className. Reason: $invalidReason" 8 | override var type: NotificationType = NotificationType.ERROR 9 | } 10 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/TestLoggingListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners 2 | 3 | import com.intellij.execution.testframework.AbstractTestProxy 4 | import com.intellij.execution.testframework.TestStatusListener 5 | import com.testknight.services.UsageDataService 6 | 7 | class TestLoggingListener : TestStatusListener() { 8 | 9 | override fun testSuiteFinished(root: AbstractTestProxy?) { 10 | root ?: return 11 | UsageDataService.instance.logTests(root) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/TestClassData.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.intellij.psi.PsiClass 4 | 5 | /** 6 | * Contains information about a test class. 7 | * 8 | * @param name the name of the test class 9 | * @param methods list of TestMethodData objects representing the methods in the class 10 | * @param psiClass the reference to the actual class in the PSI tree 11 | */ 12 | data class TestClassData(val name: String, val methods: List, val psiClass: PsiClass) 13 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/TestKnightTelemetryServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class TestKnightTelemetryServerApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(TestKnightTelemetryServerApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/checklistGenerationStrategies/parentStrategies/ParentChecklistGeneratorStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies.parentStrategies 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.testknight.checklistGenerationStrategies.ChecklistGeneratorStrategy 5 | import com.testknight.models.testingChecklist.parentNodes.TestingChecklistParentNode 6 | 7 | interface ParentChecklistGeneratorStrategy< 8 | E : PsiElement, 9 | G : TestingChecklistParentNode> : ChecklistGeneratorStrategy 10 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/InvalidConfigurationException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | class InvalidConfigurationException(private val property: String, private val invalidConfiguration: String) : 6 | TestKnightException() { 7 | override var title: String = "Invalid Configuration" 8 | override var content: String = "Property $property cannot be set to $invalidConfiguration" 9 | override var type: NotificationType = NotificationType.ERROR 10 | } 11 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/diffcoverage/HideDiffCovHighlights.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.diffcoverage 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.components.service 6 | import com.testknight.services.CoverageHighlighterService 7 | 8 | class HideDiffCovHighlights : AnAction() { 9 | 10 | override fun actionPerformed(e: AnActionEvent) { 11 | e.project?.service()?.removeHighlights() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/diffcoverage/RefreshDiffHighlights.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.diffcoverage 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.components.service 6 | import com.testknight.services.CoverageHighlighterService 7 | 8 | class RefreshDiffHighlights : AnAction() { 9 | 10 | override fun actionPerformed(e: AnActionEvent) { 11 | e.project?.service()?.rebuildHighlights() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/exceptions/NullFieldException.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.exceptions; 2 | 3 | public class NullFieldException extends ValidationException { 4 | 5 | public static final long serialVersionUID = 2567201903468237233L; 6 | 7 | /** 8 | * Constructs a new NullFieldException 9 | * 10 | * @param nameOfField the name of the null field. 11 | */ 12 | public NullFieldException(String nameOfField) { 13 | super("Field " + nameOfField + " cannot be null"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/exceptions/InvalidTreePathException.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.exceptions 2 | 3 | import com.intellij.notification.NotificationType 4 | 5 | /** 6 | * Invalid tree path exception. 7 | * Doesn't need to be notified to the user (In case ParameterSuggestion tree throws this exception). 8 | */ 9 | class InvalidTreePathException(reason: String) : TestKnightException() { 10 | override var title: String = "Invalid Tree Path" 11 | override var content: String = "Reason: $reason" 12 | override var type: NotificationType = NotificationType.ERROR 13 | } 14 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/messageBundleHandlers/ServerMessageBundleHandler.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.messageBundleHandlers 2 | 3 | import com.intellij.AbstractBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.ServerBundle" 9 | 10 | object ServerMessageBundleHandler : AbstractBundle(BUNDLE) { 11 | 12 | @Suppress("SpreadOperator") 13 | @JvmStatic 14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 15 | getMessage(key, *params) 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/TestKnightExecutionListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners 2 | 3 | import com.intellij.execution.ExecutionListener 4 | import com.intellij.execution.process.ProcessHandler 5 | import com.intellij.execution.runners.ExecutionEnvironment 6 | import com.testknight.services.UsageDataService 7 | 8 | class TestKnightExecutionListener : ExecutionListener { 9 | 10 | override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { 11 | if (executorId == "Coverage") UsageDataService.instance.recordRunWithCoverage() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /plugin/testdata/Math2.java: -------------------------------------------------------------------------------- 1 | public class Math2 { 2 | 3 | public void add(){ 4 | int [] a = {0, 1} 5 | for(Int i : a){ 6 | print(a[i]) 7 | } 8 | } 9 | 10 | public void add2(){ 11 | int [] a = {0, 1} 12 | for(Int i : a){ 13 | print(a[i]) 14 | } 15 | } 16 | 17 | // public int sub(int a, int b) { 18 | // return a - b; 19 | // } 20 | // 21 | // public int mult(int a, int b) { 22 | // return a * b; 23 | // } 24 | // 25 | // public double div(int a, int b) { 26 | // return (double) a / b; 27 | // } 28 | 29 | 30 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/checklistGenerationStrategies/ChecklistGeneratorStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies 2 | 3 | import com.intellij.psi.PsiElement 4 | 5 | interface ChecklistGeneratorStrategy { 6 | 7 | /** 8 | * Generates the checklist of all tests that should be created for a given PsiElement. 9 | * 10 | * @param psiElement the PsiElement for which the checklist should be generated. 11 | * @return a list of TestingChecklistItem objects corresponding to suggested test cases. 12 | */ 13 | fun generateChecklist(psiElement: E): G 14 | } 15 | -------------------------------------------------------------------------------- /plugin/testdata/MixedTestAndNoTestMethods.java: -------------------------------------------------------------------------------- 1 | public class MixedTestAndNoTestMethods { 2 | 3 | @Test 4 | public void test() { 5 | getAnInt(); 6 | doNothing(); 7 | } 8 | 9 | @Test 10 | public void testMethod() { 11 | String noun = "Testing"; 12 | String adverb = "awesome"; 13 | methodUnderTest(noun, adverb); 14 | } 15 | 16 | public void methodUnderTest(String a, String b) { 17 | a.append(" is ", b); 18 | } 19 | 20 | public int getAnInt() { 21 | return 1; 22 | } 23 | 24 | public void doNothing() { 25 | return; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /server/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | #This file contains the Spring configuration to be used when running tests 2 | server.port=8080 3 | #H2 Configurations 4 | spring.datasource.url=jdbc:h2:mem:testdb 5 | spring.datasource.driverClassName=org.h2.Driver 6 | spring.datasource.username=sa 7 | spring.datasource.password= 8 | spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 9 | spring.h2.console.enabled=true 10 | spring.jpa.hibernate.ddl-auto=create-drop 11 | spring.sql.init.enabled=false 12 | #Set those to "true" for debugging 13 | spring.jpa.show-sql=true 14 | spring.jpa.properties.hibernate.format_sql=true 15 | 16 | 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/messageBundleHandlers/TestingChecklistMessageBundleHandler.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.messageBundleHandlers 2 | 3 | import com.intellij.AbstractBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.TestingChecklistMessageBundle" 9 | 10 | object TestingChecklistMessageBundleHandler : AbstractBundle(BUNDLE) { 11 | 12 | @Suppress("SpreadOperator") 13 | @JvmStatic 14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 15 | getMessage(key, *params) 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/HideTracedTestHighlightsAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.components.service 6 | import com.testknight.services.TestTracingService 7 | 8 | class HideTracedTestHighlightsAction : AnAction() { 9 | 10 | override fun actionPerformed(e: AnActionEvent) { 11 | val project = e.project ?: return 12 | val testTracingService = project.service() 13 | 14 | testTracingService.removeHighlights() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/TestMethodData.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.intellij.psi.PsiMethod 4 | 5 | /** 6 | * The TestMethodData class contains information about a test method. 7 | * 8 | * @param name the name of the test method. 9 | * @param testClassName the name of the class the method is part of. 10 | * If for some reason the method is not part of a class this will be the empty string. 11 | * @param psiMethod the reference to the actual method as part of the PsiTree. 12 | */ 13 | data class TestMethodData(val name: String, val testClassName: String, val psiMethod: PsiMethod) 14 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/messageBundleHandlers/AssertionSuggestionMessageBundleHandler.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.messageBundleHandlers 2 | 3 | import com.intellij.AbstractBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.AssertionSuggestionMessageBundle" 9 | 10 | object AssertionSuggestionMessageBundleHandler : AbstractBundle(BUNDLE) { 11 | 12 | @Suppress("SpreadOperator") 13 | @JvmStatic 14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 15 | getMessage(key, *params) 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/messageBundleHandlers/TestMethodGenerationMessageBundleHandler.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.messageBundleHandlers 2 | 3 | import com.intellij.AbstractBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.TestMethodGenerationMessageBundle" 9 | 10 | object TestMethodGenerationMessageBundleHandler : AbstractBundle(BUNDLE) { 11 | 12 | @Suppress("SpreadOperator") 13 | @JvmStatic 14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 15 | getMessage(key, *params) 16 | } 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/TestMethodUserObject.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.project.Project 5 | 6 | /** 7 | * The user object which is stored inside the node of the test case tree. 8 | * Contains additional information such as the current project and current editor (which can be null) 9 | * 10 | * @param reference reference to the TestMethodData 11 | * @param project Current project, can be null. 12 | * @param editor Current editor, can be null. 13 | */ 14 | data class TestMethodUserObject(val reference: TestMethodData, val project: Project?, val editor: Editor?) 15 | -------------------------------------------------------------------------------- /plugin/src/main/resources/intentionDescriptions/Trace test/description.html: -------------------------------------------------------------------------------- 1 | Using this intention action you can quickly inspect the lines covered by a specific method invocation. If you have Run with Coverage before, this intention action will take you to the method declaration and highlight the covered lines. This relies on information collected during the latest coverage with tracing run.
2 | Therefore for it to work properly you first have to enable tracing by going to Run -> Edit Configurations -> Code coverage and select the Tracing option. Afterward, you have to run the test-suite containing the invocation with Coverage. 3 | -------------------------------------------------------------------------------- /plugin/testdata/expected/AssertionSuggestionServiceTest.testGetAssertionsReturnsNothing.java: -------------------------------------------------------------------------------- 1 | public class MixedTestAndNoTestMethods { 2 | 3 | @Test 4 | public void test() { 5 | getAnInt(); 6 | doNothing(); 7 | } 8 | 9 | @Test 10 | public void testMethod() { 11 | String noun = "Testing"; 12 | String adverb = "awesome"; 13 | methodUnderTest(noun, adverb); 14 | } 15 | 16 | public void methodUnderTest(String a, String b) { 17 | a.append(" is ", b); 18 | } 19 | 20 | public int getAnInt() { 21 | return 1; 22 | } 23 | 24 | public void doNothing() { 25 | return; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/ActionData.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import java.time.LocalDateTime 4 | import java.time.format.DateTimeFormatter 5 | 6 | /** 7 | * Represents a logged action. 8 | * 9 | * @param actionId the action logged 10 | */ 11 | data class ActionData( 12 | val actionId: String, 13 | val dateTime: String = DateTimeFormatter.ISO_DATE_TIME.format(LocalDateTime.now()) 14 | ) { 15 | 16 | /** 17 | * Creates a string representation 18 | * of the DTO that is suitable for 19 | * hashing. 20 | * 21 | * @return the string that can be used 22 | * to generate a hash. 23 | */ 24 | fun toHashString() = "$actionId$dateTime" 25 | } 26 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/models/UsageDataTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.testknight.extensions.TestKnightTestCase 4 | import com.testknight.settings.SettingsService 5 | import org.junit.Test 6 | 7 | class UsageDataTest : TestKnightTestCase() { 8 | 9 | @Test 10 | fun testToHashString() { 11 | val userId = SettingsService.instance.state.userId 12 | val a1 = ActionData("action1") 13 | val a2 = ActionData("action2") 14 | val usageData = UsageData(listOf(a1, a2)) 15 | val hashString = usageData.toHashString() 16 | val expected = "${userId}${a1.toHashString()}${a2.toHashString()}" 17 | assertEquals(hashString, expected) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #This file contains the Spring configuration to be used when running in production 2 | server.port=8080 3 | #MySql Configurations 4 | #spring.datasource.url=jdbc:mysql://databaseServerUrlHere 5 | #spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver 6 | #spring.datasource.username=databaseUsernameHere 7 | #spring.datasource.password=databaseUserPasswordHere 8 | #spring.jpa.hibernate.ddl-auto=update 9 | spring.datasource.url= 10 | spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver 11 | spring.datasource.username= 12 | spring.datasource.password= 13 | spring.jpa.hibernate.ddl-auto=update 14 | #Set those to "true" for debugging 15 | spring.jpa.show-sql=false 16 | spring.jpa.properties.hibernate.format_sql=false 17 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/highlightResolutionStrategies/MagicNumberStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.highlightResolutionStrategies 2 | 3 | import com.intellij.psi.PsiLiteralExpression 4 | import com.intellij.psi.PsiMethod 5 | import com.intellij.psi.util.PsiTreeUtil 6 | import com.testknight.models.HighlightedTextData 7 | 8 | object MagicNumberStrategy : HighlightResolutionStrategy { 9 | 10 | override val priority: Int = 1 11 | 12 | override val settingsName = "Highlight literals" 13 | 14 | override fun getElements(psiMethod: PsiMethod): List { 15 | return PsiTreeUtil.findChildrenOfType(psiMethod, PsiLiteralExpression::class.java) 16 | .map { 17 | HighlightedTextData(it) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/testdata/expected/AssertionSuggestionServiceTest.testGetAssertionsOnReturnType.java: -------------------------------------------------------------------------------- 1 | public class MixedTestAndNoTestMethods { 2 | 3 | @Test 4 | public void test() { 5 | getAnInt(); 6 | doNothing(); 7 | 8 | /** 9 | * Assert that "getAnInt" returns the proper "int". 10 | */ 11 | 12 | } 13 | 14 | @Test 15 | public void testMethod() { 16 | String noun = "Testing"; 17 | String adverb = "awesome"; 18 | methodUnderTest(noun, adverb); 19 | } 20 | 21 | public void methodUnderTest(String a, String b) { 22 | a.append(" is ", b); 23 | } 24 | 25 | public int getAnInt() { 26 | return 1; 27 | } 28 | 29 | public void doNothing() { 30 | return; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/highlightResolutionStrategies/MagicNumberStrategyTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.highlightResolutionStrategies 2 | 3 | import com.intellij.psi.PsiMethod 4 | import com.testknight.extensions.TestKnightTestCase 5 | import junit.framework.TestCase 6 | import org.junit.Test 7 | 8 | internal class MagicNumberStrategyTest : TestKnightTestCase() { 9 | 10 | @Test 11 | fun testBasic() { 12 | val data = getBasicTestInfo("PointTest.java") 13 | 14 | val testMethod = data.psiClass!!.findMethodsByName("translateTest")[0] as PsiMethod 15 | 16 | val toBeChangedTxt = MagicNumberStrategy.getElements(testMethod).map { it.text }.toSet() 17 | 18 | TestCase.assertEquals(toBeChangedTxt, setOf("0", "0", "1", "2", "1", "2")) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/testdata/BrokenClass.java: -------------------------------------------------------------------------------- 1 | //A class containing compilation errors 2 | //for testing purposes. 3 | class BrokenClass { 4 | 5 | private int a = 24; 6 | private int b = 42; 7 | 8 | public int incompleteCondition() { 9 | if () { 10 | return a + b; 11 | } else { 12 | return b; 13 | } 14 | } 15 | 16 | public int conditionalWithLiteral() { 17 | if (true) { 18 | return a; 19 | } 20 | return b; 21 | } 22 | 23 | public int incompleteConditionalExpression(){ 24 | int a=5; 25 | int b=10; 26 | int c = null ? a:b; 27 | return c; 28 | } 29 | 30 | public int conditionalExpressionWithLiteral() { 31 | return true ? a : b; 32 | } 33 | 34 | 35 | 36 | } -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/dataTransferObjects/requests/ActionEventDto.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests; 2 | 3 | 4 | import lombok.Data; 5 | import lombok.AllArgsConstructor; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | public class ActionEventDto implements RequestDto { 12 | 13 | private String actionId; 14 | private LocalDateTime dateTime; 15 | 16 | /** 17 | * Creates a string representation 18 | * of the DTO that is suitable for 19 | * hashing. 20 | * 21 | * @return the string that can be used 22 | * to generate a hash. 23 | */ 24 | @Override 25 | public String toHashString() { 26 | return actionId + dateTime.toString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/highlightResolutionStrategies/AssertionArgsStrategyTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.highlightResolutionStrategies 2 | 3 | import com.intellij.psi.PsiMethod 4 | import com.intellij.testFramework.UsefulTestCase 5 | import com.testknight.extensions.TestKnightTestCase 6 | import org.junit.Test 7 | 8 | internal class AssertionArgsStrategyTest : TestKnightTestCase() { 9 | 10 | @Test 11 | fun testBasic() { 12 | val data = getBasicTestInfo("PointTest.java") 13 | 14 | val testMethod = data.psiClass!!.findMethodsByName("translateTest")[0] as PsiMethod 15 | 16 | val toBeChangedTxt = AssertionArgsStrategy.getElements(testMethod).map { it.text } 17 | 18 | assertContainsElements(toBeChangedTxt, "p1", "p2") 19 | UsefulTestCase.assertSize(2, toBeChangedTxt) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/services/GotoTestService.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.editor.ScrollType 5 | import com.intellij.refactoring.suggested.startOffset 6 | import com.testknight.models.TestMethodData 7 | 8 | /** 9 | * Centers user view to the specified method. 10 | * 11 | * @param editor the current instance of the text editor. 12 | * @param testMethodData custom object containing a reference to the PSI method where the user view should be centered. 13 | */ 14 | class GotoTestService { 15 | 16 | fun gotoMethod(editor: Editor, testMethodData: TestMethodData) { 17 | editor.caretModel.primaryCaret.moveToOffset(testMethodData.psiMethod.startOffset) 18 | editor.scrollingModel.scrollToCaret(ScrollType.CENTER) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/views/UserInterfaceFactory.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.views 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.wm.ToolWindow 5 | import com.intellij.openapi.wm.ToolWindowFactory 6 | 7 | class UserInterfaceFactory : ToolWindowFactory { 8 | /** 9 | * Create the tool window content. 10 | * 11 | * @param project current project 12 | * @param toolWindow current tool window 13 | */ 14 | override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { 15 | val userInterface = UserInterface(project) 16 | val contentManager = toolWindow.contentManager 17 | val content = contentManager.factory.createContent(userInterface.getContent(), null, false) 18 | toolWindow.contentManager.addContent(content) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/highlightResolutionStrategies/ConstructorArgsStrategyTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.highlightResolutionStrategies 2 | 3 | import com.intellij.psi.PsiMethod 4 | import com.intellij.testFramework.UsefulTestCase 5 | import com.testknight.extensions.TestKnightTestCase 6 | import org.junit.Test 7 | 8 | internal class ConstructorArgsStrategyTest : TestKnightTestCase() { 9 | 10 | @Test 11 | fun testGetConstructorArgs() { 12 | val data = getBasicTestInfo("PointTest.java") 13 | 14 | val testMethod = data.psiClass!!.findMethodsByName("translateTest")[0] as PsiMethod 15 | val toBeChangedTxt = ConstructorArgsStrategy.getElements(testMethod).map { it.text } 16 | 17 | assertContainsElements(toBeChangedTxt, "0", "0", "1", "2") 18 | UsefulTestCase.assertSize(4, toBeChangedTxt) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/test/resources/import.sql: -------------------------------------------------------------------------------- 1 | insert into ACTIONS (action_Id) values ('duplicateTest'); 2 | insert into ACTIONS (action_Id) values ('gotoTest'); 3 | insert into ACTIONS (action_Id) values ('suggestAssertion'); 4 | insert into ACTIONS (action_Id) values ('generateChecklist'); 5 | insert into ACTIONS (action_Id) values ('splitDiffView'); 6 | insert into ACTIONS (action_Id) values ('integratedDiffView'); 7 | insert into ACTIONS (action_Id) values ('traceTest'); 8 | insert into ACTIONS (action_Id) values ('generateTest'); 9 | insert into ACTIONS (action_Id) values ('itemMarked'); 10 | insert into ACTIONS (action_Id) values ('itemDeleted'); 11 | insert into ACTIONS (action_Id) values ('runWithCoverage'); 12 | insert into ACTIONS (action_Id) values ('testRun'); 13 | insert into ACTIONS (action_Id) values ('testFail'); 14 | insert into ACTIONS (action_Id) values ('testAdd'); 15 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/extensions/DiffCoverageLineMarkerRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.extensions 2 | 3 | import com.intellij.coverage.LineMarkerRendererWithErrorStripe 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.openapi.editor.markup.LineMarkerRendererEx.Position 6 | import java.awt.Color 7 | import java.awt.Graphics 8 | import java.awt.Rectangle 9 | 10 | class DiffCoverageLineMarkerRenderer(val color: Color) : LineMarkerRendererWithErrorStripe { 11 | 12 | override fun paint(editor: Editor, g: Graphics, r: Rectangle) { 13 | g.color = color 14 | g.fillRect(r.x, r.y, r.width, +r.height) 15 | } 16 | 17 | override fun getPosition(): Position { 18 | return Position.LEFT 19 | } 20 | 21 | override fun getErrorStripeColor(editor: Editor?): Color { 22 | return Color.CYAN 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plugin/testdata/expected/AssertionSuggestionServiceTest.testGetAssertionsWithSideEffects.java: -------------------------------------------------------------------------------- 1 | public class MixedTestAndNoTestMethods { 2 | 3 | @Test 4 | public void test() { 5 | getAnInt(); 6 | doNothing(); 7 | } 8 | 9 | @Test 10 | public void testMethod() { 11 | String noun = "Testing"; 12 | String adverb = "awesome"; 13 | methodUnderTest(noun, adverb); 14 | 15 | /** 16 | * Assert that method "append" modifies argument "noun" properly. 17 | * Assert that method "append" modifies argument "adverb" properly. 18 | */ 19 | 20 | } 21 | 22 | public void methodUnderTest(String a, String b) { 23 | a.append(" is ", b); 24 | } 25 | 26 | public int getAnInt() { 27 | return 1; 28 | } 29 | 30 | public void doNothing() { 31 | return; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/InitializationListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners 2 | 3 | import com.intellij.coverage.CoverageDataManager 4 | import com.intellij.openapi.components.service 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.project.ProjectManagerListener 7 | import com.testknight.extensions.DiffCoverageListener 8 | import com.testknight.services.CoverageDataService 9 | import com.testknight.services.checklist.ChecklistTreeService 10 | 11 | class InitializationListener : ProjectManagerListener { 12 | 13 | override fun projectOpened(project: Project) { 14 | val covDataManager = CoverageDataManager.getInstance(project) 15 | val covDataService = project.service() 16 | project.service().initUiTree() 17 | covDataManager.addSuiteListener(DiffCoverageListener(project), covDataService) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/server.yml: -------------------------------------------------------------------------------- 1 | name: Server verification 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '/server/**/' 7 | push: 8 | branches: 9 | - master 10 | - develop 11 | 12 | jobs: 13 | 14 | build: 15 | name: Compiles the server 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: build 22 | run: gradle assemble 23 | 24 | staticAnalysis: 25 | name: Code style static analysis 26 | runs-on: ubuntu-latest 27 | 28 | steps: 29 | - uses: actions/checkout@v2 30 | 31 | - name: checkStyle 32 | run: gradle checkstyleMain 33 | 34 | - name: PMD 35 | run: gradle pmdMain 36 | 37 | test: 38 | name: Tests the server 39 | runs-on: ubuntu-latest 40 | 41 | steps: 42 | - uses: actions/checkout@v2 43 | 44 | - name: test 45 | run: gradle test 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /.run/Run IDE with Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | 21 | 22 | -------------------------------------------------------------------------------- /server/src/main/resources/populate_database.sql: -------------------------------------------------------------------------------- 1 | -- You can use this script to insert default action ids in the actions table 2 | -- You need to manually run it 3 | insert into actions (action_Id) values ('duplicateTest'); 4 | insert into actions (action_Id) values ('gotoTest'); 5 | insert into actions (action_Id) values ('suggestAssertion'); 6 | insert into actions (action_Id) values ('generateChecklist'); 7 | insert into actions (action_Id) values ('splitDiffView'); 8 | insert into actions (action_Id) values ('integratedDiffView'); 9 | insert into actions (action_Id) values ('traceTest'); 10 | insert into actions (action_Id) values ('generateTest'); 11 | insert into actions (action_Id) values ('itemMarked'); 12 | insert into actions (action_Id) values ('itemDeleted'); 13 | insert into actions (action_Id) values ('runWithCoverage'); 14 | insert into actions (action_Id) values ('testRun'); 15 | insert into actions (action_Id) values ('testFail'); 16 | insert into actions (action_Id) values ('testAdd'); -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/CoverageDiffObject.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.intellij.openapi.vfs.VirtualFile 4 | 5 | @Suppress("LongParameterList") 6 | class CoverageDiffObject( 7 | val allLinesPrev: Set = emptySet(), 8 | val allLinesNow: Set = emptySet(), 9 | val coveredPrev: Set = emptySet(), 10 | val coveredNow: Set = emptySet(), 11 | var prevStamp: Long = 0, 12 | var currStamp: Long = 0, 13 | val virtualFile: VirtualFile? = null 14 | ) { 15 | 16 | val linesNewlyRemoved: Set 17 | val linesNewlyAdded: Set 18 | val linesCoveredInBoth: Set 19 | val linesNotCovered: Set 20 | 21 | init { 22 | linesNewlyRemoved = coveredPrev - coveredNow 23 | linesNewlyAdded = coveredNow - coveredPrev 24 | linesCoveredInBoth = coveredPrev.intersect(coveredNow) 25 | linesNotCovered = (allLinesNow - coveredPrev).intersect(allLinesNow - coveredNow) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/CustomChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.utilities.PsiConverter 9 | 10 | class CustomChecklistNode( 11 | override var description: String = "", 12 | @OptionTag(converter = PsiConverter::class) 13 | override var element: PsiElement? = null, 14 | override var checked: Int = 0 15 | ) : TestingChecklistLeafNode(description, element) { 16 | override fun generateTestMethod(project: Project): PsiMethod { 17 | val methodName = TestMethodGenerationMessageBundleHandler.message("customNodeTestCaseName") 18 | return super.generateTestMethod(project, methodName) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # TestKnight Changelog 4 | 5 | 22 | 23 | ## [1.0.2] 24 | 25 | ### Added 26 | - Added support for build 213. 27 | 28 | ## [1.0.1] 29 | 30 | ### Added 31 | - Added support for build 212. 32 | ### Changed 33 | - Improved checklist generation parsing strategy. 34 | - Changed how notifications are registered to 2020.3 and later version. 35 | ### Removed 36 | - Removed support for build 202. 37 | ### Fixed 38 | - Fix coverage not showing for classes inside a package. 39 | - Fix parameter suggestions tree adding empty class/values. 40 | 41 | ## [1.0.0] 42 | - Initial release. 43 | 44 | -------------------------------------------------------------------------------- /plugin/src/main/resources/messages/TestingChecklistMessageBundle.properties: -------------------------------------------------------------------------------- 1 | switchVariableDefault=Test {0} is different from all the switch cases 2 | switchVariableCase=Test {0} is {1} 3 | tryBlockSuccess=Test with the try block running successfully 4 | tryBlockGeneralException=Test with the try block throwing an exception 5 | tryBlockThrowsSpecificException=Test with the try block throwing a {0} 6 | doWhileMultipleTimes=Test where do-while loop runs multiple times 7 | forEachEmpty=Test where {0} is empty 8 | forEachNull=Test where {0} is null 9 | forEachOnce=Test where {0} has one element 10 | forEachMultiple=Test where foreach loop runs multiple times 11 | forLoopMultiple=Test where for loop runs multiple times 12 | whileLoopMultiple=Test where while loop runs multiple times 13 | conditionBaseMessage=Test where in the condition "{0}", 14 | conditionSingleMessage=Test where 15 | conditionAssignmentMessage="{0}" is {1}, 16 | parameterBaseMessage=Test method parameter "{0}" equal to: {1} 17 | throw=Test when {0} is thrown -------------------------------------------------------------------------------- /.github/workflows/plugin.yml: -------------------------------------------------------------------------------- 1 | name: Plugin verification 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '/plugin/**/' 7 | push: 8 | branches: 9 | - master 10 | - develop 11 | 12 | jobs: 13 | 14 | build: 15 | name: Compiles the plugin 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: build 22 | run: | 23 | gradle assemble 24 | gradle testClasses 25 | 26 | staticAnalysis: 27 | name: Code style static analysis 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | 33 | - name: detekt 34 | run: gradle detekt 35 | 36 | - name: ktlint 37 | run: gradle ktlintCheck 38 | 39 | test: 40 | name: Tests the plugin 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - uses: actions/checkout@v2 45 | 46 | - name: test 47 | run: gradle test 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/highlightResolutionStrategies/ConstructorArgsStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.highlightResolutionStrategies 2 | 3 | import com.intellij.psi.PsiConstructorCall 4 | import com.intellij.psi.PsiMethod 5 | import com.intellij.psi.util.PsiTreeUtil 6 | import com.testknight.models.HighlightedTextData 7 | 8 | object ConstructorArgsStrategy : HighlightResolutionStrategy { 9 | 10 | override val priority = 1 11 | 12 | override val settingsName = "Highlight constructor arguments" 13 | 14 | override fun getElements(psiMethod: PsiMethod): List { 15 | val res = arrayListOf() 16 | 17 | PsiTreeUtil.findChildrenOfType(psiMethod, PsiConstructorCall::class.java) 18 | .forEach { constructorCall -> 19 | constructorCall.argumentList?.expressions?.forEach { 20 | res.add(HighlightedTextData(it)) 21 | } 22 | } 23 | 24 | return res 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugin/testdata/TestDuplication.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class PointTest { 12 | 13 | @Test 14 | void duplicate() { 15 | Point p = new Point(0, 1); 16 | } 17 | 18 | @Test 19 | void containing() { 20 | Point p = new Point(0, somefunc(1, 2)); 21 | } 22 | 23 | @Test 24 | void nestedContains() { 25 | Point p = new Point(0, somefunc(foo(), bar(1, 2, 3)), dar()); 26 | } 27 | 28 | @Test 29 | void strAndChar() { 30 | int s = "string"; 31 | char c = 'c'; 32 | } 33 | 34 | @Test 35 | void hasAll() { 36 | Point p = new Point(0, 0); 37 | 38 | p.x += 1; 39 | p.y += 1; 40 | 41 | assertEquals(1, p.x); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/FileEditorListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners 2 | 3 | import com.intellij.openapi.components.service 4 | import com.intellij.openapi.editor.Editor 5 | import com.intellij.openapi.fileEditor.FileEditorManager 6 | import com.intellij.openapi.fileEditor.FileEditorManagerListener 7 | import com.intellij.openapi.fileEditor.TextEditor 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.vfs.VirtualFile 10 | import com.testknight.services.CoverageHighlighterService 11 | import com.testknight.services.TestTracingService 12 | 13 | class FileEditorListener(val project: Project) : FileEditorManagerListener { 14 | 15 | override fun fileOpened(source: FileEditorManager, file: VirtualFile) { 16 | 17 | val editors = mutableListOf() 18 | source.getEditors(file).forEach { if (it is TextEditor) editors.add(it.editor) } 19 | 20 | project.service().addHighlights(editors) 21 | project.service().addHighlights(editors) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/loopStatements/DoWhileStatementChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes.loopStatements 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 9 | import com.testknight.utilities.PsiConverter 10 | 11 | data class DoWhileStatementChecklistNode( 12 | override var description: String = "", 13 | @OptionTag(converter = PsiConverter::class) 14 | override var element: PsiElement? = null 15 | ) : TestingChecklistLeafNode(description, element) { 16 | override fun generateTestMethod(project: Project): PsiMethod { 17 | val methodName = TestMethodGenerationMessageBundleHandler.message("doWhileTestCaseName") 18 | return super.generateTestMethod(project, methodName) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/services/TestAnalyzerServiceTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.testknight.extensions.TestKnightTestCase 4 | import junit.framework.TestCase 5 | import org.junit.Test 6 | 7 | internal class TestAnalyzerServiceTest : TestKnightTestCase() { 8 | 9 | @Test 10 | fun testIsTestClassTrue() { 11 | val data = getBasicTestInfo("/PointTest.java") 12 | val testAnalyzerService = TestAnalyzerService() 13 | TestCase.assertTrue(testAnalyzerService.isTestClass(data.psiClass!!)) 14 | } 15 | 16 | @Test 17 | fun testIsTestClassFalse() { 18 | val data = getBasicTestInfo("/Person.java") 19 | val testAnalyzerService = TestAnalyzerService() 20 | TestCase.assertFalse(testAnalyzerService.isTestClass(data.psiClass!!)) 21 | } 22 | 23 | @Test 24 | fun testIsTestClassEmptyClass() { 25 | val data = getBasicTestInfo("/EmptyClass.java") 26 | val testAnalyzerService = TestAnalyzerService() 27 | TestCase.assertFalse(testAnalyzerService.isTestClass(data.psiClass!!)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /server/config/pmd/pmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | Add only the desired pmd rules. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/validation/BaseValidator.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.validation; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 5 | 6 | public abstract class BaseValidator implements RequestValidator { 7 | 8 | private RequestValidator next; 9 | 10 | /** 11 | * Sets the next validator in the chain. 12 | * 13 | * @param nextValidator the validator to be used next. 14 | */ 15 | @Override 16 | public void setNext(RequestValidator nextValidator) { 17 | this.next = nextValidator; 18 | } 19 | 20 | /** 21 | * Handles the request. 22 | * 23 | * @param requestDto the request to be handled. 24 | * @throws ValidationException thrown iff the request is invalid. 25 | */ 26 | @Override 27 | public void handle(E requestDto) throws ValidationException { 28 | if (this.next != null) { 29 | next.handle(requestDto); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /plugin/testdata/Tests.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class PointTest { 12 | 13 | @Test 14 | void basic() { 15 | // contents 16 | } 17 | 18 | @Test 19 | public static void hasModifiers() { 20 | // contents 21 | } 22 | 23 | @Test 24 | String hasReturnTy() { 25 | // contents 26 | } 27 | 28 | @Test 29 | void hasParams(int x, int y) { 30 | // contents 31 | int expected = x; 32 | int actual = SomeClass.magic(x, y); 33 | assertEquals(expected, actual); 34 | } 35 | 36 | @Test 37 | void hasTypeParams() { 38 | // contents 39 | } 40 | 41 | @Test 42 | void throwsException() throws Exception { 43 | // contents 44 | } 45 | 46 | @Test 47 | void hasAssertion() { 48 | assertEquals(2, 1 + 1) 49 | } 50 | } -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/dataTransferObjects/requests/UsageDataDto.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests; 2 | 3 | import lombok.Data; 4 | import lombok.AllArgsConstructor; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | public class UsageDataDto implements RequestDto { 11 | 12 | private String userId; 13 | private List actionsRecorded; 14 | private String hash; 15 | 16 | /** 17 | * Creates a string representation 18 | * of the DTO that is suitable for 19 | * hashing. 20 | * 21 | * @return the string that can be used 22 | * to generate a hash. 23 | */ 24 | @Override 25 | public String toHashString() { 26 | StringBuilder builder = new StringBuilder(); 27 | builder.append(userId); 28 | if (actionsRecorded != null) { 29 | for (ActionEventDto actionEventDto : actionsRecorded) { 30 | builder.append(actionEventDto.toHashString()); 31 | } 32 | } 33 | return builder.toString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /documentation/mockups/AssertionSuggestion/DetailedAssertionSuggestionMockup.md: -------------------------------------------------------------------------------- 1 | # Description of the Assertion Suggestions mockup 2 | 3 | - The user can request a list of assertion suggestions for a specific test case by pressing a button near the method name. 4 | - Currently, the icon in the mockup is a "+" icon, but it is just a placeholder. 5 | - This action can also be called by a keyboard hotkey on the code the text cursor currently points to. 6 | - If no assertion suggestion can be made for any reason, a message can pop up in the form of a balloon. 7 | - The list of the assertions suggestions will look like in the following at the bottom of the test case: 8 | ``` 9 | /* 10 | TODO Create an assertion for the attribute x of the classY. 11 | assert(...., x) 12 | 13 | TODO Create an assertion for the attribute x of the classY. 14 | assert(...., x) 15 | 16 | TODO Create an assertion for the attribute x of the classY. 17 | assert(...., x) 18 | */ 19 | ``` 20 | - Using this approach, It is easy for the user to copy the snippet code. 21 | 22 | # Graphical mockup 23 | ### Editor view 24 | ![Full Editor View](./AssertionSuggestionOverallUI.png) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Software Engineering Research Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/ConditionChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.utilities.PsiConverter 9 | 10 | data class ConditionChecklistNode( 11 | override var description: String = "", 12 | @OptionTag(converter = PsiConverter::class) 13 | override var element: PsiElement? = null 14 | ) : TestingChecklistLeafNode(description, element) { 15 | /** 16 | * Generate a test method for a condition statement. 17 | * 18 | * @param project the project. 19 | * @return the PsiMethod representing the test. 20 | */ 21 | override fun generateTestMethod(project: Project): PsiMethod { 22 | val methodName = TestMethodGenerationMessageBundleHandler.message("conditionTestCaseName") 23 | return super.generateTestMethod(project, methodName) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories 2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 3 | 4 | pluginGroup = com.testknight 5 | pluginName = TestKnight 6 | pluginVersion = 1.0.2 7 | pluginSinceBuild = 203 8 | pluginUntilBuild = 213.* 9 | # Plugin Verifier integration -> https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl 10 | # See https://jb.gg/intellij-platform-builds-list for available build versions 11 | pluginVerifierIdeVersions = 2020.3.4, 2021.1.3, 2021.2.1, 2021.3.1 12 | 13 | platformType = IC 14 | platformVersion = 2020.3.4 15 | platformDownloadSources = true 16 | 17 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 18 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 19 | platformPlugins = com.intellij.java, java, coverage 20 | 21 | pluginDescription = The IDE plugin that helps you during testing. 22 | 23 | # Opt-out flag for bundling Kotlin standard library. 24 | # See https://kotlinlang.org/docs/reference/using-gradle.html#dependency-on-the-standard-library for details. 25 | kotlin.stdlib.default.dependency = false 26 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/domain/model/UsageRecord.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.domain.model; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.time.*; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Entity 12 | @Table(name = "USAGE_RECORDS") 13 | public class UsageRecord { 14 | 15 | @Id 16 | @Column(name = "id") 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | @Column(name = "user_Id") 21 | private String userId; 22 | 23 | @ManyToOne 24 | private Action actionId; 25 | 26 | @Column(name = "datetime") 27 | private LocalDateTime dateTime; 28 | 29 | /** 30 | * Creates a new UsageRecord object. 31 | * 32 | * @param userId the id of the user. 33 | * @param actionId the id of the action. 34 | * @param dateTime the date-time of the event. 35 | */ 36 | public UsageRecord(String userId, Action actionId, LocalDateTime dateTime) { 37 | this.userId = userId; 38 | this.actionId = actionId; 39 | this.dateTime = dateTime; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/highlightResolutionStrategies/HighlightResolutionStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.highlightResolutionStrategies 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.psi.PsiMethod 5 | import com.testknight.models.HighlightedTextData 6 | import com.testknight.settings.SettingsService 7 | 8 | interface HighlightResolutionStrategy { 9 | 10 | /** 11 | * Represents the priority of the strategy. Lower is higher priority 12 | */ 13 | val priority: Int 14 | 15 | val settingsName: String 16 | 17 | /** 18 | * @return true if the strategy is enabled in the settings 19 | */ 20 | fun isEnabled() = ApplicationManager 21 | .getApplication() 22 | .getService(SettingsService::class.java) 23 | .state 24 | .testListSettings.highlightStrategies[settingsName]!! 25 | 26 | /** 27 | * Gets a list of PSI elements to be highlighted from a PSI method 28 | * 29 | * @param psiMethod the method 30 | * @return a list of PSI elements to be highlighted 31 | */ 32 | fun getElements(psiMethod: PsiMethod): List 33 | } 34 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/checklist/ClassChecklistIconHandler.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners.checklist 2 | 3 | import com.intellij.codeInsight.daemon.GutterIconNavigationHandler 4 | import com.intellij.openapi.components.service 5 | import com.intellij.psi.PsiElement 6 | import com.testknight.actions.checklist.LoadChecklistAction 7 | import com.testknight.utilities.UserInterfaceHelper 8 | import java.awt.event.MouseEvent 9 | 10 | /** 11 | * Handler for the gutter icons which are used for the class. 12 | */ 13 | class ClassChecklistIconHandler : GutterIconNavigationHandler { 14 | 15 | /** 16 | * This handler generates the checklist for the chosen class. 17 | * 18 | * @param event MouseEvent received from the click 19 | * @param element PsiElement for which you have to generate the checklist 20 | */ 21 | override fun navigate(event: MouseEvent?, element: PsiElement?) { 22 | if (element == null) return 23 | val project = element.project 24 | if (LoadChecklistAction().actionPerformed(project, element.parent)) { 25 | project.service().showTab("Checklist") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/settings/EditElementAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.settings 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.ui.treeStructure.Tree 6 | 7 | class EditElementAction : AnAction() { 8 | 9 | var tree: Tree? = null 10 | /** 11 | * Starts editing at the selected node. 12 | * 13 | * @param e Carries information on the invocation place 14 | */ 15 | override fun actionPerformed(e: AnActionEvent) { 16 | tree!!.startEditingAtPath(tree!!.selectionPath) 17 | } 18 | 19 | /** 20 | * Updates the state of the action. 21 | * The action is enabled only if the tree is not null and atleast one node has been selected. 22 | * 23 | * @param e Carries information on the invocation place 24 | */ 25 | override fun update(e: AnActionEvent) { 26 | e.presentation.isEnabled = (tree != null && tree!!.selectionCount > 0) 27 | } 28 | 29 | /** 30 | * Sets the tree attribute which is used for accessing tree functions. 31 | */ 32 | fun init(tree: Tree) { 33 | this.tree = tree 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/checklist/MethodChecklistIconHandler.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners.checklist 2 | 3 | import com.intellij.codeInsight.daemon.GutterIconNavigationHandler 4 | import com.intellij.openapi.components.service 5 | import com.intellij.psi.PsiElement 6 | import com.testknight.actions.checklist.LoadChecklistAction 7 | import com.testknight.utilities.UserInterfaceHelper 8 | import java.awt.event.MouseEvent 9 | 10 | /** 11 | * Handler for the gutter icons which are used for the methods. 12 | */ 13 | class MethodChecklistIconHandler : GutterIconNavigationHandler { 14 | 15 | /** 16 | * This handler generates the checklist for the chosen method. 17 | * 18 | * @param event MouseEvent received from the click 19 | * @param element PsiElement for which you have to generate the checklist 20 | */ 21 | override fun navigate(event: MouseEvent?, element: PsiElement?) { 22 | if (element == null) return 23 | val project = element.project 24 | if (LoadChecklistAction().actionPerformed(project, element.parent)) { 25 | project.service().showTab("Checklist") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/UsageData.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.testknight.messageBundleHandlers.ServerMessageBundleHandler 4 | import com.testknight.settings.SettingsService 5 | import org.apache.commons.codec.digest.DigestUtils 6 | 7 | /** 8 | * Represents a session usage data 9 | * 10 | * @param actionsRecorded the list of actions to include in the usageData 11 | */ 12 | data class UsageData( 13 | val actionsRecorded: List, 14 | val userId: String = SettingsService.instance.state.userId, 15 | ) { 16 | 17 | // the hash is used to verify the request. 18 | val hash: String = DigestUtils.md5Hex("${toHashString()}${ServerMessageBundleHandler.message("hashString")}") 19 | 20 | /** 21 | * Creates a string representation 22 | * of the DTO that is suitable for 23 | * hashing. 24 | * 25 | * @return the string that can be used 26 | * to generate a hash. 27 | */ 28 | fun toHashString(): String { 29 | val builder = StringBuilder() 30 | builder.append(userId) 31 | for (actionEventDto in actionsRecorded) { 32 | builder.append(actionEventDto.toHashString()) 33 | } 34 | return builder.toString() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/ThrowStatementChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.utilities.PsiConverter 9 | 10 | data class ThrowStatementChecklistNode( 11 | override var description: String = "", 12 | // override val element: PsiThrowStatement, 13 | @OptionTag(converter = PsiConverter::class) 14 | override var element: PsiElement? = null, 15 | val nameOfException: String = "" 16 | ) : TestingChecklistLeafNode(description, element) { 17 | 18 | /** 19 | * Generate a test method for a throw statement. 20 | * 21 | * @param project the project. 22 | * @return the PsiMethod representing the test. 23 | */ 24 | override fun generateTestMethod(project: Project): PsiMethod { 25 | val methodName = TestMethodGenerationMessageBundleHandler.message("throwTestCaseName") 26 | return super.generateTestMethod(project, methodName) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/validation/RequestValidator.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.validation; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 5 | 6 | /** 7 | * The request validator interface. To be implemented 8 | * by all classes of the request validation chain. 9 | * 10 | * @param The type of RequestDto being validated. 11 | * The generic here allows to customize the handle 12 | * method as well as ensure that the next validator 13 | * in the chain handles the same type of request. 14 | */ 15 | public interface RequestValidator { 16 | 17 | /** 18 | * Sets the next validator in the chain. 19 | * 20 | * @param nextValidator the validator to be used next. 21 | */ 22 | public void setNext(RequestValidator nextValidator); 23 | 24 | /** 25 | * Handles the request. 26 | * 27 | * @param requestDto the request to be handled. 28 | * @throws ValidationException thrown iff the request is invalid. 29 | */ 30 | public void handle(E requestDto) throws ValidationException; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/utilities/StringFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.utilities 2 | 3 | class StringFormatter private constructor() { 4 | 5 | companion object { 6 | /** 7 | * Formats the name of the method to be of the form: "methodName". 8 | * 9 | * @param methodName the full name of the method. 10 | * @return a String representing the formatted method name. 11 | */ 12 | fun formatMethodName(methodName: String): String { 13 | return if (methodName.contains(".")) { 14 | methodName.substring(methodName.lastIndexOf(".") + 1) 15 | } else { 16 | methodName 17 | } 18 | } 19 | 20 | /** 21 | * Formats the name of the field that is affected so that 22 | * all fields have the form "this.nameOfField". 23 | * 24 | * @param name the name of the field. 25 | * @return the formatted version of the name. 26 | */ 27 | fun formatClassFieldName(name: String): String { 28 | return if (name.startsWith("this.")) { 29 | name.replaceFirst("this.", "") 30 | } else { 31 | name 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/ParameterChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.utilities.PsiConverter 9 | 10 | data class ParameterChecklistNode( 11 | override var description: String = "", 12 | // override var element: PsiParameter?, 13 | @OptionTag(converter = PsiConverter::class) 14 | override var element: PsiElement? = null, 15 | val parameterName: String = "", 16 | val suggestedValue: String = "" 17 | ) : TestingChecklistLeafNode(description, element) { 18 | /** 19 | * Generate a test method for a parameter. 20 | * 21 | * @param project the project. 22 | * @return the PsiMethod representing the test. 23 | */ 24 | override fun generateTestMethod(project: Project): PsiMethod { 25 | val methodName = TestMethodGenerationMessageBundleHandler.message("parameterTestCaseName") 26 | return super.generateTestMethod(project, methodName) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/loopStatements/ForLoopStatementChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes.loopStatements 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 9 | import com.testknight.utilities.PsiConverter 10 | 11 | data class ForLoopStatementChecklistNode( 12 | override var description: String = "", 13 | @OptionTag(converter = PsiConverter::class) 14 | override var element: PsiElement? = null 15 | ) : TestingChecklistLeafNode(description, element) { 16 | /** 17 | * Generate a test method for a for loop statement. 18 | * 19 | * @param project the project. 20 | * @return the PsiMethod representing the test. 21 | */ 22 | override fun generateTestMethod(project: Project): PsiMethod { 23 | val methodName = TestMethodGenerationMessageBundleHandler.message("forLoopTestCaseName") 24 | return super.generateTestMethod(project, methodName) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/validation/HashValidator.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.validation; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 5 | import com.testknight.TestKnightTelemetryServer.security.*; 6 | 7 | public class HashValidator extends BaseValidator { 8 | 9 | private final Hasher hasher; 10 | private final String magicString = "exampleMagicString"; 11 | 12 | public HashValidator(Hasher hasher) { 13 | this.hasher = hasher; 14 | } 15 | 16 | /** 17 | * Handles the request by checking if the hash 18 | * was generated in a verified client. 19 | * 20 | * @param requestDto the request to be handled. 21 | * @throws ValidationException thrown iff the request is invalid. 22 | */ 23 | @Override 24 | public void handle(UsageDataDto requestDto) throws ValidationException { 25 | String hashedContents = hasher.hash(requestDto.toHashString() + magicString); 26 | if (!hashedContents.equals(requestDto.getHash())) { 27 | throw new InvalidHashException(); 28 | } 29 | super.handle(requestDto); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/settings/DeleteElementAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.settings 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.ui.treeStructure.Tree 6 | import com.testknight.models.ParameterSuggestionTreeModel 7 | 8 | class DeleteElementAction : AnAction() { 9 | var tree: Tree? = null 10 | /** 11 | * Removes the selected node. 12 | * 13 | * @param e Carries information on the invocation place 14 | */ 15 | override fun actionPerformed(e: AnActionEvent) { 16 | (tree!!.model as ParameterSuggestionTreeModel).removePathElement(tree!!.selectionPath) 17 | } 18 | 19 | /** 20 | * Updates the state of the action. 21 | *The action is enabled only if the tree is not null and atleast one node has been selected. 22 | * 23 | * @param e Carries information on the invocation place 24 | */ 25 | override fun update(e: AnActionEvent) { 26 | e.presentation.isEnabled = (tree != null && tree!!.selectionCount > 0) 27 | } 28 | 29 | /** 30 | * Sets the tree attribute which is used for accessing tree functions. 31 | */ 32 | fun init(tree: Tree) { 33 | this.tree = tree 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/loopStatements/WhileStatementChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes.loopStatements 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 9 | import com.testknight.utilities.PsiConverter 10 | 11 | data class WhileStatementChecklistNode( 12 | override var description: String = "", 13 | 14 | @OptionTag(converter = PsiConverter::class) 15 | override var element: PsiElement? = null, 16 | ) : TestingChecklistLeafNode(description, element) { 17 | 18 | /** 19 | * Generate a test method for a while loop statement. 20 | * 21 | * @param project the project. 22 | * @return the PsiMethod representing the test. 23 | */ 24 | override fun generateTestMethod(project: Project): PsiMethod { 25 | val methodName = TestMethodGenerationMessageBundleHandler.message("whileLoopTestCaseName") 26 | return super.generateTestMethod(project, methodName) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.run/Run Plugin Verification.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/loopStatements/ForEachStatementChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes.loopStatements 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 9 | import com.testknight.utilities.PsiConverter 10 | 11 | data class ForEachStatementChecklistNode( 12 | override var description: String = "", 13 | @OptionTag(converter = PsiConverter::class) 14 | override var element: PsiElement? = null, 15 | val iteratedValue: String = "" 16 | ) : TestingChecklistLeafNode( 17 | description, element 18 | ) { 19 | /** 20 | * Generate a test method for a for each statement. 21 | * 22 | * @param project the project. 23 | * @return the PsiMethod representing the test. 24 | */ 25 | override fun generateTestMethod(project: Project): PsiMethod { 26 | val methodName = TestMethodGenerationMessageBundleHandler.message("forEachTestCaseName") 27 | return super.generateTestMethod(project, methodName) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/branchingStatements/TryStatementChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes.branchingStatements 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 9 | import com.testknight.utilities.PsiConverter 10 | 11 | data class TryStatementChecklistNode( 12 | override var description: String = "", 13 | @OptionTag(converter = PsiConverter::class) 14 | override var element: PsiElement? = null, 15 | val exceptionName: String? = "", 16 | val isExceptionThrown: Boolean = exceptionName != null 17 | ) : TestingChecklistLeafNode(description, element) { 18 | 19 | /** 20 | * Generate a test method for a try statement. 21 | * 22 | * @param project the project. 23 | * @return the PsiMethod representing the test. 24 | */ 25 | override fun generateTestMethod(project: Project): PsiMethod { 26 | val methodName = TestMethodGenerationMessageBundleHandler.message("tryTestCaseName") 27 | return super.generateTestMethod(project, methodName) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/testingChecklist/leafNodes/branchingStatements/SwitchStatementChecklistNode.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.testingChecklist.leafNodes.branchingStatements 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiMethod 6 | import com.intellij.util.xmlb.annotations.OptionTag 7 | import com.testknight.messageBundleHandlers.TestMethodGenerationMessageBundleHandler 8 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 9 | import com.testknight.utilities.PsiConverter 10 | 11 | data class SwitchStatementChecklistNode( 12 | override var description: String = "", 13 | @OptionTag(converter = PsiConverter::class) 14 | override var element: PsiElement? = null, 15 | val switchVariable: String = "", 16 | val value: String? = "", 17 | val isDefaultCase: Boolean = value == null 18 | ) : TestingChecklistLeafNode(description, element) { 19 | /** 20 | * Generate a test method for a switch statement. 21 | * 22 | * @param project the project. 23 | * @return the PsiMethod representing the test. 24 | */ 25 | override fun generateTestMethod(project: Project): PsiMethod { 26 | val methodName = TestMethodGenerationMessageBundleHandler.message("switchTestCaseName") 27 | return super.generateTestMethod(project, methodName) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/models/TruthTableTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import org.junit.Test 4 | import kotlin.test.assertEquals 5 | 6 | internal class TruthTableTest { 7 | 8 | @Test 9 | fun testBasic() { 10 | val table = TruthTable(listOf("a", "b"), "a && b") 11 | 12 | assertEquals(table.value(0), false) 13 | assertEquals(table.value(1), false) 14 | assertEquals(table.value(2), false) 15 | assertEquals(table.value(3), true) 16 | } 17 | 18 | @Test 19 | fun testNonlazy() { 20 | val table = TruthTable(listOf("a", "b"), "a & b") 21 | 22 | assertEquals(table.value(0), false) 23 | assertEquals(table.value(1), false) 24 | assertEquals(table.value(2), false) 25 | assertEquals(table.value(3), true) 26 | } 27 | 28 | @Test 29 | fun testXor() { 30 | val table = TruthTable(listOf("a", "b"), "a ^ b") 31 | 32 | assertEquals(table.value(0), false) 33 | assertEquals(table.value(1), true) 34 | assertEquals(table.value(2), true) 35 | assertEquals(table.value(3), false) 36 | } 37 | 38 | @Test 39 | fun testAssignments() { 40 | val table = TruthTable(listOf("a", "b"), "a && b") 41 | 42 | val assignments = table.assignments(3) 43 | 44 | assertEquals(assignments["a"], true) 45 | assertEquals(assignments["b"], true) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/settings/AddClassAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.settings 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.ui.treeStructure.Tree 6 | import com.testknight.models.ParameterSuggestionTreeModel 7 | import javax.swing.tree.TreePath 8 | 9 | class AddClassAction : AnAction() { 10 | var tree: Tree? = null 11 | 12 | /** 13 | * Adds a new class node and starts editing the class node. 14 | * 15 | * @param e Carries information on the invocation place 16 | */ 17 | override fun actionPerformed(e: AnActionEvent) { 18 | 19 | val path = TreePath((tree!!.model as ParameterSuggestionTreeModel).root).pathByAddingChild("NewClass") 20 | (tree!!.model as ParameterSuggestionTreeModel).addPathElement(path) 21 | tree!!.startEditingAtPath(path) 22 | } 23 | 24 | /** 25 | * Updates the state of the action. 26 | * The action is enabled only if the tree is not null. 27 | * 28 | * @param e Carries information on the invocation place 29 | */ 30 | override fun update(e: AnActionEvent) { 31 | e.presentation.isEnabled = (tree != null) 32 | } 33 | 34 | /** 35 | * Sets the tree attribute which is used for accessing tree functions. 36 | */ 37 | fun init(tree: Tree) { 38 | this.tree = tree 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/checklist/ClearChecklistAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.checklist 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.components.service 6 | import com.intellij.openapi.project.Project 7 | import com.testknight.services.checklist.ChecklistTreeService 8 | 9 | class ClearChecklistAction : AnAction() { 10 | 11 | /** 12 | * Clears the Checklist tree. 13 | * 14 | * @param event Event received when the associated menu item is chosen. 15 | */ 16 | override fun actionPerformed(event: AnActionEvent) { 17 | actionPerformed(event.project!!) 18 | } 19 | 20 | /** 21 | * Clears the Checklist tree. 22 | * 23 | * @param project current open project. 24 | */ 25 | private fun actionPerformed(project: Project) { 26 | val checklistTreeService = project.service() 27 | checklistTreeService.resetTree() 28 | } 29 | 30 | /** 31 | * Determines whether this action button is available for the current context. 32 | * Requires a project to be open. 33 | * 34 | * @param e Event received when the associated group-id menu is chosen. 35 | */ 36 | override fun update(e: AnActionEvent) { 37 | // Set the availability if project is open 38 | e.presentation.isEnabled = e.project != null 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/highlightResolutionStrategies/AssertionArgsStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.highlightResolutionStrategies 2 | 3 | import com.intellij.psi.PsiMethod 4 | import com.intellij.psi.PsiMethodCallExpression 5 | import com.intellij.psi.util.PsiTreeUtil 6 | import com.testknight.models.HighlightedTextData 7 | 8 | object AssertionArgsStrategy : HighlightResolutionStrategy { 9 | 10 | /** 11 | * A set of recognized assertion names 12 | */ 13 | private val assertionNames = setOf( 14 | "assertEquals", 15 | "assertTrue", 16 | "assertFalse", 17 | "assertNotNull", 18 | "assertNull", 19 | "assertSame", 20 | "assertNotSame" 21 | ) 22 | 23 | override val priority: Int = 2 24 | 25 | override val settingsName = "Highlight assertion statements" 26 | 27 | override fun getElements(psiMethod: PsiMethod): List { 28 | 29 | val res = arrayListOf() 30 | 31 | PsiTreeUtil.findChildrenOfType(psiMethod, PsiMethodCallExpression::class.java) 32 | .forEach { methodCall -> 33 | if (assertionNames.contains(methodCall.methodExpression.qualifiedName)) { 34 | methodCall.argumentList.expressions.forEach { 35 | res.add(HighlightedTextData(it)) 36 | } 37 | } 38 | } 39 | 40 | return res 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/services/TestAnalyzerService.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.intellij.psi.PsiClass 4 | import com.intellij.psi.PsiMethod 5 | import com.intellij.psi.util.PsiTreeUtil 6 | 7 | class TestAnalyzerService { 8 | 9 | /** 10 | * A list of all recognized test annotations 11 | */ 12 | private val testAnnotations = setOf( 13 | "Test", 14 | "ParameterizedTest", 15 | "org.junit.jupiter.api.Test", 16 | "org.junit.jupiter.api.ParameterizedTest", 17 | "org.junit.Test", 18 | "org.junit.ParameterizedTest" 19 | ) 20 | 21 | /** 22 | * Checks if a Psi class is a test class. 23 | * 24 | * @param psiClass the PsiClass to be examined. 25 | * @return true iff the given class contains a test method. 26 | */ 27 | fun isTestClass(psiClass: PsiClass): Boolean { 28 | val methods = PsiTreeUtil.findChildrenOfType(psiClass, PsiMethod::class.java) 29 | methods.forEach { if (isTestMethod(it)) return true } 30 | return false 31 | } 32 | 33 | /** 34 | * Checks if a PSI method is a test method. 35 | * 36 | * @param method the PSI method 37 | * @return true iff the method is a test method 38 | */ 39 | fun isTestMethod(method: PsiMethod): Boolean { 40 | val annotations = method.annotations.map { it.qualifiedName }.toSet() 41 | return annotations.intersect(testAnnotations).isNotEmpty() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /documentation/mockups/CodeCoverage/DetailedCodeCoverage.md: -------------------------------------------------------------------------------- 1 | # Description of the Coverage mockup 2 | 3 | - The Tool window will contain useful information regarding the coverage history. For example, the old and new branch coverage %. 4 | - The image gives a rough idea about the structure of the layout. The user can expand on a test method to see old coverage information as well. 5 | - The main editor which contains the source code will highlight the changes in coverages. 6 | - The new coverage will be highlighted light green. 7 | - The old coverage which didn't change will be highlighted in dark green. 8 | - The old coverage which is no longer covered will be highlighted in red. 9 | - Every other case will not have any highlights. 10 | - This is not the final choice for the colors. The colors can be changed after considering potential issues due to color blindness. 11 | - The user can also request a new diff tab where the old and new coverage will be shown side by side with the usual IntelliJ coverage color scheme. Here any changes in the source code can also be shown. 12 | - The image shows the diff window IntelliJ uses. No modification to their UI has been made there and it already showcases all the necessary diff information. 13 | - The user can request these information by pressing a button in the tool window or by using an action hotkey. 14 | 15 | # Graphical mockup 16 | ###Full Window View 17 | ![Full Window View](./CoverageFullWindowView.png) 18 | ###Diff View 19 | ![Diff View](./CoverageDiffView.png) -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/validation/ContentValidator.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.validation; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 5 | import com.testknight.TestKnightTelemetryServer.repositories.*; 6 | 7 | public class ContentValidator extends BaseValidator { 8 | 9 | private ActionRepository actionRepository; 10 | 11 | public ContentValidator(ActionRepository actionRepository) { 12 | this.actionRepository = actionRepository; 13 | } 14 | 15 | /** 16 | * Handles the request. 17 | * 18 | * @param requestDto the request to be handled. 19 | * @throws ValidationException thrown iff the request is invalid. 20 | */ 21 | @Override 22 | public void handle(UsageDataDto requestDto) throws ValidationException { 23 | if (requestDto.getActionsRecorded() == null) { 24 | throw new NullFieldException("actionsRecorded"); 25 | } 26 | if (requestDto.getUserId() == null) { 27 | throw new NullFieldException("userId"); 28 | } 29 | for (ActionEventDto actionEventDto : requestDto.getActionsRecorded()) { 30 | if (!actionRepository.existsById(actionEventDto.getActionId())) { 31 | throw new InvalidActionIdException(actionEventDto.getActionId()); 32 | } 33 | } 34 | super.handle(requestDto); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/views/coverage/CoverageStatsCellRenderer.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.views.coverage 2 | 3 | import com.intellij.ui.ColoredTableCellRenderer 4 | import com.intellij.ui.JBColor 5 | import com.intellij.ui.SimpleTextAttributes 6 | import com.testknight.models.CoverageStatsObject 7 | import javax.swing.JTable 8 | 9 | class CoverageStatsCellRenderer : ColoredTableCellRenderer() { 10 | override fun customizeCellRenderer( 11 | table: JTable, 12 | value: Any?, 13 | selected: Boolean, 14 | hasFocus: Boolean, 15 | row: Int, 16 | column: Int 17 | ) { 18 | 19 | if (value is CoverageStatsObject) { 20 | 21 | append("${value.percentageCovered}%") 22 | val percentChange = value.percentChange 23 | if (percentChange > 0) { 24 | append( 25 | " (+$percentChange)%", 26 | SimpleTextAttributes( 27 | SimpleTextAttributes.STYLE_SMALLER, 28 | JBColor.GREEN 29 | ) 30 | ) 31 | } else if (percentChange < 0) { 32 | append( 33 | " ($percentChange)%", 34 | SimpleTextAttributes( 35 | SimpleTextAttributes.STYLE_SMALLER, 36 | JBColor.RED 37 | ) 38 | ) 39 | } else { 40 | append(" (+0)%", SimpleTextAttributes.GRAY_SMALL_ATTRIBUTES) 41 | } 42 | 43 | append(" (${value.coveredLines}/${value.allLines})") 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/testdata/Dijkstra.java: -------------------------------------------------------------------------------- 1 | public class Dijkstra { 2 | 3 | /** 4 | * Dijkstra implementation 5 | * 6 | * @param s start node index 7 | * @param n number of nodes 8 | * @param edges set of edges 9 | * @return shortest distance from s to each node 10 | */ 11 | public static int[] dijkstra(int s, int n, Set edges) { 12 | 13 | PriorityQueue pq = new PriorityQueue<>(); 14 | 15 | int[] distances = new int[n]; 16 | 17 | // set distances to all nodes to infinity 18 | for (int i=0; i 0 && tree!!.selectionPath!!.pathCount >= 2) 36 | } 37 | 38 | /** 39 | * Sets the tree attribute which is used for accessing tree functions. 40 | */ 41 | fun init(tree: Tree) { 42 | this.tree = tree 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/testlist/ClearTestAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.testlist 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.components.service 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.ui.treeStructure.Tree 8 | import com.testknight.utilities.UserInterfaceHelper 9 | import javax.swing.tree.DefaultMutableTreeNode 10 | import javax.swing.tree.DefaultTreeModel 11 | 12 | class ClearTestAction : AnAction() { 13 | 14 | /** 15 | * Clears the TestList tree. 16 | * 17 | * @param event Event received when the associated menu item is chosen. 18 | */ 19 | override fun actionPerformed(event: AnActionEvent) { 20 | actionPerformed(event.project!!) 21 | } 22 | 23 | /** 24 | * Clears the TestList tree. 25 | * 26 | * @param project current open project. 27 | */ 28 | fun actionPerformed(project: Project) { 29 | val viewport = project.service().getTabViewport("Test List") ?: return 30 | val testListTree = viewport.view as Tree 31 | val root = testListTree.model.root as DefaultMutableTreeNode 32 | root.removeAllChildren() 33 | (testListTree.model as DefaultTreeModel).reload() 34 | } 35 | 36 | /** 37 | * Determines whether this action button is available for the current context. 38 | * Requires a project to be open. 39 | * 40 | * @param e Event received when the associated group-id menu is chosen. 41 | */ 42 | override fun update(e: AnActionEvent) { 43 | // Set the availability if project is open 44 | e.presentation.isEnabled = e.project != null 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/services/checklist/ChecklistTreePersistent.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services.checklist 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.State 5 | import com.intellij.openapi.components.Storage 6 | import com.intellij.openapi.project.Project 7 | import com.testknight.models.testingChecklist.TestingChecklist 8 | 9 | @State(name = "ChecklistTreePersistent", storages = [Storage("checklistData.xml")]) 10 | class ChecklistTreePersistent(val project: Project) : PersistentStateComponent { 11 | 12 | private var testingChecklistTree: TestingChecklist = TestingChecklist(mutableListOf()) 13 | 14 | /** 15 | * @return a component state. All properties, public and annotated fields are serialized. Only values, which differ 16 | * from the default (i.e., the value of newly instantiated class) are serialized. `null` value indicates 17 | * that the returned state won't be stored, as a result previously stored state will be used. 18 | * @see com.intellij.util.xmlb.XmlSerializer 19 | */ 20 | override fun getState(): TestingChecklist { 21 | return testingChecklistTree 22 | } 23 | 24 | /** 25 | * This method is called when new component state is loaded. The method can and will be called several times, if 26 | * config files were externally changed while IDE was running. 27 | * 28 | * 29 | * State object should be used directly, defensive copying is not required. 30 | * 31 | * @param state loaded component state 32 | * @see com.intellij.util.xmlb.XmlSerializerUtil.copyBean 33 | */ 34 | override fun loadState(state: TestingChecklist) { 35 | testingChecklistTree = state 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /plugin/testdata/PointTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class PointTest { 12 | 13 | @BeforeAll 14 | public static void before() throws Exception { 15 | return; 16 | } 17 | 18 | @AfterAll 19 | public static void after() throws Exception { 20 | return; 21 | } 22 | 23 | @Test 24 | void translateTest() { 25 | Point p1 = new Point(0, 0); 26 | Point p2 = new Point(1, 2); 27 | p1.translate(1, 2); 28 | assertEquals(p1, p2); 29 | } 30 | 31 | 32 | @Test 33 | void setXTest() { 34 | Point p = new Point(0, 0); 35 | p.setX(5); 36 | assertEquals(5, p.getX()); 37 | } 38 | 39 | @Test 40 | void setYTest() { 41 | Point p = new Point(0, 0); 42 | p.setY(5); 43 | assertEquals(5, p.getY()); 44 | } 45 | 46 | @ParameterizedTest(name = "x1={0}, y1={1}, x2={2}, y2={3}, x3={4}, y3={5}") 47 | @CsvSource({ 48 | "1,1,0,0,1,1", 49 | "-1,-1,1,1,0,0", 50 | "1,2,0,3,1,5" 51 | }) 52 | void parameterizedTest(int x1, int y1, int x2, int y2, int x3, int y3) { 53 | Point p = new Point(x1, y1); 54 | Point expected = new Point(x3, y3); 55 | p.translate(x2, y2); 56 | assertEquals(expected, p); 57 | } 58 | 59 | // this is not a test, should not be returned 60 | void notATest() { 61 | return; 62 | } 63 | 64 | void simple() { 65 | assertTrue(true); 66 | } 67 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/services/ExceptionHandlerService.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.intellij.notification.NotificationGroupManager 4 | import com.intellij.notification.NotificationType 5 | import com.intellij.openapi.project.Project 6 | import com.testknight.exceptions.TestKnightException 7 | 8 | class ExceptionHandlerService(private val project: Project) { 9 | 10 | /** 11 | * Display a notification with the provided title and content. 12 | * The type will represents the way that notification should be displayed: Warning, Error and Information. 13 | * 14 | * @param title String which represents the title of the Notification 15 | * @param content String which represents the content of the Notification 16 | * @param type String which represents the Icon type (WARNING, ERROR and INFORMATION) 17 | */ 18 | fun notify(title: String, content: String, type: NotificationType) { 19 | 20 | NotificationGroupManager.getInstance() 21 | .getNotificationGroup("testknight.notifications") 22 | .createNotification(title, content, type) 23 | .notify(project) 24 | } 25 | 26 | /** 27 | * Display a notification with the provided title and content. 28 | * The type will represents the way that notification should be displayed: Warning, Error and Information. 29 | * 30 | * @param exception the TestKnightException for which the user should be notified 31 | */ 32 | fun notify(exception: TestKnightException) { 33 | NotificationGroupManager.getInstance() 34 | .getNotificationGroup("testknight.notifications") 35 | .createNotification(exception.title, exception.content, exception.type) 36 | .notify(project) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/extensions/TestKnightTestCase.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.extensions 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.psi.PsiClass 6 | import com.intellij.psi.PsiFile 7 | import com.intellij.psi.PsiMethod 8 | import com.intellij.psi.util.PsiTreeUtil 9 | import com.intellij.testFramework.fixtures.BasePlatformTestCase 10 | import com.testknight.settings.SettingsService 11 | import org.jetbrains.annotations.NotNull 12 | 13 | open class TestKnightTestCase : BasePlatformTestCase() { 14 | 15 | override fun setUp() { 16 | super.setUp() 17 | SettingsService.instance.resetState() 18 | } 19 | 20 | override fun getTestDataPath() = "testdata" 21 | 22 | fun getBasicTestInfo(filepath: String): Data { 23 | myFixture.configureByFile(filepath) 24 | val project = myFixture.project 25 | val psiClass = PsiTreeUtil.findChildOfType(myFixture.file, PsiClass::class.java) 26 | val psiFile = myFixture.file 27 | val testClasses = PsiTreeUtil.findChildrenOfType(psiFile, PsiClass::class.java) 28 | val editor = myFixture.editor 29 | return Data(filepath, project, psiClass, psiFile, testClasses, editor) 30 | } 31 | 32 | fun getMethodByName(methodName: String): PsiMethod { 33 | val psi = this.myFixture.file 34 | val testClass = PsiTreeUtil.findChildOfType(psi, PsiClass::class.java) 35 | return testClass!!.findMethodsByName(methodName)[0] as PsiMethod 36 | } 37 | } 38 | 39 | data class Data( 40 | var filepath: String, 41 | var project: Project, 42 | var psiClass: PsiClass?, 43 | var psiFile: PsiFile, 44 | val testClasses: @NotNull MutableCollection, 45 | val editor: Editor 46 | ) 47 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/extensions/DiffCoverageListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.extensions 2 | 3 | import com.intellij.coverage.CoverageDataManager 4 | import com.intellij.coverage.CoverageSuiteListener 5 | import com.intellij.ide.DataManager 6 | import com.intellij.openapi.actionSystem.ActionManager 7 | import com.intellij.openapi.actionSystem.AnActionEvent 8 | import com.intellij.openapi.components.service 9 | import com.intellij.openapi.project.Project 10 | import com.testknight.services.CoverageDataService 11 | import com.testknight.services.CoverageHighlighterService 12 | import com.testknight.settings.SettingsService 13 | 14 | class DiffCoverageListener(val project: Project) : CoverageSuiteListener { 15 | 16 | private val covDataService = project.service() 17 | private val covDataManager = CoverageDataManager.getInstance(project) 18 | 19 | override fun beforeSuiteChosen() { 20 | // pass 21 | } 22 | 23 | override fun afterSuiteChosen() { 24 | 25 | val suite = covDataManager.currentSuitesBundle 26 | val data = suite.coverageData 27 | 28 | covDataService.updateCoverage(suite, data) 29 | DataManager.getInstance().dataContextFromFocusAsync 30 | .onSuccess { 31 | val actionEvent = AnActionEvent.createFromDataContext("DiffCoverageListener", null, it) 32 | val action = ActionManager.getInstance().getAction("LoadCoverageAction") 33 | 34 | if (actionEvent.project != null) { 35 | action.actionPerformed(actionEvent) 36 | } 37 | } 38 | 39 | if (SettingsService.state.coverageSettings.showIntegratedView) { 40 | 41 | project.service().rebuildHighlights() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/services/TestTracingServiceTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.intellij.testFramework.UsefulTestCase 4 | import com.intellij.util.ThrowableRunnable 5 | import com.testknight.exceptions.CorruptedTraceFileException 6 | import com.testknight.extensions.TestKnightTestCase 7 | import com.testknight.models.TestCoverageData 8 | import org.junit.jupiter.api.Test 9 | import java.io.File 10 | 11 | internal class TestTracingServiceTest : TestKnightTestCase() { 12 | 13 | @Test 14 | fun testTraceFileReading() { 15 | val service = TestTracingService(project) 16 | 17 | val file = File("testdata/traceFiles/PointTest,test.tr") 18 | 19 | val coverageData = service.readTraceFile(file) 20 | 21 | assertContainsElements(coverageData.classes["Point"]!!, 8, 9, 10, 11, 14, 30, 31, 32, 50, 54) 22 | } 23 | 24 | @Test 25 | fun testInvalidTraceFileReading() { 26 | val service = TestTracingService(project) 27 | 28 | val file = File("testdata/traceFiles/BadPointTest,test.tr") 29 | 30 | assertThrows( 31 | CorruptedTraceFileException::class.java, 32 | ThrowableRunnable { 33 | service.readTraceFile(file) 34 | } 35 | ) 36 | } 37 | 38 | fun testHighlightEditor() { 39 | val service = TestTracingService(project) 40 | 41 | val covData = TestCoverageData("test") 42 | covData.classes["test.Person"] = mutableListOf(1, 3, 3) 43 | service.activeCovData = covData 44 | 45 | myFixture.configureByFile("Person.java") 46 | val editor = myFixture.editor 47 | 48 | service.highlightEditor(editor) 49 | 50 | UsefulTestCase.assertSize(3, editor.markupModel.allHighlighters) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/domain/factories/UsageRecordFactory.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.domain.factories; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 5 | import com.testknight.TestKnightTelemetryServer.domain.model.*; 6 | import org.springframework.stereotype.*; 7 | 8 | import java.util.*; 9 | 10 | @Component 11 | public class UsageRecordFactory { 12 | 13 | /** 14 | * Creates a list of UsageRecords from a UsageDataDto. 15 | * 16 | * @param usageDataDto the DTO to create from. 17 | * @return a list of UsageRecord objects. 18 | */ 19 | public List createUsageRecordFromDto(UsageDataDto usageDataDto) { 20 | validateNonNullFields(usageDataDto); 21 | ArrayList usageRecords = new ArrayList<>(); 22 | for (ActionEventDto actionEventDto : usageDataDto.getActionsRecorded()) { 23 | usageRecords.add( 24 | new UsageRecord( 25 | usageDataDto.getUserId(), 26 | new Action(actionEventDto.getActionId()), 27 | actionEventDto.getDateTime() 28 | ) 29 | ); 30 | } 31 | return usageRecords; 32 | } 33 | 34 | private void validateNonNullFields(UsageDataDto dto) throws NullFieldException { 35 | if (dto.getUserId() == null) { 36 | throw new NullFieldException("userId"); 37 | } else if (dto.getHash() == null) { 38 | throw new NullFieldException("hash"); 39 | } else if (dto.getActionsRecorded() == null) { 40 | throw new NullFieldException("actionsRecorded"); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /plugin/testdata/expected/Dijkstra.testDijkstraTest.java: -------------------------------------------------------------------------------- 1 | public class Dijkstra { 2 | 3 | /** 4 | * Dijkstra implementation 5 | * 6 | * @param s start node index 7 | * @param n number of nodes 8 | * @param edges set of edges 9 | * @return shortest distance from s to each node 10 | */ 11 | public static int[] dijkstra(int s, int n, Set edges) { 12 | 13 | PriorityQueue pq = new PriorityQueue<>(); 14 | 15 | int[] distances = new int[n]; 16 | 17 | // set distances to all nodes to infinity 18 | for (int i=0; i { 10 | 11 | companion object Factory { 12 | /** 13 | * Creates a new ThrowStatementChecklistGenerationStrategy object. 14 | * 15 | * @return a ThrowStatementChecklistGenerationStrategy object. 16 | */ 17 | fun create(): ThrowStatementChecklistGenerationStrategy { 18 | return ThrowStatementChecklistGenerationStrategy() 19 | } 20 | } 21 | 22 | /** 23 | * Generates the checklist for a given throw statement. 24 | * 25 | * @param psiElement the throw statement. 26 | * @return a list of TestingChecklistLeafNode objects corresponding to the required checklist items. 27 | */ 28 | 29 | override fun generateChecklist(psiElement: PsiThrowStatement): List { 30 | val newExpression = (psiElement.exception as? PsiNewExpression) 31 | val nameOfException = newExpression?.classReference?.qualifiedName 32 | if (newExpression == null || nameOfException == null) return emptyList() 33 | return listOf( 34 | ThrowStatementChecklistNode( 35 | TestingChecklistMessageBundleHandler.message("throw", nameOfException), 36 | psiElement, 37 | nameOfException 38 | ) 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugin/testdata/PointTestFullAnnotations.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class PointTest { 12 | 13 | @BeforeAll 14 | public static void before() throws Exception { 15 | return; 16 | } 17 | 18 | @AfterAll 19 | public static void after() throws Exception { 20 | return; 21 | } 22 | 23 | @org.junit.jupiter.api.Test 24 | void translateTest() { 25 | Point p1 = new Point(0, 0); 26 | Point p2 = new Point(1, 2); 27 | p1.translate(1, 2); 28 | assertEquals(p1, p2); 29 | } 30 | 31 | 32 | @org.junit.jupiter.api.Test 33 | void setXTest() { 34 | Point p = new Point(0, 0); 35 | p.setX(5); 36 | assertEquals(5, p.getX()); 37 | } 38 | 39 | @org.junit.jupiter.api.Test 40 | void setYTest() { 41 | Point p = new Point(0, 0); 42 | p.setY(5); 43 | assertEquals(5, p.getY()); 44 | } 45 | 46 | @org.junit.jupiter.api.ParameterizedTest(name = "x1={0}, y1={1}, x2={2}, y2={3}, x3={4}, y3={5}") 47 | @CsvSource({ 48 | "1,1,0,0,1,1", 49 | "-1,-1,1,1,0,0", 50 | "1,2,0,3,1,5" 51 | }) 52 | void parameterizedTest(int x1, int y1, int x2, int y2, int x3, int y3) { 53 | Point p = new Point(x1, y1); 54 | Point expected = new Point(x3, y3); 55 | p.translate(x2, y2); 56 | assertEquals(expected, p); 57 | } 58 | 59 | // this is not a test, should not be returned 60 | void notATest() { 61 | return; 62 | } 63 | 64 | void simple() { 65 | assertTrue(true); 66 | } 67 | } -------------------------------------------------------------------------------- /server/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.5.0' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | 6 | id 'io.freefair.lombok' version "6.0.0-m2" 7 | 8 | id 'jacoco' 9 | id 'checkstyle' 10 | id 'pmd' 11 | } 12 | 13 | group = 'com.testknight' 14 | version = '0.0.1-SNAPSHOT' 15 | sourceCompatibility = '11' 16 | 17 | configurations { 18 | compileOnly { 19 | extendsFrom annotationProcessor 20 | } 21 | } 22 | 23 | repositories { 24 | mavenCentral() 25 | } 26 | 27 | dependencies { 28 | implementation 'org.springframework.boot:spring-boot-starter-web' 29 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 30 | implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' 31 | implementation group: 'commons-codec', name: 'commons-codec', version: '1.5' 32 | compileOnly 'org.projectlombok:lombok:1.18.20' 33 | runtimeOnly 'com.h2database:h2' 34 | runtimeOnly 'mysql:mysql-connector-java' 35 | annotationProcessor 'org.projectlombok:lombok:1.18.20' 36 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 37 | } 38 | 39 | jacoco { 40 | toolVersion = "0.8.6" 41 | } 42 | 43 | test { 44 | useJUnitPlatform() 45 | jacoco { 46 | enabled = true 47 | } 48 | } 49 | 50 | jacocoTestCoverageVerification() { 51 | dependsOn test 52 | violationRules { 53 | rule { 54 | enabled = true 55 | element = 'CLASS' 56 | includes = ['com.testknight.server.*'] 57 | 58 | limit { 59 | counter = 'BRANCH' 60 | value = 'COVEREDRATIO' 61 | minimum = 0.1 62 | } 63 | } 64 | } 65 | } 66 | 67 | 68 | checkstyle { 69 | toolVersion "8.37" 70 | configFile = file("${rootDir}/server/config/checkstyle/checkstyle.xml") 71 | } 72 | 73 | pmd { 74 | incrementalAnalysis = true 75 | ruleSetFiles = files("${rootDir}/server/config/pmd/pmd.xml") 76 | ruleSets = [] 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /plugin/src/main/resources/icons/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 13 | 16 | 21 | 26 | 30 | 31 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/checklist/ChecklistClassLineMarkerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.checklist 2 | 3 | import com.intellij.codeInsight.daemon.LineMarkerInfo 4 | import com.intellij.codeInsight.daemon.LineMarkerProvider 5 | import com.intellij.icons.AllIcons 6 | import com.intellij.openapi.editor.markup.GutterIconRenderer 7 | import com.intellij.psi.PsiClass 8 | import com.intellij.psi.PsiElement 9 | import com.intellij.psi.PsiIdentifier 10 | import com.testknight.listeners.checklist.ClassChecklistIconHandler 11 | import com.testknight.services.TestAnalyzerService 12 | import com.testknight.settings.SettingsService 13 | 14 | /** 15 | * The Line Marker which is on the same line as the class declaration 16 | */ 17 | class ChecklistClassLineMarkerProvider : LineMarkerProvider { 18 | 19 | private val testAnalyzerService = TestAnalyzerService() 20 | 21 | private fun settingsState() = SettingsService.instance.state 22 | 23 | private fun isClassDeclaration(element: PsiElement) = (element is PsiIdentifier && element.parent is PsiClass) 24 | 25 | /** 26 | * The method which creates the Line Marker for the class 27 | * @param element PsiElement for which we have to build the Line Marker 28 | */ 29 | override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { 30 | 31 | if (isClassDeclaration(element) && 32 | !testAnalyzerService.isTestClass(element.parent as PsiClass) && 33 | settingsState().checklistSettings.showGutterIcons 34 | ) { 35 | return LineMarkerInfo( 36 | element, 37 | element.textRange, 38 | AllIcons.General.Add, 39 | { el: PsiElement -> "Generate checklist" }, 40 | ClassChecklistIconHandler(), GutterIconRenderer.Alignment.RIGHT 41 | ) 42 | } 43 | return null 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/checklist/ChecklistMethodLineMarkerProvider.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.checklist 2 | 3 | import com.intellij.codeInsight.daemon.LineMarkerInfo 4 | import com.intellij.codeInsight.daemon.LineMarkerProvider 5 | import com.intellij.icons.AllIcons 6 | import com.intellij.openapi.editor.markup.GutterIconRenderer 7 | import com.intellij.psi.PsiElement 8 | import com.intellij.psi.PsiIdentifier 9 | import com.intellij.psi.PsiMethod 10 | import com.testknight.listeners.checklist.MethodChecklistIconHandler 11 | import com.testknight.services.TestAnalyzerService 12 | import com.testknight.settings.SettingsService 13 | 14 | /** 15 | * The Line Marker which is on the same line as the method declaration 16 | */ 17 | class ChecklistMethodLineMarkerProvider : LineMarkerProvider { 18 | 19 | private val testAnalyzerService = TestAnalyzerService() 20 | 21 | private fun settingsState() = SettingsService.instance.state 22 | 23 | private fun isMethodDeclaration(element: PsiElement) = (element is PsiIdentifier && element.parent is PsiMethod) 24 | 25 | /** 26 | * The method which creates the Line Marker for the method 27 | * @param element PsiElement for which we have to build the Line Marker 28 | */ 29 | override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { 30 | 31 | if (isMethodDeclaration(element) && 32 | !testAnalyzerService.isTestMethod(element.parent as PsiMethod) && 33 | settingsState().checklistSettings.showGutterIcons 34 | ) { 35 | return LineMarkerInfo( 36 | element, 37 | element.textRange, 38 | AllIcons.General.Add, 39 | { el: PsiElement -> "Generate Checklist" }, 40 | MethodChecklistIconHandler(), GutterIconRenderer.Alignment.RIGHT 41 | ) 42 | } 43 | return null 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/checklist/CheckListKeyboardListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners.checklist 2 | 3 | import com.intellij.openapi.components.service 4 | import com.intellij.openapi.project.Project 5 | import com.intellij.ui.CheckboxTree 6 | import com.intellij.ui.CheckedTreeNode 7 | import com.testknight.models.ChecklistUserObject 8 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 9 | import com.testknight.services.checklist.ChecklistTreeService 10 | import java.awt.event.KeyAdapter 11 | import java.awt.event.KeyEvent 12 | import javax.swing.tree.TreePath 13 | 14 | class CheckListKeyboardListener(private val tree: CheckboxTree, private val project: Project) : KeyAdapter() { 15 | 16 | /** 17 | * Goes to the test method/class if ENTER has been pressed. 18 | * Duplicates the test method if SHIFT is also held along with pressing enter. 19 | * Delete a node if DELETE has been pressed 20 | * 21 | * @param e The key press event. 22 | */ 23 | override fun keyPressed(e: KeyEvent) { 24 | if (e.keyCode == KeyEvent.VK_ENTER) { 25 | 26 | val path: TreePath = tree.selectionPath ?: return 27 | 28 | val node = path.lastPathComponent as CheckedTreeNode 29 | val userObject = ((node.userObject ?: return) as ChecklistUserObject) 30 | 31 | // If its the leaf node 32 | if (userObject.checklistNode is TestingChecklistLeafNode && node.isEnabled) { 33 | tree.setNodeState(node, !node.isChecked) 34 | e.consume() 35 | } 36 | } else if (e.keyCode == KeyEvent.VK_DELETE) { 37 | 38 | val path: TreePath = tree.selectionPath ?: return 39 | 40 | val node = path.lastPathComponent as CheckedTreeNode 41 | 42 | project.service().deleteElement(node) 43 | e.consume() 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/src/main/java/com/testknight/TestKnightTelemetryServer/controllers/UsageDataController.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.controllers; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.responses.*; 5 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 6 | import com.testknight.TestKnightTelemetryServer.services.*; 7 | import org.springframework.beans.factory.annotation.*; 8 | import org.springframework.http.*; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | @RestController 12 | @RequestMapping("/usagedata") 13 | public class UsageDataController { 14 | 15 | private final UsageRecordService usageDataService; 16 | 17 | @Autowired 18 | public UsageDataController(UsageRecordService usageDataService) { 19 | this.usageDataService = usageDataService; 20 | } 21 | 22 | @PostMapping 23 | @ResponseStatus(HttpStatus.CREATED) 24 | public UsageDataAddedDto addUsageData(@RequestBody UsageDataDto usageData) { 25 | return usageDataService.persistUsageData(usageData); 26 | } 27 | 28 | @ExceptionHandler(value = InvalidActionIdException.class) 29 | @ResponseStatus(HttpStatus.BAD_REQUEST) 30 | public ExceptionDto invalidActionIdHandler(InvalidActionIdException exception) { 31 | return new ExceptionDto(400, exception.getMessage()); 32 | } 33 | 34 | @ExceptionHandler(value = NullFieldException.class) 35 | @ResponseStatus(HttpStatus.BAD_REQUEST) 36 | public ExceptionDto nullFieldHandler(NullFieldException exception) { 37 | return new ExceptionDto(400, exception.getMessage()); 38 | } 39 | 40 | @ExceptionHandler(value = InvalidHashException.class) 41 | @ResponseStatus(HttpStatus.UNAUTHORIZED) 42 | public ExceptionDto invalidHashHandler(InvalidHashException exception) { 43 | return new ExceptionDto(401, exception.getMessage()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/testlist/DuplicateTestUnderCaretAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.testlist 2 | 3 | import com.intellij.openapi.actionSystem.AnAction 4 | import com.intellij.openapi.actionSystem.AnActionEvent 5 | import com.intellij.openapi.actionSystem.CommonDataKeys 6 | import com.intellij.openapi.components.service 7 | import com.testknight.services.DuplicateTestsService 8 | import com.testknight.services.UsageDataService 9 | import com.testknight.utilities.UserInterfaceHelper 10 | 11 | class DuplicateTestUnderCaretAction : AnAction() { 12 | 13 | /** 14 | * Duplicates the test case under caret. 15 | * 16 | * @param event Event received when the associated menu item is chosen. 17 | */ 18 | override fun actionPerformed(event: AnActionEvent) { 19 | val project = event.project!! 20 | val duplicateTestsService = project.service() 21 | 22 | val psiFile = event.getData(CommonDataKeys.PSI_FILE)!! 23 | val editor = event.getData(CommonDataKeys.EDITOR)!! 24 | 25 | if (duplicateTestsService.duplicateMethodUnderCaret(psiFile, editor)) { 26 | project.service().showTab("Test List") 27 | } 28 | UsageDataService.instance.recordDuplicateTest() 29 | } 30 | 31 | /** 32 | * Determines whether this menu item is available for the current context. 33 | * Requires a project to be open and psiFile and Editor to be accessible from the action event. 34 | * 35 | * @param e Event received when the associated group-id menu is chosen. 36 | */ 37 | override fun update(e: AnActionEvent) { 38 | // Set the availability based on whether the project, psiFile and editor is not null 39 | e.presentation.isEnabled = ( 40 | e.project != null && 41 | e.getData(CommonDataKeys.PSI_FILE) != null && 42 | e.getData(CommonDataKeys.EDITOR) != null 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/sideEffectAnalysis/Class.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models.sideEffectAnalysis 2 | 3 | import com.intellij.psi.PsiClass 4 | import com.intellij.psi.PsiMethod 5 | import com.intellij.psi.util.PsiTreeUtil 6 | 7 | data class Class(val fields: Map) { 8 | 9 | companion object Factory { 10 | /** 11 | * Creates a new Class object, containing information regarding the 12 | * class and the class fields. 13 | * 14 | * @param method a PsiMethod from which the class information is extracted. 15 | * @return a new Class object. 16 | */ 17 | fun createClassFromMethod(method: PsiMethod): Class { 18 | val parentClass = 19 | PsiTreeUtil.getParentOfType(method, PsiClass::class.java) ?: return Class(emptyMap()) 20 | val result = mutableMapOf() 21 | parentClass.allFields.forEach { 22 | result[it.name] = it.type.canonicalText 23 | result["this.${it.name}"] = it.type.canonicalText 24 | } 25 | result["this"] = parentClass.name ?: "!UNKNOWN" 26 | return Class(result) 27 | } 28 | } 29 | 30 | /** 31 | * Checks whether an identifier is a class field. 32 | * 33 | * @param identifier the name of the identifier. 34 | * @param identifiersInMethodScope the identifiers in the method scope. 35 | * @return true iff identifier is a class field. 36 | */ 37 | fun isClassField( 38 | identifier: String, 39 | identifiersInMethodScope: Map 40 | ): Boolean { 41 | return if (identifier.contains("this.")) { 42 | val newName = identifier.replaceFirst("this.", "") 43 | !identifiersInMethodScope.contains(newName) && this.fields.contains(newName) 44 | } else { 45 | !identifiersInMethodScope.contains(identifier) && this.fields.contains(identifier) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/TruthTable.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import java.util.Locale 4 | import javax.script.ScriptEngineManager 5 | import javax.script.SimpleBindings 6 | import kotlin.math.pow 7 | 8 | class TruthTable(private val vars: List, expressionString: String) { 9 | 10 | private val n = vars.size 11 | private val table = arrayOfNulls(2.0.pow(n).toInt()) 12 | private val engine = ScriptEngineManager().getEngineByName("groovy") 13 | 14 | init { 15 | for (i in table.indices) { 16 | 17 | val bindings = SimpleBindings() 18 | 19 | for ((varI, varStr) in vars.withIndex()) { 20 | bindings[varStr] = getBit(i, varI) 21 | } 22 | 23 | table[i] = engine.eval(expressionString, bindings) as Boolean? 24 | } 25 | } 26 | 27 | /** 28 | * Gets the ith bit in n. 29 | * 30 | * @param num the number 31 | * @param i the index of bit to be read 32 | * @return the ith bit of n 33 | */ 34 | private fun getBit(num: Int, i: Int): Boolean { 35 | val binStr = String.format( 36 | Locale.getDefault(), 37 | "%${n}s", 38 | Integer.toBinaryString(num) 39 | ).replace(' ', '0') 40 | return binStr[i] == '1' 41 | } 42 | 43 | /** 44 | * Get the truth table value at a row. 45 | * 46 | * @param row the row 47 | * @return the value at index i 48 | */ 49 | fun value(row: Int): Boolean { 50 | return table[row]!! 51 | } 52 | 53 | /** 54 | * Get the variable assignments for row. 55 | * 56 | * @param row the row 57 | * @return a mapping from variable names to truth values 58 | */ 59 | fun assignments(row: Int): Map { 60 | val res = HashMap() 61 | for ((varI, variable) in vars.withIndex()) { 62 | res[variable] = getBit(row, varI) 63 | } 64 | return res 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /plugin/testdata/AnimalTest.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class AnimalTest { 12 | 13 | //contrived example intentionally made large to test scrolling functionality 14 | @BeforeAll 15 | public static void before() throws Exception { 16 | return; 17 | } 18 | 19 | @AfterAll 20 | public static void after() throws Exception { 21 | return; 22 | } 23 | 24 | @org.junit.jupiter.api.Test 25 | void AnimalSoundTest() { 26 | Animal cat = new Animal("cat"); 27 | Animal dog = new Animal("dog"); 28 | assertEquals(cat.getSound(), "meow"); 29 | } 30 | 31 | @org.junit.jupiter.api.Test 32 | void setSoundTest() { 33 | Animal dog = new Animal("dog"); 34 | dog.setSound("meow"); 35 | assertEquals("meow", dog.getSound()); 36 | } 37 | 38 | @Test 39 | void overloadedGetFeetTest() { 40 | Animal chicken = new Animal("chicken",2); 41 | assertEquals(chicken.getFeet(), 2); 42 | } 43 | 44 | 45 | @Test 46 | void overloadedGetFeetTestHorse() { 47 | Animal horse = new Animal("horse",2); 48 | horse.setFeet(4); 49 | assertEquals(horse.getFeet(), 4); 50 | } 51 | 52 | @Test 53 | void overloadedGetFeetTestCow() { 54 | Animal cow = new Animal("cow",2); 55 | horse.setFeet(4); 56 | assertEquals(horse.getFeet(), 4); 57 | } 58 | 59 | @Test 60 | void setSoundTest() { 61 | Animal cat = new Animal("cat"); 62 | cat.setSound("woof"); 63 | assertEquals("woof", cat.getSound()); 64 | } 65 | 66 | void notATest() { 67 | return; 68 | } 69 | 70 | @Test 71 | void simple() { 72 | assertTrue(true); 73 | } 74 | 75 | 76 | } -------------------------------------------------------------------------------- /plugin/testdata/expected/DuplicateTestsServiceTest.testDuplicateMethod.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class PointTest { 12 | 13 | @BeforeAll 14 | public static void before() throws Exception { 15 | return; 16 | } 17 | 18 | @AfterAll 19 | public static void after() throws Exception { 20 | return; 21 | } 22 | 23 | @Test 24 | void translateTest() { 25 | Point p1 = new Point(0, 0); 26 | Point p2 = new Point(1, 2); 27 | p1.translate(1, 2); 28 | assertEquals(p1, p2); 29 | } 30 | 31 | @Test 32 | void translateTest() { 33 | Point p1 = new Point(0, 0); 34 | Point p2 = new Point(1, 2); 35 | p1.translate(1, 2); 36 | assertEquals(p1, p2); 37 | } 38 | 39 | 40 | @Test 41 | void setXTest() { 42 | Point p = new Point(0, 0); 43 | p.setX(5); 44 | assertEquals(5, p.getX()); 45 | } 46 | 47 | @Test 48 | void setYTest() { 49 | Point p = new Point(0, 0); 50 | p.setY(5); 51 | assertEquals(5, p.getY()); 52 | } 53 | 54 | @ParameterizedTest(name = "x1={0}, y1={1}, x2={2}, y2={3}, x3={4}, y3={5}") 55 | @CsvSource({ 56 | "1,1,0,0,1,1", 57 | "-1,-1,1,1,0,0", 58 | "1,2,0,3,1,5" 59 | }) 60 | void parameterizedTest(int x1, int y1, int x2, int y2, int x3, int y3) { 61 | Point p = new Point(x1, y1); 62 | Point expected = new Point(x3, y3); 63 | p.translate(x2, y2); 64 | assertEquals(expected, p); 65 | } 66 | 67 | // this is not a test, should not be returned 68 | void notATest() { 69 | return; 70 | } 71 | 72 | void simple() { 73 | assertTrue(true); 74 | } 75 | } -------------------------------------------------------------------------------- /plugin/testdata/expected/DuplicateTestsServiceTest.testDuplicateMethodUnderCaret.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.AfterAll; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | public class PointTest { 12 | 13 | @BeforeAll 14 | public static void before() throws Exception { 15 | return; 16 | } 17 | 18 | @AfterAll 19 | public static void after() throws Exception { 20 | return; 21 | } 22 | 23 | @Test 24 | void translateTest() { 25 | Point p1 = new Point(0, 0); 26 | Point p2 = new Point(1, 2); 27 | p1.translate(1, 2); 28 | assertEquals(p1, p2); 29 | } 30 | 31 | @Test 32 | void translateTest() { 33 | Point p1 = new Point(0, 0); 34 | Point p2 = new Point(1, 2); 35 | p1.translate(1, 2); 36 | assertEquals(p1, p2); 37 | } 38 | 39 | 40 | @Test 41 | void setXTest() { 42 | Point p = new Point(0, 0); 43 | p.setX(5); 44 | assertEquals(5, p.getX()); 45 | } 46 | 47 | @Test 48 | void setYTest() { 49 | Point p = new Point(0, 0); 50 | p.setY(5); 51 | assertEquals(5, p.getY()); 52 | } 53 | 54 | @ParameterizedTest(name = "x1={0}, y1={1}, x2={2}, y2={3}, x3={4}, y3={5}") 55 | @CsvSource({ 56 | "1,1,0,0,1,1", 57 | "-1,-1,1,1,0,0", 58 | "1,2,0,3,1,5" 59 | }) 60 | void parameterizedTest(int x1, int y1, int x2, int y2, int x3, int y3) { 61 | Point p = new Point(x1, y1); 62 | Point expected = new Point(x3, y3); 63 | p.translate(x2, y2); 64 | assertEquals(expected, p); 65 | } 66 | 67 | // this is not a test, should not be returned 68 | void notATest() { 69 | return; 70 | } 71 | 72 | void simple() { 73 | assertTrue(true); 74 | } 75 | } -------------------------------------------------------------------------------- /server/src/test/java/com/testknight/TestKnightTelemetryServer/domain/model/ActionTest.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.domain.model; 2 | 3 | import org.junit.jupiter.api.*; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | class ActionTest { 8 | 9 | @Test 10 | void testGetActionId() { 11 | Action action = new Action("actionId"); 12 | assertEquals("actionId", action.getActionId()); 13 | } 14 | 15 | @Test 16 | void testSetActionId() { 17 | Action action = new Action(); 18 | action.setActionId("newActionId"); 19 | assertEquals("newActionId", action.getActionId()); 20 | } 21 | 22 | @Test 23 | void testEqualsTrue() { 24 | Action actionOne = new Action("actionId"); 25 | Action actionTwo = new Action("actionId"); 26 | assertEquals(actionTwo, actionOne); 27 | } 28 | 29 | @Test 30 | void testEqualsSelf() { 31 | Action actionOne = new Action("actionId"); 32 | assertEquals(actionOne, actionOne); 33 | } 34 | 35 | @Test 36 | void testEqualsFalse() { 37 | Action actionOne = new Action("actionId"); 38 | Action actionTwo = new Action("otherActionId"); 39 | assertNotEquals(actionTwo, actionOne); 40 | } 41 | 42 | @Test 43 | void testEqualsOtherType() { 44 | Action actionOne = new Action("actionId"); 45 | UsageRecord usageRecord = new UsageRecord(); 46 | assertNotEquals(actionOne, usageRecord); 47 | } 48 | 49 | @Test 50 | void testEqualsWithNull() { 51 | Action actionOne = new Action("actionId"); 52 | assertNotEquals(actionOne, null); 53 | } 54 | 55 | @Test 56 | void testCanEqual() { 57 | Action actionOne = new Action("actionId"); 58 | Action actionTwo = new Action("actionId"); 59 | assertTrue(actionOne.canEqual(actionTwo)); 60 | } 61 | 62 | @Test 63 | void testToString() { 64 | Action action = new Action("actionId"); 65 | assertEquals("Action(actionId=actionId)", action.toString()); 66 | } 67 | } -------------------------------------------------------------------------------- /plugin/src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 15 | 18 | 21 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/services/LoadTestsService.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.psi.PsiClass 5 | import com.intellij.psi.PsiFile 6 | import com.intellij.psi.PsiMethod 7 | import com.intellij.psi.util.PsiTreeUtil 8 | import com.testknight.models.TestClassData 9 | import com.testknight.models.TestMethodData 10 | 11 | class LoadTestsService : Disposable { 12 | 13 | private val testAnalyzer = TestAnalyzerService() 14 | 15 | /** 16 | * Extracts all the test methods from a PSI file. 17 | * 18 | * @param file the PSI file 19 | * @return a list of TestCaseData elements representing the test cases 20 | */ 21 | fun getTests(file: PsiFile): List { 22 | val methods = PsiTreeUtil.findChildrenOfType(file, PsiMethod::class.java) 23 | return methods.filter { testAnalyzer.isTestMethod(it) }.map { 24 | TestMethodData(it.name, (it.parent as PsiClass).name ?: "", it) 25 | } 26 | } 27 | 28 | /** 29 | * Extract all test methods in a PSI file in the form of a tree. 30 | * 31 | * @param file the PSI file 32 | * @return a list of TestClassData corresponding to the classes in the file 33 | */ 34 | fun getTestsTree(file: PsiFile): List { 35 | val classes = PsiTreeUtil.findChildrenOfType(file, PsiClass::class.java) 36 | 37 | return classes 38 | .filter { testAnalyzer.isTestClass(it) } 39 | .map { psiClass -> 40 | TestClassData( 41 | psiClass.name ?: "", 42 | psiClass.methods 43 | .filter { testAnalyzer.isTestMethod(it) } 44 | .map { TestMethodData(it.name, psiClass.name ?: "", it) }, 45 | psiClass 46 | ) 47 | } 48 | } 49 | 50 | /** 51 | * Overridden function for Disposable. Doesn't require anything to be disposed. 52 | */ 53 | override fun dispose() { 54 | // No specific dispose function is required. 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/services/TestMethodGenerationService.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.intellij.codeInsight.template.TemplateManager 4 | import com.intellij.openapi.components.service 5 | import com.intellij.openapi.editor.Editor 6 | import com.intellij.openapi.editor.ScrollType 7 | import com.intellij.openapi.fileEditor.FileEditorManager 8 | import com.intellij.openapi.fileEditor.TextEditor 9 | import com.intellij.openapi.project.Project 10 | import com.intellij.psi.PsiManager 11 | import com.intellij.psi.PsiMethod 12 | import com.intellij.psi.util.PsiTreeUtil 13 | import com.intellij.refactoring.suggested.endOffset 14 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 15 | 16 | class TestMethodGenerationService(val project: Project) { 17 | 18 | /** 19 | * Generates and appends a test method for the given checklist item 20 | * in the current caret position. 21 | * 22 | * @param editor the current editor. 23 | * @param checklistItem the checklist item with to generate the method for. 24 | */ 25 | fun generateTestMethod(editor: Editor, checklistItem: TestingChecklistLeafNode) { 26 | val templateCreationService = project.service() 27 | val testMethod = checklistItem.generateTestMethod(project) 28 | val caret = editor.caretModel.primaryCaret 29 | val offset = caret.offset 30 | 31 | val textEditor = FileEditorManager.getInstance(project).selectedEditor as TextEditor? ?: return 32 | val file = PsiManager.getInstance(project).findFile(textEditor.file!!) ?: return 33 | 34 | val element = file.findElementAt(offset) 35 | val parentMethod = PsiTreeUtil.getParentOfType(element, PsiMethod::class.java) 36 | if (parentMethod != null) { 37 | caret.moveToOffset(parentMethod.endOffset) 38 | } 39 | val template = templateCreationService.createBasicTemplate(testMethod) 40 | editor.scrollingModel.scrollToCaret(ScrollType.CENTER) 41 | TemplateManager.getInstance(project).startTemplate(editor, template) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/checklist/CheckedNodeListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners.checklist 2 | 3 | import com.intellij.ui.CheckboxTreeListener 4 | import com.intellij.ui.CheckedTreeNode 5 | import com.testknight.models.ChecklistUserObject 6 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 7 | import com.testknight.services.UsageDataService 8 | 9 | /** 10 | * Custom CheckboxTreeListener which support counting the selected items. 11 | * 12 | * @param checklistTree CheckboxTree the listener listens to. 13 | */ 14 | class CheckedNodeListener : CheckboxTreeListener { 15 | 16 | /** 17 | * This method just makes the changes for the number of checked nodes for the selected node. 18 | * 19 | * @param node the selected CheckedTreeNode for which we have to do the changes in the checked attribute 20 | */ 21 | override fun nodeStateChanged(node: CheckedTreeNode) { 22 | 23 | if (node.userObject is ChecklistUserObject) { 24 | val userObject = node.userObject as ChecklistUserObject 25 | 26 | if (userObject.checklistNode is TestingChecklistLeafNode) { 27 | if (node.isChecked) { 28 | userObject.checklistNode.checked = 1 29 | val parent = (node.parent as CheckedTreeNode) 30 | (parent.userObject as ChecklistUserObject).checklistNode.checked += 1 31 | 32 | val grandParent = (parent.parent as CheckedTreeNode) 33 | (grandParent.userObject as ChecklistUserObject).checklistNode.checked += 1 34 | UsageDataService.instance.recordItemMarked() 35 | } else { 36 | userObject.checklistNode.checked = 0 37 | val parent = (node.parent as CheckedTreeNode) 38 | (parent.userObject as ChecklistUserObject).checklistNode.checked -= 1 39 | 40 | val grandParent = (parent.parent as CheckedTreeNode) 41 | (grandParent.userObject as ChecklistUserObject).checklistNode.checked -= 1 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/checklistGenerationStrategies/TestKnightJavaPsiVisitorTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies 2 | 3 | import com.testknight.extensions.TestKnightTestCase 4 | import com.testknight.models.testingChecklist.parentNodes.TestingChecklistClassNode 5 | import com.testknight.models.testingChecklist.parentNodes.TestingChecklistMethodNode 6 | import org.junit.jupiter.api.Test 7 | 8 | internal class TestKnightJavaPsiVisitorTest : TestKnightTestCase() { 9 | 10 | @Test 11 | fun testClassInitializedProperly() { 12 | val visitor = TestKnightJavaPsiVisitor() 13 | val data = getBasicTestInfo("/EmptyClass.java") 14 | data.psiClass?.accept(visitor) 15 | val expected = TestingChecklistClassNode("EmptyClass", mutableListOf(), data.psiClass) 16 | val actual = visitor.classNode 17 | assertEquals(expected, actual) 18 | } 19 | 20 | @Test 21 | fun testMethodsAreNotAnalyzed() { 22 | val visitor = TestKnightJavaPsiVisitor() 23 | val data = getBasicTestInfo("/Tests.java") 24 | data.psiClass?.accept(visitor) 25 | val expected = TestingChecklistClassNode( 26 | "PointTest", 27 | mutableListOf( 28 | TestingChecklistMethodNode("basic", mutableListOf(), data.psiClass?.methods?.get(0)), 29 | TestingChecklistMethodNode("hasModifiers", mutableListOf(), data.psiClass?.methods?.get(1)), 30 | TestingChecklistMethodNode("hasReturnTy", mutableListOf(), data.psiClass?.methods?.get(2)), 31 | TestingChecklistMethodNode("hasParams", mutableListOf(), data.psiClass?.methods?.get(3)), 32 | TestingChecklistMethodNode("hasTypeParams", mutableListOf(), data.psiClass?.methods?.get(4)), 33 | TestingChecklistMethodNode("throwsException", mutableListOf(), data.psiClass?.methods?.get(5)), 34 | TestingChecklistMethodNode("hasAssertion", mutableListOf(), data.psiClass?.methods?.get(6)) 35 | ), 36 | data.psiClass 37 | ) 38 | val actual = visitor.classNode 39 | assertEquals(expected, actual) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/services/GlobalHighlighter.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.intellij.openapi.editor.Editor 4 | import com.intellij.openapi.editor.markup.RangeHighlighter 5 | import com.intellij.openapi.fileEditor.FileEditorManager 6 | import com.intellij.openapi.fileEditor.TextEditor 7 | import com.intellij.openapi.project.Project 8 | 9 | /** 10 | * Implements the logic for highlighting all editors given an editor-highlighting function 11 | */ 12 | abstract class GlobalHighlighter(project: Project) { 13 | 14 | /** 15 | * Keeps track of the 16 | */ 17 | private var highlighters = mutableMapOf>() 18 | private val fileEditorManager = FileEditorManager.getInstance(project) 19 | 20 | /** 21 | * Reconstruct all highlights 22 | */ 23 | fun rebuildHighlights() { 24 | 25 | // remove all highlights 26 | removeHighlights() 27 | 28 | // get all open editors 29 | val editors = mutableListOf() 30 | fileEditorManager.allEditors.forEach { if (it is TextEditor) editors.add(it.editor) } 31 | 32 | // highlight them 33 | addHighlights(editors) 34 | } 35 | 36 | /** 37 | * Highlight the provided editors. 38 | * 39 | * @param editors the editors 40 | */ 41 | fun addHighlights(editors: List) { 42 | editors.forEach { 43 | val newHls = highlightEditor(it) 44 | if (highlighters[it] == null) highlighters[it] = mutableListOf() 45 | highlighters[it]!!.addAll(newHls) 46 | } 47 | } 48 | 49 | /** 50 | * Remove all Highlights 51 | */ 52 | fun removeHighlights() { 53 | 54 | // foreach editor, remove all of its highlights 55 | highlighters.keys.forEach { ed -> 56 | val hls = highlighters[ed] 57 | hls?.forEach { hl -> ed.markupModel.removeHighlighter(hl) } 58 | } 59 | } 60 | 61 | /** 62 | * Highlight the lines covered within an editor. 63 | * 64 | * @param editor the editor 65 | */ 66 | abstract fun highlightEditor(editor: Editor): List 67 | } 68 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/checklistGenerationStrategies/leafStrategies/ThrowStatementChecklistGenerationStrategyTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies.leafStrategies 2 | 3 | import com.intellij.psi.PsiThrowStatement 4 | import com.intellij.psi.util.PsiTreeUtil 5 | import com.testknight.extensions.TestKnightTestCase 6 | import com.testknight.models.testingChecklist.leafNodes.ThrowStatementChecklistNode 7 | import junit.framework.TestCase 8 | import org.junit.Test 9 | 10 | internal class ThrowStatementChecklistGenerationStrategyTest : TestKnightTestCase() { 11 | 12 | private val generationStrategy = ThrowStatementChecklistGenerationStrategy.create() 13 | 14 | @Test 15 | fun testSimpleThrow() { 16 | getBasicTestInfo("/Person.java") 17 | 18 | val method = getMethodByName("getSpouse") 19 | val throwStatement = PsiTreeUtil.findChildOfType(method, PsiThrowStatement::class.java) 20 | 21 | val expected = listOf(ThrowStatementChecklistNode("Test when NotMarriedException is thrown", throwStatement!!, "NotMarriedException")) 22 | val actual = generationStrategy.generateChecklist(throwStatement) 23 | 24 | TestCase.assertEquals(expected, actual) 25 | } 26 | 27 | @Test 28 | fun testInvalidNewExpressionInThrow() { 29 | getBasicTestInfo("/Person.java") 30 | 31 | val method = getMethodByName("methodWithBrokenThrows") 32 | val throwStatement = PsiTreeUtil.findChildrenOfType(method, PsiThrowStatement::class.java).elementAt(0) 33 | 34 | val expected = emptyList() 35 | val actual = generationStrategy.generateChecklist(throwStatement) 36 | 37 | TestCase.assertEquals(expected, actual) 38 | } 39 | 40 | @Test 41 | fun testMissingExceptionClassInThrow() { 42 | getBasicTestInfo("/Person.java") 43 | 44 | val method = getMethodByName("methodWithBrokenThrows") 45 | val throwStatement = PsiTreeUtil.findChildrenOfType(method, PsiThrowStatement::class.java).elementAt(1) 46 | 47 | val expected = emptyList() 48 | val actual = generationStrategy.generateChecklist(throwStatement) 49 | 50 | TestCase.assertEquals(expected, actual) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /documentation/design/telemetryDesign/ActionIds.md: -------------------------------------------------------------------------------- 1 | This file includes the mapping between action IDs (stored in the telemetry server's DB) and the actions 2 | that the user performed. 3 | 4 | | Action ID | Description | 5 | |----------------------|-------------------------------------------------------------------------------------------------| 6 | | duplicateTest | The user used TestKnight to duplicate a test | 7 | | gotoTest | The user used TestKnight to quickly navigate to a test | 8 | | suggestAssertion | The user used TestKnight to get assertion suggestions | 9 | | generateChecklist | The user used TestKnight to generate a testing checklist | 10 | | splitDiffView | The user used TestKnight to inspect how the coverage changed between two consecutive test runs. | 11 | | integratedDiffView | The user used TestKnight to inspect how the coverage changed between two consecutive test runs. | 12 | | traceTest | The user used TestKnight to inspect what specific lines a test covers | 13 | | generateTest | The user used TestKnight to generate a new test method from a checklist item | 14 | | itemMarked | The user marked one of the items in the generated checklist | 15 | | itemDeleted | The user deleted one of the items in the generated checklist | 16 | | runWithCoverage | The user run a test suite with coverage | 17 | | testRun | The user run a test | 18 | | testFail | A test failed | 19 | | testAdd | The user added a new test | | | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/checklist/DeleteElementChecklistAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.checklist 2 | 3 | import com.intellij.notification.NotificationType 4 | import com.intellij.openapi.actionSystem.AnAction 5 | import com.intellij.openapi.actionSystem.AnActionEvent 6 | import com.intellij.openapi.components.service 7 | import com.intellij.ui.CheckedTreeNode 8 | import com.intellij.ui.treeStructure.Tree 9 | import com.testknight.services.ExceptionHandlerService 10 | import com.testknight.services.checklist.ChecklistTreeService 11 | import javax.swing.tree.TreePath 12 | 13 | class DeleteElementChecklistAction : AnAction() { 14 | 15 | private lateinit var tree: Tree 16 | 17 | override fun actionPerformed(e: AnActionEvent) { 18 | 19 | val path: TreePath = tree.selectionPath 20 | 21 | if (path.lastPathComponent !is CheckedTreeNode) { 22 | notifyUser(e) 23 | return 24 | } 25 | 26 | val node = path.lastPathComponent as CheckedTreeNode 27 | if (e.project == null) { 28 | return 29 | } else { 30 | e.project!!.service().deleteElement(node) 31 | } 32 | } 33 | 34 | override fun update(e: AnActionEvent) { 35 | super.update(e) 36 | if (tree.selectionPath == null) { 37 | e.presentation.isEnabled = false 38 | } 39 | } 40 | 41 | /** 42 | * Getter for the tree attribute. 43 | * 44 | * @return the Tree attribute 45 | */ 46 | fun getTree(): Tree { 47 | return tree 48 | } 49 | 50 | /** 51 | * Setter for the tree attribute. 52 | * 53 | * @param tree the new value of the Tree attribute 54 | */ 55 | fun setTree(tree: Tree) { 56 | this.tree = tree 57 | } 58 | 59 | /** 60 | * Notify the user in case of no path found. 61 | * 62 | * @param e the AnActionEvent for which the user must be notified 63 | */ 64 | private fun notifyUser(e: AnActionEvent) { 65 | if (e.project == null) { 66 | return 67 | } else { 68 | e.project!!.service().notify( 69 | "Delete Element not available", 70 | "Not selected element", NotificationType.WARNING 71 | ) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /documentation/mockups/Checklist/DetailedChecklist.md: -------------------------------------------------------------------------------- 1 | # Description of the Checklist mockup 2 | 3 | - We will have the checklist tree following this hierarchy: 4 | ``` 5 | > class 1 6 | > method 1 7 | > Item 1 8 | > Item 2 9 | > Item 3 10 | > method 2 11 | > Item 1 12 | > Item 2 13 | > Item 3 14 | > Item 4 15 | > class 2 16 | > method 1 17 | > Item 1 18 | > Item 2 19 | > Item 3 20 | ``` 21 | 22 | - For each method we will have a button which will create checklist for that method. Visually, the tree will be updated and the new method checklist items will be expanded (visible without manually pressing the button for expanding the tree). 23 | - For each class we will have a button which will create checklist for that class. Visually, the tree will be updated and the new class and its methods checklist items will be expanded (visible without manually pressing the button for expanding the tree). 24 | - Adding new method and class checklists can also be accessed by right clicking on the method/class on the code editor in addition to the button. 25 | - Near each method, we will have a label which will tell the user how many items of that specific method have been checked (i.e. "6 of 9 items checked") 26 | - Near each class, we will have a label which will tells the user how many methods checklists have been generated for that specific class (i.e. "6 of 9 methods has checklist") 27 | - The number of checked item per method will have a text color to indicate how many checklist items have been checked. 28 | - In the future, the checklist will be dynamically refreshed but it can also be manually refreshed. 29 | - If the user wants to generate the checklist for an existing class/existing method, the Toolwindow UI will not create a new checklist as it already exists. Thus, we will preserve the final version and the "human mistakes" will be avoided. 30 | - The user will be able to modify the checklist item (add/edit/remove) manually by pressing right-click on the toolwindow UI but they can only remove it for the checklist method or checklist class from the Toolwindow UI. 31 | - The stats regarding checklist will be updated after any operation which modifies the tree. 32 | 33 | # Graphical mockup 34 | ###Editor View 35 | ![image info](./ChecklistEditor.png) 36 | ###Side Bar View 37 | ![image info](./ChecklistSideBar.png) 38 | 39 | 40 | -------------------------------------------------------------------------------- /documentation/design/telemetryDesign/testKnightTelemtryApi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: TestKnight Telemetry Server API 4 | description: This the API specification for TestKnight's telemetry server. It contains all the necessary information about how to upload usage data. 5 | contact: 6 | name: TU Delft Software Engineering Research Group 7 | url: https://se.ewi.tudelft.nl/ 8 | version: 1.0.0 #API Version 9 | servers: 10 | - url: https://testknight.ewi.tudelft.nl 11 | paths: 12 | /usagedata: 13 | description: Usage Data 14 | post: 15 | description: Operation to upload new usage data. Usage data records are uploaded in an array. This ensures that the endpoint supports both single and multiple entries. A list of all valid actions can be found under `/documentation/design/telemetry`. Furthermore the requests should include an MD5 hash to ensure that the requests are coming from an approved client. The hash is generated by hashing the contents of the request with a secret string appending. Lastly the dateTime field should follow Java's LocalDateTime format. 16 | requestBody: 17 | content: 18 | application/json: 19 | schema: 20 | type: object 21 | properties: 22 | userId: 23 | type: string 24 | format: uuid 25 | example: "123e4567-e89b-12d3-a456-426614174000" 26 | actionsRecorded: 27 | type: array 28 | items: 29 | type: object 30 | properties: 31 | actionId: 32 | type: string 33 | example: "testAdd" 34 | dateTime: 35 | type: string 36 | example: "2021-12-03T10:15:30" 37 | hash: 38 | type: string 39 | example: "79054025255fb1a26e4bc422aef54eb4" 40 | responses: 41 | 201: 42 | description: Successfully added the usage data. 43 | 400: 44 | description: Invalid input. For example an invalid action id or a date-time with the wrong format. 45 | 401: 46 | description: Unauthorized. The hash included in the request does not belong to an authorized client. 47 | tags: 48 | - Data collecting -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/testlist/TestListSelectionListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners.testlist 2 | 3 | import com.intellij.openapi.components.service 4 | import com.intellij.openapi.project.Project 5 | import com.testknight.actions.testlist.TestListTraceabilityAction 6 | import com.testknight.exceptions.CorruptedTraceFileException 7 | import com.testknight.exceptions.NoTestCoverageDataException 8 | import com.testknight.exceptions.TraceFileNotFoundException 9 | import com.testknight.models.TestMethodUserObject 10 | import com.testknight.services.ExceptionHandlerService 11 | import com.testknight.services.TestTracingService 12 | import javax.swing.event.TreeSelectionEvent 13 | import javax.swing.event.TreeSelectionListener 14 | import javax.swing.tree.DefaultMutableTreeNode 15 | 16 | class TestListSelectionListener( 17 | private val project: Project, 18 | private val traceabilityButton: TestListTraceabilityAction 19 | ) : TreeSelectionListener { 20 | 21 | private val tracingService = project.service() 22 | 23 | /** 24 | * Called whenever the value of the selection changes. 25 | * @param e the event that characterizes the change. 26 | */ 27 | override fun valueChanged(e: TreeSelectionEvent?) { 28 | tracingService.removeHighlights() 29 | if (!traceabilityButton.getSelected()) { 30 | return 31 | } 32 | 33 | val component = (e?.newLeadSelectionPath ?: return).lastPathComponent ?: return 34 | if (component is DefaultMutableTreeNode && component.userObject is TestMethodUserObject) { 35 | val testUserObject = (component.userObject as TestMethodUserObject) 36 | try { 37 | tracingService 38 | .highlightTest("${testUserObject.reference.testClassName},${testUserObject.reference.name}") 39 | } catch (ex: TraceFileNotFoundException) { 40 | project.service().notify(ex) 41 | return 42 | } catch (ex: CorruptedTraceFileException) { 43 | project.service().notify(ex) 44 | return 45 | } catch (ex: NoTestCoverageDataException) { 46 | project.service().notify(ex) 47 | return 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/checklistGenerationStrategies/leafStrategies/loopStatements/ForEachStatementChecklistGenerationStrategyTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies.leafStrategies.loopStatements 2 | 3 | import com.intellij.psi.PsiElement 4 | import com.intellij.psi.PsiForeachStatement 5 | import com.intellij.psi.util.PsiTreeUtil 6 | import com.testknight.extensions.TestKnightTestCase 7 | import com.testknight.models.testingChecklist.leafNodes.loopStatements.ForEachStatementChecklistNode 8 | import junit.framework.TestCase 9 | import org.junit.Test 10 | 11 | internal class ForEachStatementChecklistGenerationStrategyTest : TestKnightTestCase() { 12 | 13 | private val generationStrategy = ForEachStatementChecklistGenerationStrategy.create() 14 | 15 | @Test 16 | fun testMissingIteratedValueReturnsEmptyList() { 17 | getBasicTestInfo("/SimpleArray.java") 18 | 19 | val method = getMethodByName("brokenForEach") 20 | val foreachStatement = PsiTreeUtil.findChildOfType(method, PsiForeachStatement::class.java) 21 | 22 | val expected = emptyList() 23 | val actual = generationStrategy.generateChecklist(foreachStatement!!) 24 | 25 | TestCase.assertEquals(expected, actual) 26 | } 27 | 28 | @Test 29 | fun testForeachChecklistGenerationCorrect() { 30 | getBasicTestInfo("/SimpleArray.java") 31 | 32 | val method = getMethodByName("incrementByOneForEach") 33 | val foreachStatement = PsiTreeUtil.findChildOfType(method, PsiForeachStatement::class.java) 34 | 35 | val expected = listOf( 36 | ForEachStatementChecklistNode(description = "Test where getArrayOfInts() is empty", foreachStatement as PsiElement, "getArrayOfInts()"), 37 | ForEachStatementChecklistNode(description = "Test where getArrayOfInts() has one element", foreachStatement as PsiElement, "getArrayOfInts()"), 38 | ForEachStatementChecklistNode(description = "Test where getArrayOfInts() is null", foreachStatement as PsiElement, "getArrayOfInts()"), 39 | ForEachStatementChecklistNode(description = "Test where foreach loop runs multiple times", foreachStatement as PsiElement, "getArrayOfInts()") 40 | ) 41 | val actual = generationStrategy.generateChecklist(foreachStatement!!) 42 | 43 | TestCase.assertEquals(expected, actual) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/models/HighlightedTextData.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.models 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.psi.PsiElement 5 | import com.intellij.psi.PsiLiteralExpression 6 | import com.intellij.refactoring.suggested.endOffset 7 | import com.intellij.refactoring.suggested.startOffset 8 | import com.testknight.settings.SettingsService 9 | 10 | /** 11 | * Represents an element to be highlighted when a test is duplicated 12 | * 13 | * @param startOffset the start position of the element in the source code 14 | * @param endOffset the end position of the element in the source code 15 | * @param text the text 16 | */ 17 | class HighlightedTextData(var startOffset: Int, var endOffset: Int, var text: String) { 18 | 19 | /** 20 | * Construct it for a literal. This takes into account strings. 21 | * 22 | * @param element: The PsiLiteralExpression 23 | */ 24 | constructor(element: PsiElement) : this(element.startOffset, element.endOffset, element.text) { 25 | 26 | // if literal is string, include only the text inside quotes 27 | if (isQuoteHlEnabled() && (isString(element) || isChar(element))) { 28 | this.startOffset = element.startOffset + 1 29 | this.endOffset = element.endOffset - 1 30 | this.text = element.text.substring(1, element.text.length - 1) 31 | } 32 | } 33 | 34 | /** 35 | * Returns true if the provided PSI element corresponds to a string literal. 36 | * 37 | * @param element the PSI element 38 | */ 39 | private fun isString(element: PsiElement) = 40 | element is PsiLiteralExpression && element.text.matches(Regex("\".*\"")) 41 | 42 | /** 43 | * Returns true if the provided PSI element corresponds to a string literal. 44 | * 45 | * @param element the PSI element 46 | */ 47 | private fun isChar(element: PsiElement) = 48 | element is PsiLiteralExpression && element.text.matches(Regex("'.'")) 49 | 50 | /** 51 | * Reads the settings to determine if in-quote highlighting is enabled. 52 | * 53 | * @return true if in-quote highlighting is enabled 54 | */ 55 | private fun isQuoteHlEnabled() = ApplicationManager 56 | .getApplication() 57 | .getService(SettingsService::class.java) 58 | .state.testListSettings.highlightStrategies["Highlight string inside quotes"]!! 59 | } 60 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/listeners/checklist/ChecklistMouseListener.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.listeners.checklist 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.ui.JBMenuItem 5 | import com.intellij.openapi.ui.JBPopupMenu 6 | import com.intellij.ui.CheckedTreeNode 7 | import com.intellij.ui.treeStructure.Tree 8 | import com.testknight.actions.checklist.ModifyChecklistAction 9 | import com.testknight.models.ChecklistUserObject 10 | import com.testknight.models.testingChecklist.leafNodes.TestingChecklistLeafNode 11 | import com.testknight.models.testingChecklist.parentNodes.TestingChecklistMethodNode 12 | import java.awt.event.MouseAdapter 13 | import java.awt.event.MouseEvent 14 | import javax.swing.SwingUtilities 15 | import javax.swing.tree.DefaultMutableTreeNode 16 | import javax.swing.tree.TreePath 17 | 18 | class ChecklistMouseListener(private val tree: Tree, private val project: Project) : MouseAdapter() { 19 | 20 | override fun mousePressed(event: MouseEvent) { 21 | 22 | if (SwingUtilities.isRightMouseButton(event)) { 23 | 24 | val path: TreePath = tree.selectionPath ?: return 25 | 26 | if (path.lastPathComponent !is DefaultMutableTreeNode) return 27 | 28 | val node = path.lastPathComponent as CheckedTreeNode 29 | 30 | val menu = JBPopupMenu() 31 | val delete = JBMenuItem("Delete") 32 | delete.addActionListener(ModifyChecklistAction(node, tree, path, project)) 33 | menu.add(delete) 34 | 35 | if ((node.userObject as ChecklistUserObject).checklistNode is TestingChecklistLeafNode) { 36 | val edit = JBMenuItem("Edit") 37 | val generate = JBMenuItem("Generate Test Method") 38 | 39 | generate.addActionListener(ModifyChecklistAction(node, tree, path, project)) 40 | 41 | edit.addActionListener { 42 | it.apply { tree.startEditingAtPath(path) } 43 | } 44 | 45 | menu.add(edit) 46 | menu.add(generate) 47 | } else if ((node.userObject as ChecklistUserObject).checklistNode is TestingChecklistMethodNode) { 48 | val addItem = JBMenuItem("Add item") 49 | addItem.addActionListener(ModifyChecklistAction(node, tree, path, project)) 50 | menu.add(addItem) 51 | } 52 | 53 | menu.show(tree, event.x, event.y) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /server/src/test/java/com/testknight/TestKnightTelemetryServer/validation/ContentValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.validation; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 5 | import com.testknight.TestKnightTelemetryServer.repositories.*; 6 | import org.junit.jupiter.api.*; 7 | import org.mockito.*; 8 | import org.springframework.boot.test.context.*; 9 | import org.springframework.boot.test.mock.mockito.*; 10 | 11 | import java.time.*; 12 | import java.util.*; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | import static org.mockito.ArgumentMatchers.*; 16 | import static org.mockito.Mockito.*; 17 | 18 | @SpringBootTest 19 | class ContentValidatorTest { 20 | 21 | @MockBean 22 | private ActionRepository actionRepository; 23 | 24 | private UsageDataDto usageDataDto = new UsageDataDto("userId", new ArrayList<>(), "hash"); 25 | 26 | private ContentValidator contentValidator; 27 | 28 | @BeforeEach 29 | public void setup() { 30 | MockitoAnnotations.openMocks(this); 31 | when(actionRepository.existsById(anyString())).thenReturn(true); 32 | contentValidator = new ContentValidator(actionRepository); 33 | } 34 | 35 | @Test 36 | public void testGoodWeather() { 37 | assertDoesNotThrow(() -> { 38 | contentValidator.handle(usageDataDto); 39 | }); 40 | } 41 | 42 | @Test 43 | public void testNullActionsRecorded() { 44 | usageDataDto.setActionsRecorded(null); 45 | assertThrows(NullFieldException.class, () -> { 46 | contentValidator.handle(usageDataDto); 47 | }); 48 | } 49 | 50 | @Test 51 | public void testNullUserId() { 52 | usageDataDto.setUserId(null); 53 | assertThrows(NullFieldException.class, () -> { 54 | contentValidator.handle(usageDataDto); 55 | }); 56 | } 57 | 58 | @Test 59 | public void testActionDoesNotExist() { 60 | when(actionRepository.existsById(anyString())).thenReturn(false); 61 | ArrayList actions = new ArrayList<>(); 62 | actions.add(new ActionEventDto("invalidActionId", LocalDateTime.now())); 63 | usageDataDto.setActionsRecorded(actions); 64 | assertThrows(InvalidActionIdException.class, () -> { 65 | contentValidator.handle(usageDataDto); 66 | }); 67 | } 68 | 69 | 70 | } -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/actions/checklist/GenerateChecklistUnderCaretAction.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.actions.checklist 2 | 3 | import com.intellij.openapi.actionSystem.ActionManager 4 | import com.intellij.openapi.actionSystem.AnAction 5 | import com.intellij.openapi.actionSystem.AnActionEvent 6 | import com.intellij.openapi.actionSystem.CommonDataKeys 7 | import com.intellij.openapi.components.service 8 | import com.intellij.psi.PsiClass 9 | import com.intellij.psi.PsiMethod 10 | import com.intellij.psi.util.PsiTreeUtil 11 | import com.testknight.utilities.UserInterfaceHelper 12 | 13 | class GenerateChecklistUnderCaretAction : AnAction() { 14 | 15 | /** 16 | * Updates the CheckList tab to add new checklist cases. 17 | * 18 | * @param event Event received when the associated menu item is chosen. 19 | */ 20 | override fun actionPerformed(event: AnActionEvent) { 21 | val file = event.getData(CommonDataKeys.PSI_FILE)!! 22 | val editor = event.getData(CommonDataKeys.EDITOR)!! 23 | val project = event.project!! 24 | 25 | val caret = editor.caretModel.primaryCaret 26 | val element = file.findElementAt(caret.offset) 27 | val containingMethod = PsiTreeUtil.getParentOfType(element, PsiMethod::class.java) 28 | val containingClass = PsiTreeUtil.getParentOfType(element, PsiClass::class.java) 29 | 30 | val checklistAction = ActionManager.getInstance().getAction("ChecklistAction") as LoadChecklistAction 31 | 32 | if (containingMethod != null && checklistAction.actionPerformed(project, containingMethod)) { 33 | project.service().showTab("Checklist") 34 | } else if (containingClass != null && checklistAction.actionPerformed(project, containingClass)) { 35 | project.service().showTab("Checklist") 36 | } 37 | } 38 | 39 | /** 40 | * Determines whether this menu item is available for the current context. 41 | * Requires a project to be open and psiFile and Editor to be accessible from the action event. 42 | * 43 | * @param e Event received when the associated group-id menu is chosen. 44 | */ 45 | override fun update(e: AnActionEvent) { 46 | // Set the availability based on whether psiFile and editor is not null 47 | e.presentation.isEnabled = ( 48 | e.project != null && 49 | e.getData(CommonDataKeys.PSI_FILE) != null && 50 | e.getData(CommonDataKeys.EDITOR) != null 51 | ) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/services/UsageDataServiceTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.services 2 | 3 | import com.google.gson.Gson 4 | import com.testknight.extensions.TestKnightTestCase 5 | import com.testknight.models.ActionData 6 | import com.testknight.models.UsageData 7 | import com.testknight.settings.SettingsService 8 | import junit.framework.TestCase 9 | import org.junit.Test 10 | 11 | class UsageDataServiceTest : TestKnightTestCase() { 12 | 13 | override fun setUp() { 14 | super.setUp() 15 | UsageDataService.instance.clearRecords() 16 | } 17 | 18 | @Test 19 | private fun recordActions(): List? { 20 | val a0 = UsageDataService.instance.recordDuplicateTest() 21 | val a1 = UsageDataService.instance.recordGotoTest() 22 | val a2 = UsageDataService.instance.recordSuggestAssertion() 23 | val a3 = UsageDataService.instance.recordGenerateChecklist() 24 | val a4 = UsageDataService.instance.recordSplitDiffView() 25 | val a5 = UsageDataService.instance.recordIntegratedDiffView() 26 | val a6 = UsageDataService.instance.recordTraceTest() 27 | val a7 = UsageDataService.instance.recordGenerateTest() 28 | val a8 = UsageDataService.instance.recordItemMarked() 29 | val a9 = UsageDataService.instance.recordItemDeleted() 30 | 31 | val actions = listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) 32 | actions.forEach { it ?: return null } 33 | 34 | return actions.map { it!! } 35 | } 36 | 37 | @Test 38 | fun testBasic() { 39 | SettingsService.instance.state.telemetrySettings.isEnabled = true 40 | 41 | val actions = recordActions()!! 42 | 43 | val expected = UsageData(actions) 44 | val actual = UsageDataService.instance.usageData() 45 | 46 | TestCase.assertEquals(expected, actual) 47 | } 48 | 49 | @Test 50 | fun testJson() { 51 | SettingsService.instance.state.telemetrySettings.isEnabled = true 52 | 53 | val actions = recordActions()!! 54 | 55 | val expected = Gson().toJson(UsageData(actions)) 56 | val actual = UsageDataService.instance.usageDataJson() 57 | 58 | TestCase.assertEquals(expected, actual) 59 | } 60 | 61 | @Test 62 | fun testTelemetryDisabled() { 63 | 64 | // telemetry should be false by default 65 | 66 | recordActions() 67 | 68 | val expected = UsageData(listOf()) 69 | val actual = UsageDataService.instance.usageData() 70 | 71 | assertEquals(expected, actual) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /plugin/src/main/kotlin/com/testknight/checklistGenerationStrategies/parentStrategies/ClassChecklistGenerationStrategy.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies.parentStrategies 2 | 3 | import com.intellij.psi.PsiClass 4 | import com.intellij.psi.PsiMethod 5 | import com.intellij.psi.util.PsiTreeUtil 6 | import com.testknight.models.testingChecklist.parentNodes.TestingChecklistClassNode 7 | import com.testknight.models.testingChecklist.parentNodes.TestingChecklistMethodNode 8 | 9 | class ClassChecklistGenerationStrategy private constructor( 10 | private val methodChecklistGenerator: ParentChecklistGeneratorStrategy 11 | ) : 12 | ParentChecklistGeneratorStrategy { 13 | 14 | companion object Factory { 15 | /** 16 | * Creates a new ClassChecklistGenerationStrategy. 17 | * 18 | * @param methodChecklistGenerator the generator to be used to generate the method classes. 19 | * Used mainly to allow dependency injection for testing. 20 | * @return a ClassChecklistGenerationStrategy object. 21 | */ 22 | fun create( 23 | methodChecklistGenerator: ParentChecklistGeneratorStrategy 24 | ): ClassChecklistGenerationStrategy { 25 | return ClassChecklistGenerationStrategy(methodChecklistGenerator) 26 | } 27 | 28 | /** 29 | * Creates a new ClassChecklistGenerationStrategy. 30 | * 31 | * @return a ClassChecklistGenerationStrategy object. 32 | */ 33 | fun create(): ClassChecklistGenerationStrategy { 34 | return create(MethodChecklistGenerationStrategy.create()) 35 | } 36 | } 37 | 38 | /** 39 | * Generates the checklist for a given class. 40 | * 41 | * @param psiElement the PsiClass to generate the checklist on. 42 | * @return a TestingChecklistClassNode object representing the checklist for that class. 43 | */ 44 | override fun generateChecklist(psiElement: PsiClass): TestingChecklistClassNode { 45 | val methodName = psiElement.name ?: "No class name found" 46 | val methods = PsiTreeUtil.findChildrenOfType(psiElement, PsiMethod::class.java) 47 | val children = mutableListOf() 48 | // val methodChecklistGenerator = MethodChecklistGenerationStrategy.create() 49 | methods.forEach { children.add(methodChecklistGenerator.generateChecklist(it)) } 50 | return TestingChecklistClassNode(methodName, children, psiElement) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /plugin/src/test/kotlin/com/testknight/checklistGenerationStrategies/leafStrategies/branchingStatements/IfStatementChecklistGenerationStrategyTest.kt: -------------------------------------------------------------------------------- 1 | package com.testknight.checklistGenerationStrategies.leafStrategies.branchingStatements 2 | 3 | import com.intellij.psi.PsiBinaryExpression 4 | import com.intellij.psi.PsiIfStatement 5 | import com.intellij.psi.util.PsiTreeUtil 6 | import com.testknight.checklistGenerationStrategies.leafStrategies.ConditionChecklistGenerationStrategy 7 | import com.testknight.extensions.TestKnightTestCase 8 | import com.testknight.models.testingChecklist.leafNodes.ConditionChecklistNode 9 | import io.mockk.every 10 | import io.mockk.mockk 11 | import io.mockk.verify 12 | import junit.framework.TestCase 13 | import org.junit.Test 14 | 15 | internal class IfStatementChecklistGenerationStrategyTest : TestKnightTestCase() { 16 | 17 | private val conditionGenerationStrategy = mockk() 18 | private val generationStrategy = IfStatementChecklistGenerationStrategy.create(conditionGenerationStrategy) 19 | 20 | @Test 21 | fun testNoCondition() { 22 | getBasicTestInfo("/BrokenClass.java") 23 | val method = getMethodByName("incompleteCondition") 24 | 25 | val ifStatement = PsiTreeUtil.findChildOfType(method, PsiIfStatement::class.java) 26 | 27 | val expected = emptyList() 28 | val actual = generationStrategy.generateChecklist(ifStatement!!) 29 | 30 | TestCase.assertEquals(expected, actual) 31 | } 32 | 33 | @Test 34 | fun testIfStatementGeneration() { 35 | 36 | getBasicTestInfo("/Person.java") 37 | 38 | val method = getMethodByName("setAge") 39 | val conditional = PsiTreeUtil.findChildOfType(method, PsiIfStatement::class.java) 40 | val condition = PsiTreeUtil.getChildOfType(conditional, PsiBinaryExpression::class.java) 41 | 42 | every { conditionGenerationStrategy.generateChecklist(condition!!) } returns emptyList() 43 | generationStrategy.generateChecklist(conditional!!) 44 | verify { conditionGenerationStrategy.generateChecklist(condition!!) } 45 | } 46 | 47 | @Test 48 | fun testLiteralCondition() { 49 | 50 | getBasicTestInfo("/BrokenClass.java") 51 | 52 | val method = getMethodByName("conditionalWithLiteral") 53 | val ifStatement = PsiTreeUtil.findChildOfType(method, PsiIfStatement::class.java) 54 | 55 | val expected = emptyList() 56 | val actual = generationStrategy.generateChecklist(ifStatement!!) 57 | 58 | TestCase.assertEquals(expected, actual) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /server/src/test/java/com/testknight/TestKnightTelemetryServer/domain/factories/UsageRecordFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.testknight.TestKnightTelemetryServer.domain.factories; 2 | 3 | import com.testknight.TestKnightTelemetryServer.dataTransferObjects.requests.*; 4 | import com.testknight.TestKnightTelemetryServer.domain.model.*; 5 | import com.testknight.TestKnightTelemetryServer.exceptions.*; 6 | import org.junit.jupiter.api.*; 7 | 8 | import java.time.*; 9 | import java.util.*; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | class UsageRecordFactoryTest { 14 | 15 | private final UsageRecordFactory usageRecordFactory = new UsageRecordFactory(); 16 | 17 | private UsageDataDto usageDataDto; 18 | private final String userId = "userId"; 19 | private final LocalDateTime time = LocalDateTime.now(); 20 | private final String hash = "hash"; 21 | private final String actionId = "testAdd"; 22 | 23 | @BeforeEach 24 | public void setup() { 25 | ActionEventDto actionEventDto = new ActionEventDto(actionId, time); 26 | List actionEventDtoList = new ArrayList<>(); 27 | actionEventDtoList.add(actionEventDto); 28 | usageDataDto = new UsageDataDto(userId, actionEventDtoList, hash); 29 | } 30 | 31 | @Test 32 | public void testCreateFromDto() { 33 | UsageRecord expected = new UsageRecord(userId, new Action(actionId), time); 34 | UsageRecord actual = usageRecordFactory.createUsageRecordFromDto(usageDataDto).get(0); 35 | assertEquals(expected, actual); 36 | } 37 | 38 | @Test 39 | public void testCreateFromDtoEmptyList() { 40 | usageDataDto.setActionsRecorded(new ArrayList<>()); 41 | List actualList = usageRecordFactory.createUsageRecordFromDto(usageDataDto); 42 | assertEquals(0, actualList.size()); 43 | } 44 | 45 | @Test 46 | public void testNullUserId() { 47 | usageDataDto.setUserId(null); 48 | assertThrows(NullFieldException.class, () -> { 49 | usageRecordFactory.createUsageRecordFromDto(usageDataDto); 50 | }); 51 | } 52 | 53 | @Test 54 | public void testNullHash() { 55 | usageDataDto.setHash(null); 56 | assertThrows(NullFieldException.class, () -> { 57 | usageRecordFactory.createUsageRecordFromDto(usageDataDto); 58 | }); 59 | } 60 | 61 | @Test 62 | public void testNullActions() { 63 | usageDataDto.setActionsRecorded(null); 64 | assertThrows(NullFieldException.class, () -> { 65 | usageRecordFactory.createUsageRecordFromDto(usageDataDto); 66 | }); 67 | } 68 | 69 | } --------------------------------------------------------------------------------