Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<idea-plugin>
<!-- Other configurations -->

<depends>com.intellij.modules.json</depends>
</idea-plugin><!-- Keep a Changelog guide -> https://keepachangelog.com -->
</idea-plugin>

<!-- Keep a Changelog guide -> https://keepachangelog.com -->

# TestCraft Pro Changelog

Expand Down Expand Up @@ -79,3 +80,28 @@
- lib dependency upgrade, use fastjson to parse the annotation schema
- put all toolwindow into one multi-tab toolwindow
- invalid test case scan on directory

## 1.0.12 - 2025-05-14
- add plugin icon
- add i18n for testcase scan result
- fix a bug that the testcase scan result is not shown in the toolwindow

## 1.0.13 - 2025-05-19

### Added
- Mutation Testing Settings:
- Added a new settings page under Settings → TestCraft → Mutation Testing that allows users to configure the default mutator group for mutation testing.
- Supported mutator groups: DEFAULTS, STRONGER, STARTER_KIT.
- The selected group is now used for all mutation test runs.
- Internationalization:
- Added i18n keys for the new mutation testing settings.
### Changed
- Pitest tool update:
- Update the pitest tool to 1.17.4 the last version that support JDK8 runtime.
- Based on 1.17.4 version, add the support to run mutation test on specific class method.
- Based on 1.17.4 version, add the new mutator group: STARTER_KIT, which is a set of mutators that are more likely to be useful for beginners.
- Mutation Test Command:
- The mutation test runner now uses the mutator group selected in the settings instead of a hardcoded value.
- Tool window:
- Update the tool window to show the test case scan result on completion.
- Move all tool window into one multi-tab tool window.
1 change: 1 addition & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This product includes software developed by:
- JavaParser (https://javaparser.org)
- FasterXML, LLC (Jackson Project)
- JUnit Team (JUnit Library)
- PITest (http://pitest.org/) - Modified version included in lib/pitest-*.jar

This product is a derivative work that evolved from pitest-gradle.
The original pitest-gradle was licensed under MIT and has been rewritten
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pluginName = TestCraft-Pro
pluginRepositoryUrl = https://github.com/jaksonlin/testcraft-pro
# SemVer format -> https://semver.org

pluginVersion = 1.0.11
pluginVersion = 1.0.13


# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

public class RunPitestAction extends AnAction {

private final PitestService pitestService;

Check warning on line 29 in src/main/java/com/github/jaksonlin/testcraft/application/actions/RunPitestAction.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Field can be local variable

Field can be converted to a local variable

public RunPitestAction() {
pitestService = ApplicationManager.getApplication().getService(PitestService.class);
Expand Down Expand Up @@ -61,6 +61,7 @@

List<PitestCommand> commands = Arrays.asList(
new PrepareEnvironmentCommand(targetProject, context),
new MethodToMutateCommand(targetProject, context),
new BuildPitestCommandCommand(targetProject, context),
new RunPitestCommand(targetProject, context),
new HandlePitestResultCommand(targetProject, context),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.jaksonlin.testcraft.application.settings;

import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent;

import com.github.jaksonlin.testcraft.infrastructure.services.config.MutationConfigService;
import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService;

import com.github.jaksonlin.testcraft.presentation.components.configuration.MutationSettingsComponent;

public class MutationSettingsConfigurable implements Configurable {
private MutationSettingsComponent mutationSettingsComponent;

@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return I18nService.getInstance().message("settings.testcraft.mutation");
}

@Nullable
@Override
public JComponent createComponent() {
mutationSettingsComponent = new MutationSettingsComponent();
return mutationSettingsComponent.getPanel();
}

@Override
public boolean isModified() {
return !mutationSettingsComponent.getSelectedMutatorGroup().equals(MutationConfigService.getInstance().getMutatorGroup());
}

@Override
public void apply() throws ConfigurationException {
MutationConfigService.getInstance().setMutatorGroup(mutationSettingsComponent.getSelectedMutatorGroup());
}

@Override
public void reset() {
mutationSettingsComponent.setSelectedMutatorGroup(MutationConfigService.getInstance().getMutatorGroup());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ public class PitestContext {
private final long timestamp;
private List<Mutation> mutationResults;
private String workingDirectory;
private String methodsToMutate;
private String mutatorGroup;

public String getMutatorGroup() {
return mutatorGroup;
}

public void setMutatorGroup(String mutatorGroup) {
this.mutatorGroup = mutatorGroup;
}

public String getWorkingDirectory() {
return workingDirectory;
Expand Down Expand Up @@ -198,6 +208,14 @@ public long getTimestamp() {
return timestamp;
}

public String getMethodsToMutate() {
return methodsToMutate;
}

public void setMethodsToMutate(String methodsToMutate) {
this.methodsToMutate = methodsToMutate;
}

public static String dumpPitestContext(PitestContext context) {
return "testVirtualFile: " + context.getTestFilePath() + "\n" +
"fullyQualifiedTargetTestClassName: " + context.getFullyQualifiedTargetTestClassName() + "\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public void execute() {
throw new IllegalStateException("Java home not set");
}

String mutatorGroup = getContext().getMutatorGroup();
if (mutatorGroup == null) {
throw new IllegalStateException("Mutator group not set");
}

String javaExe = javaHome + "/bin/java";

List<String> command = new ArrayList<>();
Expand Down Expand Up @@ -77,9 +82,14 @@ public void execute() {
command.add("--timeoutFactor");
command.add("2.0");
command.add("--mutators");
command.add("STRONGER");
command.add(mutatorGroup);
command.add("--skipFailingTests");

if (getContext().getMethodsToMutate() != null) {
command.add("--targetMethods");
command.add(getContext().getMethodsToMutate());
}

getContext().setCommand(command);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.github.jaksonlin.testcraft.infrastructure.commands.pitest;

import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import com.github.jaksonlin.testcraft.domain.context.PitestContext;
import com.github.jaksonlin.testcraft.presentation.components.common.ItemSelectionComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.Project;
import com.github.jaksonlin.testcraft.util.ClassFileInfo;
import com.github.jaksonlin.testcraft.util.JavaFileProcessor;

public class MethodToMutateCommand extends PitestCommand {
private final JavaFileProcessor javaFileProcessor = new JavaFileProcessor();
public MethodToMutateCommand(Project project, PitestContext context) {
super(project, context);
}

@Override
public void execute() {
List<String> methods = getTargetClassMethods();
if (methods == null || methods.isEmpty()) {
return;
}
getContext().setMethodsToMutate(String.join(",", methods));
}

private List<String> getTargetClassMethods() {
ClassFileInfo classFileInfo = javaFileProcessor.getFullyQualifiedName(getContext().getTargetClassFilePath());
List<String> methods = classFileInfo.getMethods();
ItemSelectionComponent<String> itemSelectionComponent = new ItemSelectionComponent<String>(getProject(), "Select the methods to mutate");
itemSelectionComponent.setItems(methods);
AtomicReference<List<String>> result = new AtomicReference<>();
ApplicationManager.getApplication().invokeAndWait(() -> {
itemSelectionComponent.showDialog();
result.set(itemSelectionComponent.getSelectedItems());
}, ModalityState.defaultModalityState());


// format into QualifiedClassName::methodName
return result.get().stream()
.map(method -> classFileInfo.getFullyQualifiedName() + "::" + method)
.collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.github.jaksonlin.testcraft.infrastructure.commands.pitest;

import com.github.jaksonlin.testcraft.domain.context.PitestContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.Project;

import com.github.jaksonlin.testcraft.presentation.components.common.ItemSearchAdditionComponent;
import com.github.jaksonlin.testcraft.util.ClassFileInfo;
import com.github.jaksonlin.testcraft.util.FileUtils;
import com.github.jaksonlin.testcraft.util.TargetClassInfo;
import com.github.jaksonlin.testcraft.util.JavaFileProcessor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

public class MultiTargetClassCheckCommand extends PitestCommand {
private final JavaFileProcessor javaFileProcessor = new JavaFileProcessor();
public MultiTargetClassCheckCommand(Project project, PitestContext context) {
super(project, context);
}

@Override
public void execute() {
List<String> targetClassFilePaths = getMultiTargetClass();
if (targetClassFilePaths == null || targetClassFilePaths.isEmpty()) {
return;

Check warning on line 28 in src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MultiTargetClassCheckCommand.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unnecessary 'return' statement

`return` is unnecessary as the last statement in a 'void' method
}
//getContext().setTargetClassFilePaths(targetClassFilePaths);
}

private List<String> getMultiTargetClass() {
ItemSearchAdditionComponent<String> itemSearchAdditionComponent = new ItemSearchAdditionComponent<String>(getProject(), this::getTargetClassFullyQualifiedName, this::displayCandidateClass);
AtomicReference<List<String>> result = new AtomicReference<>();
ApplicationManager.getApplication().invokeAndWait(() -> {
itemSearchAdditionComponent.showDialog("Select the target class");
result.set(itemSearchAdditionComponent.getSelectedItems());
}, ModalityState.defaultModalityState());
return result.get();
}

private List<String> getTargetClassFullyQualifiedName(String classCandidateName) {
List<String> sourceRoots = getContext().getSourceRoots();
TargetClassInfo targetClassInfo = FileUtils.findTargetClassFile(sourceRoots, classCandidateName);
if (targetClassInfo == null) {
showError("Cannot find target class file");
throw new IllegalStateException("Cannot find target class file");
}
ClassFileInfo classInfo = javaFileProcessor.getFullyQualifiedName(targetClassInfo.getFile().toString());

if (classInfo == null) {
showError("Cannot get fully qualified name for target class");
throw new IllegalStateException("Cannot get fully qualified name for target class");
}
List<String> fullyQualifiedNames = new ArrayList<>();
fullyQualifiedNames.add(classInfo.getFullyQualifiedName());
return fullyQualifiedNames;
}

private String displayCandidateClass(String classCandidateName) {
return classCandidateName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.List;

import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService;
import com.github.jaksonlin.testcraft.infrastructure.services.config.MutationConfigService;

public class PrepareEnvironmentCommand extends PitestCommand {
private final JavaFileProcessor javaFileProcessor = new JavaFileProcessor();
Expand All @@ -44,6 +45,7 @@
collectSourceRoots();
collectResourceDirectories();
setWorkingDirectory();
collectMutatorGroup();

if (getContext().getSourceRoots() != null) {
collectTargetClassThatWeTest(getContext().getSourceRoots());
Expand Down Expand Up @@ -190,7 +192,7 @@
getContext().setReportDirectory(Paths.get(parentModulePath, "build", "reports", "pitest", className).toString());
File reportDir = new File(getContext().getReportDirectory());
if (!reportDir.exists()) {
reportDir.mkdirs();

Check warning on line 195 in src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Result of method call ignored

Result of `File.mkdirs()` is ignored
}
}

Expand All @@ -209,7 +211,7 @@
getContext().setClasspathFileDirectory(Paths.get(reportDirectory, targetPackageName).toString());
File classpathDir = new File(getContext().getClasspathFileDirectory());
if (!classpathDir.exists()) {
classpathDir.mkdirs();

Check warning on line 214 in src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Result of method call ignored

Result of `File.mkdirs()` is ignored
}
getContext().setClasspathFile(Paths.get(getContext().getClasspathFileDirectory(), "classpath.txt").toString());
try {
Expand Down Expand Up @@ -241,4 +243,8 @@
}
getContext().setPitestDependencies(String.join(File.pathSeparator, dependencies));
}

private void collectMutatorGroup() {
getContext().setMutatorGroup(MutationConfigService.getInstance().getMutatorGroup());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import com.github.jaksonlin.testcraft.domain.context.PitestContext;
import com.intellij.openapi.project.Project;

import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService;
import com.intellij.openapi.application.ApplicationManager;
public class StoreHistoryCommand extends PitestCommand {

public StoreHistoryCommand(Project project, PitestContext context) {
Expand All @@ -12,5 +15,15 @@
@Override
public void execute() {
runHistoryManager.saveRunHistory(getContext());
if (getContext().getProcessResult().getExitCode() == 0)
ApplicationManager.getApplication().invokeLater(() -> {

Check warning on line 19 in src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/StoreHistoryCommand.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Suspicious indentation after control statement without braces

Suspicious indentation after 'if' statement
ToolWindow toolWindow = ToolWindowManager.getInstance(this.getProject()).getToolWindow("TestCraft");
if (toolWindow != null) {
toolWindow.show();
toolWindow.getContentManager().setSelectedContent(
toolWindow.getContentManager().findContent(I18nService.getInstance().message("toolwindow.mutation.tab.name"))
);
}
});
}
}
Loading
Loading