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
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

## [Unreleased]
### Added
NA
N/A

### Changed
- remove the annoying inspector for unittest check during writing the unittest, reason: inspector is for something that can be fast fixed, but the unittest check is not, so remove it to avoid the confusion.
Expand Down Expand Up @@ -111,4 +111,10 @@ NA

## 1.0.14 - 2025-06-08
- remove the annoying inspector for unittest check during writing the unittest, reason: inspector is for something that can be fast fixed, but the unittest check is not, so remove it to avoid the confusion.
- add the support to Junit 5 from version 5.7.0 to 5.13.0.
- add the support to Junit 5 from version 5.7.0 to 5.13.0.

## 1.0.15 - 2025-06-24
- add the support to sort the test dependencies by the order of the dependency directories. (with this for large application, user can specify which set of dependency directories to be loaded first)
- add the support to sort the test dependencies by the order of the first-load dependent jars. (with this when there are same class accross multiple jars, the class in the first-load dependent jars will be loaded first)
- align the resource directory ordering in the classpath and cp arguments with the test case file's source root.
- add the support to configure the default mutator group, dependency directories order, and first-load dependent jars.
16 changes: 15 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.markdownToHTML
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.RunIdeTask

Check warning on line 4 in build.gradle.kts

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused import directive

Unused import directive

plugins {
id("java") // Java support
Expand Down Expand Up @@ -160,10 +161,22 @@
publishPlugin {
dependsOn(patchChangelog)
}

}

intellijPlatformTesting {
runIde {
register("runIdeEn") {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
listOf(
"-Duser.language=en",
"-Duser.country=US",
)
}
}
}

register("runIdeForUiTests") {
task {
jvmArgumentProviders += CommandLineArgumentProvider {
Expand All @@ -181,4 +194,5 @@
}
}
}
}
}

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.14
pluginVersion = 1.0.15


# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
Expand Down
Binary file added screenshots/menu/right-click.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/menu/scan.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/mutation/classpath.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/mutation/history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/mutation/input-class.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/mutation/result-dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/mutation/result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/mutation/select-mutate-target.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added screenshots/mutation/select-mutate-target.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,22 @@ public JComponent createComponent() {

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

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

@Override
public void reset() {
mutationSettingsComponent.setSelectedMutatorGroup(MutationConfigService.getInstance().getMutatorGroup());
mutationSettingsComponent.setDependencyDirectoriesOrder(MutationConfigService.getInstance().getDependencyDirectoriesOrder());
mutationSettingsComponent.setFirstLoadDependentJars(MutationConfigService.getInstance().getFirstLoadDependentJars());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,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 233 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 Down Expand Up @@ -259,7 +259,7 @@
} else if (middleVersionInt == 13) {
return getPitestJunit5PluginFile("junit-platform-launcher-1.13.0.jar");
} else {
continue;

Check warning on line 262 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

Unnecessary 'continue' statement

`continue` is unnecessary as the last statement in a loop
}

}
Expand Down Expand Up @@ -296,18 +296,74 @@
return junit5PitestPluginJars;
}

private boolean matchesAnyPattern(String fileName, List<String> patterns) {
for (String pattern : patterns) {
// Convert wildcard to regex
if (fileName.matches(pattern)) {
return true;
}
}
return false;
}
private void sortTestDependencies(List<String> testDependencies) {
// sort the test dependencies by the order of the dependencies
String dependencyDirectoriesOrder = MutationConfigService.getInstance().getDependencyDirectoriesOrder();
String[] dependencyDirectories = dependencyDirectoriesOrder.split(";");
List<String> firstLoadDependentJarsPatterns = MutationConfigService.getInstance().getFirstLoadDependentJarsPatterns();

List<String> sortedDependencies = new ArrayList<>();
List<String> remainingDependencies = new ArrayList<>(testDependencies);

// 1. Add first-load dependencies
for (String dependency : new ArrayList<>(remainingDependencies)) {
String fileName = new File(dependency).getName();
if (matchesAnyPattern(fileName, firstLoadDependentJarsPatterns)) {
sortedDependencies.add(dependency);
remainingDependencies.remove(dependency);
}
}

// 2. Add dependencies in the order of the dependency directories
for (String dependencyDirectory : dependencyDirectories) {
for (String dependency : new ArrayList<>(remainingDependencies)) {
String dirName = new File(dependency).getParent();
if (dirName != null && dirName.endsWith(dependencyDirectory)) {
// Check if the dependency is in the current directory
// If so, add it to the sorted dependencies
// and remove it from the remaining dependencies
sortedDependencies.add(dependency);
remainingDependencies.remove(dependency);
}
}
}
// 3. Add remaining dependencies
sortedDependencies.addAll(remainingDependencies);
// 4. update the test dependencies
testDependencies.clear();
testDependencies.addAll(sortedDependencies);
}

private void collectClassPathFileForPitest(String reportDirectory, String targetPackageName, List<String> resourceDirectories) {
String classPathFileContent = ReadAction.compute(() -> {
// class file output path
List<String> classpath = GradleUtils.getCompilationOutputPaths(getProject());
// external jars
List<String> testDependencies = GradleUtils.getTestRunDependencies(getProject());
// sort them by the orders
sortTestDependencies(testDependencies);
// 0. add the class file output path
List<String> allDependencies = new ArrayList<>(classpath);
// 1. add the resource directories
if (resourceDirectories != null) {
allDependencies.addAll(resourceDirectories);
}
// 2. add the external jars
allDependencies.addAll(testDependencies);
// 3. add the junit 5 pitest plugin jars
if (getContext().getIsJunit5()) {
allDependencies.addAll(getJunit5PitestPluginJars(testDependencies));
}
// 4. add the tools.jar for JDK 8
if (getContext().getJavaVersion().contains("1.8.")) {
allDependencies.add(findToolsJarForJDK8());
}
Expand All @@ -317,7 +373,7 @@
getContext().setClasspathFileDirectory(Paths.get(reportDirectory, targetPackageName).toString());
File classpathDir = new File(getContext().getClasspathFileDirectory());
if (!classpathDir.exists()) {
classpathDir.mkdirs();

Check warning on line 376 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 All @@ -330,7 +386,7 @@

private void setupPitestLibDependencies(List<String> resourceDirectories) {
String pluginLibDir = ReadAction.compute(() -> PathManager.getPluginsPath() + "/TestCraft-Pro/lib");
List<String> dependencies = new ArrayList<>();

Check notice on line 389 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

Method can be extracted

It's possible to extract method returning 'dependencies' from a long surrounding method
File libDir = new File(pluginLibDir);
File[] files = libDir.listFiles();
if (files != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import org.jetbrains.annotations.Nullable;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.application.ApplicationManager;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Service(Service.Level.APP)
Expand All @@ -23,6 +26,8 @@ public static MutationConfigService getInstance() {

public static class State {
public String mutatorGroup = "STARTER_KIT"; // Default value
public String dependencyDirectoriesOrder = "bos;biz;trd"; // The order of the dependency directories
public String firstLoadDependentJars = "mockito-*.jar"; // The first load dependent jars

public State() {
}
Expand All @@ -37,7 +42,7 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(mutatorGroup);
return Objects.hash(mutatorGroup, dependencyDirectoriesOrder, firstLoadDependentJars);
}
}

Expand All @@ -51,6 +56,33 @@ public void setMutatorGroup(String mutatorGroup) {
myState.mutatorGroup = mutatorGroup;
}

public String getDependencyDirectoriesOrder() {
return myState.dependencyDirectoriesOrder;
}

public void setDependencyDirectoriesOrder(String dependencyDirectoriesOrder) {
myState.dependencyDirectoriesOrder = dependencyDirectoriesOrder;
}

public String getFirstLoadDependentJars() {
return myState.firstLoadDependentJars;
}

public List<String> getFirstLoadDependentJarsPatterns() {
List<String> firstLoadDependentJarsArray = new ArrayList<>();
String[] patterns = myState.firstLoadDependentJars.split(";");
for (String pattern : patterns) {
// Convert wildcard to regex
String regex = pattern.replace(".", "\\.").replace("*", ".*");
firstLoadDependentJarsArray.add(regex);
}
return firstLoadDependentJarsArray;
}

public void setFirstLoadDependentJars(String firstLoadDependentJars) {
myState.firstLoadDependentJars = firstLoadDependentJars;
}

@Nullable
@Override
public State getState() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.github.jaksonlin.testcraft.presentation.components.configuration;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

Check warning on line 5 in src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/MutationSettingsComponent.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused import

Unused import `import java.awt.event.ActionEvent;`
import java.awt.event.ActionListener;

Check warning on line 6 in src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/MutationSettingsComponent.java

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused import

Unused import `import java.awt.event.ActionListener;`

import com.github.jaksonlin.testcraft.infrastructure.services.config.MutationConfigService;
import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.FormBuilder;
Expand All @@ -11,15 +15,75 @@
private final JPanel mainPanel;
private final ComboBox<String> mutatorGroupComboBox;

// Dependency Directories UI
private final DefaultListModel<String> dependencyDirsModel = new DefaultListModel<>();
private final JList<String> dependencyDirsList = new JList<>(dependencyDirsModel);

Check warning on line 20 in src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/MutationSettingsComponent.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

// First-Load JAR Patterns UI
private final DefaultListModel<String> firstLoadJarsModel = new DefaultListModel<>();
private final JList<String> firstLoadJarsList = new JList<>(firstLoadJarsModel);

Check warning on line 24 in src/main/java/com/github/jaksonlin/testcraft/presentation/components/configuration/MutationSettingsComponent.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 MutationSettingsComponent() {
mutatorGroupComboBox = new ComboBox<>(new String[]{"DEFAULTS", "STRONGER", "STARTER_KIT"});
mutatorGroupComboBox.setSelectedItem(MutationConfigService.getInstance().getMutatorGroup());

// Load initial values
for (String dir : MutationConfigService.getInstance().getDependencyDirectoriesOrder().split(";")) {
if (!dir.trim().isEmpty()) dependencyDirsModel.addElement(dir.trim());
}
for (String pattern : MutationConfigService.getInstance().getFirstLoadDependentJars().split(";")) {
if (!pattern.trim().isEmpty()) firstLoadJarsModel.addElement(pattern.trim());
}

// Dependency Directories Panel
JPanel depDirsPanel = createEditableListPanel(
I18nService.getInstance().message("settings.mutation.dependency.directories.order.label"),
dependencyDirsList, dependencyDirsModel);

// First-Load JAR Patterns Panel
JPanel firstLoadJarsPanel = createEditableListPanel(
I18nService.getInstance().message("settings.mutation.first.load.jar.patterns.label"),
firstLoadJarsList, firstLoadJarsModel);

mainPanel = FormBuilder.createFormBuilder()
.addLabeledComponent(new JBLabel("Default Mutator Group:"), mutatorGroupComboBox)
.addLabeledComponent(new JBLabel(I18nService.getInstance().message("settings.mutation.default.mutator.group.label")), mutatorGroupComboBox)
.addComponent(depDirsPanel)
.addComponent(firstLoadJarsPanel)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}

private JPanel createEditableListPanel(String label, JList<String> list, DefaultListModel<String> model) {
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JBLabel(label), BorderLayout.NORTH);

JScrollPane scrollPane = new JScrollPane(list);
panel.add(scrollPane, BorderLayout.CENTER);

JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton addButton = new JButton(I18nService.getInstance().message("settings.mutation.button.add"));
JButton removeButton = new JButton(I18nService.getInstance().message("settings.mutation.button.remove"));

addButton.addActionListener(e -> {
String input = JOptionPane.showInputDialog(panel, I18nService.getInstance().message("settings.mutation.dialog.enter.value"));
if (input != null && !input.trim().isEmpty()) {
model.addElement(input.trim());
}
});

removeButton.addActionListener(e -> {
int selected = list.getSelectedIndex();
if (selected != -1) {
model.remove(selected);
}
});

buttonsPanel.add(addButton);
buttonsPanel.add(removeButton);
panel.add(buttonsPanel, BorderLayout.SOUTH);

panel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
return panel;
}

public JPanel getPanel() {
Expand All @@ -34,4 +98,37 @@
mutatorGroupComboBox.setSelectedItem(mutatorGroup);
}

// New: Getters for the dependency settings
public String getDependencyDirectoriesOrder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < dependencyDirsModel.size(); i++) {
if (i > 0) sb.append(";");
sb.append(dependencyDirsModel.get(i));
}
return sb.toString();
}

public String getFirstLoadDependentJars() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < firstLoadJarsModel.size(); i++) {
if (i > 0) sb.append(";");
sb.append(firstLoadJarsModel.get(i));
}
return sb.toString();
}

// New: Setters for loading from config if needed
public void setDependencyDirectoriesOrder(String dirs) {
dependencyDirsModel.clear();
for (String dir : dirs.split(";")) {
if (!dir.trim().isEmpty()) dependencyDirsModel.addElement(dir.trim());
}
}

public void setFirstLoadDependentJars(String patterns) {
firstLoadJarsModel.clear();
for (String pattern : patterns.split(";")) {
if (!pattern.trim().isEmpty()) firstLoadJarsModel.addElement(pattern.trim());
}
}
}
9 changes: 8 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@

<change-notes><![CDATA[
<h2>Change Notes</h2>
<h3>1.0.15 - 2025-06-24</h3>
<ul>
<li>add the support to sort the test dependencies by the order of the dependency directories. (with this for large application, user can specify which set of dependency directories to be loaded first)</li>
<li>add the support to sort the test dependencies by the order of the first-load dependent jars. (with this when there are same class accross multiple jars, the class in the first-load dependent jars will be loaded first)</li>
<li>align the resource directory ordering in the classpath and cp arguments with the test case file's source root.</li>
<li>add the support to configure the default mutator group, dependency directories order, and first-load dependent jars.</li>
</ul>
<h3>1.0.14 - 2025-06-08</h3>
<ul>
<li>remove the annoying inspector for unittest check during writing the unittest, reason: inspector is for something that can be fast fixed, but the unittest check is not, so remove it to avoid the confusion.</li>
Expand Down Expand Up @@ -227,7 +234,7 @@
<!-- Add right-click entry to run Pitest on a JUnit test file -->
<actions>
<!-- First Level Menu -->
<group id="UnittestHelperToolMenu" text="Unittest Helpers" popup="true" description="Java unittest helper tool" icon="/icons/testcraft.svg">
<group id="UnittestHelperToolMenu" text="TestCraft Unittest Helpers" popup="true" description="Java unittest helper tool" icon="/icons/testcraft.svg">
<add-to-group group-id="EditorPopupMenu" anchor="last"/>
</group>

Expand Down
Loading
Loading