diff --git a/CHANGELOG.md b/CHANGELOG.md index f3fa8a3..14db6fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,10 @@ ## [Unreleased] ### Added +NA +### 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. ## 1.0.0 - 2024-10-12 ### Initial Release @@ -105,3 +108,7 @@ - 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.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. \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 82b4371..df1e6ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ pluginName = TestCraft-Pro pluginRepositoryUrl = https://github.com/jaksonlin/testcraft-pro # SemVer format -> https://semver.org -pluginVersion = 1.0.13 +pluginVersion = 1.0.14 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html diff --git a/lib/apiguardian-api-1.1.2.jar b/lib/apiguardian-api-1.1.2.jar new file mode 100644 index 0000000..2b678e1 Binary files /dev/null and b/lib/apiguardian-api-1.1.2.jar differ diff --git a/lib/junit-platform-launcher-1.12.2.jar b/lib/junit-platform-launcher-1.12.2.jar new file mode 100644 index 0000000..5310fee Binary files /dev/null and b/lib/junit-platform-launcher-1.12.2.jar differ diff --git a/lib/junit-platform-launcher-1.13.0.jar b/lib/junit-platform-launcher-1.13.0.jar new file mode 100644 index 0000000..8cbdc96 Binary files /dev/null and b/lib/junit-platform-launcher-1.13.0.jar differ diff --git a/lib/junit-platform-launcher-1.9.2.jar b/lib/junit-platform-launcher-1.9.2.jar new file mode 100644 index 0000000..4671007 Binary files /dev/null and b/lib/junit-platform-launcher-1.9.2.jar differ diff --git a/lib/pitest-junit5-plugin-1.2.2.jar b/lib/pitest-junit5-plugin-1.2.2.jar new file mode 100644 index 0000000..0420809 Binary files /dev/null and b/lib/pitest-junit5-plugin-1.2.2.jar differ diff --git a/src/main/java/com/github/jaksonlin/testcraft/application/actions/PreCommitValidationAction.java b/src/main/java/com/github/jaksonlin/testcraft/application/actions/PreCommitValidationAction.java new file mode 100644 index 0000000..f901b4f --- /dev/null +++ b/src/main/java/com/github/jaksonlin/testcraft/application/actions/PreCommitValidationAction.java @@ -0,0 +1,140 @@ +package com.github.jaksonlin.testcraft.application.actions; + +import com.github.jaksonlin.testcraft.infrastructure.commands.testscan.UnittestFileBatchScanCommand; +import com.github.jaksonlin.testcraft.infrastructure.services.business.RunHistoryManagerService; +import com.github.jaksonlin.testcraft.infrastructure.services.config.InvalidTestCaseConfigService; +import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.impl.SimpleDataContext; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.CheckinProjectPanel; +import com.intellij.openapi.vcs.changes.CommitContext; +import com.intellij.openapi.vcs.checkin.CheckinHandler; +import com.intellij.openapi.vcs.checkin.CheckinHandlerFactory; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.openapi.ui.Messages; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class PreCommitValidationAction extends CheckinHandlerFactory { + @Override + public @NotNull CheckinHandler createHandler(@NotNull CheckinProjectPanel checkinProjectPanel, @NotNull CommitContext commitContext) { + return null; + } +// private final InvalidTestCaseConfigService invalidTestCaseConfigService; +// private final RunHistoryManagerService runHistoryManager; +// +// public PreCommitValidationAction() { +// this.invalidTestCaseConfigService = ApplicationManager.getApplication().getService(InvalidTestCaseConfigService.class); +// this.runHistoryManager = RunHistoryManagerService.getInstance(); +// } +// +// @Override +// public @NotNull CheckinHandler createHandler(@NotNull CheckinProjectPanel checkinProjectPanel, @NotNull CommitContext commitContext) { +// +// return new CheckinHandler() { +// @Override +// public ReturnResult beforeCheckin() { +// try { +// if (!invalidTestCaseConfigService.isEnable()) { +// return ReturnResult.COMMIT; +// } +// +// Project project = checkinProjectPanel.getProject(); +// if (project == null) { +// return ReturnResult.COMMIT; +// } +// +// Collection files = checkinProjectPanel.getVirtualFiles(); +// if (files == null || files.isEmpty()) { +// return ReturnResult.COMMIT; +// } +// +// // Collect test files +// List testFiles = new ArrayList<>(); +// PsiManager psiManager = PsiManager.getInstance(project); +// for (VirtualFile file : files) { +// if (file.getName().endsWith("Test.java")) { +// PsiFile psiFile = psiManager.findFile(file); +// if (psiFile != null) { +// testFiles.add(psiFile); +// } +// } +// } +// +// if (!testFiles.isEmpty()) { +// // Create a simple data context for the action event +// DataContext dataContext = SimpleDataContext.getProjectContext(project); +// AnActionEvent actionEvent = AnActionEvent.createFromDataContext( +// "PreCommitValidation", +// null, +// dataContext +// ); +// +// // Run validation on all test files +// UnittestFileBatchScanCommand scanCommand = new UnittestFileBatchScanCommand(project, actionEvent); +// +// // Create a CompletableFuture to handle the validation result +// CompletableFuture validationFuture = new CompletableFuture<>(); +// +// // Add a listener to handle the validation result +//// scanCommand.setOnValidationCompleteListener(hasInvalidTests -> { +//// validationFuture.complete(hasInvalidTests); +//// }); +// +// // Execute the validation +// scanCommand.execute(); +// +// try { +// // Wait for validation to complete with a timeout +// boolean hasInvalidTests = validationFuture.get(30, TimeUnit.SECONDS); +// +// if (hasInvalidTests) { +// // Show error message and block commit +// Messages.showErrorDialog( +// project, +// I18nService.getInstance().message("testscan.commit_blocked_message"), +// I18nService.getInstance().message("testscan.test_case_validation_results") +// ); +// +// return ReturnResult.CANCEL; +// } +// } catch (InterruptedException | ExecutionException | TimeoutException ex) { +// // Handle timeout or other errors +// Messages.showErrorDialog( +// project, +// I18nService.getInstance().message("testscan.validation_timeout_message"), +// I18nService.getInstance().message("testscan.test_case_validation_results") +// ); +// return ReturnResult.CANCEL; +// } +// } +// +// return ReturnResult.COMMIT; +// } catch (Exception e) { +// // Handle any unexpected errors +// Project project = checkinProjectPanel.getProject(); +// if (project != null) { +// Messages.showErrorDialog( +// project, +// "An unexpected error occurred during pre-commit validation: " + e.getMessage(), +// "Pre-commit Validation Error" +// ); +// } +// return ReturnResult.COMMIT; +// } +// } +// }; +// } +} \ No newline at end of file diff --git a/src/main/java/com/github/jaksonlin/testcraft/domain/context/PitestContext.java b/src/main/java/com/github/jaksonlin/testcraft/domain/context/PitestContext.java index c08897c..933a389 100644 --- a/src/main/java/com/github/jaksonlin/testcraft/domain/context/PitestContext.java +++ b/src/main/java/com/github/jaksonlin/testcraft/domain/context/PitestContext.java @@ -30,6 +30,24 @@ public class PitestContext { private String workingDirectory; private String methodsToMutate; private String mutatorGroup; + private Boolean isJunit5; + private String javaVersion; + + public String getJavaVersion() { + return javaVersion; + } + + public void setJavaVersion(String javaVersion) { + this.javaVersion = javaVersion; + } + + public Boolean getIsJunit5() { + return isJunit5; + } + + public void setIsJunit5(Boolean isJunit5) { + this.isJunit5 = isJunit5; + } public String getMutatorGroup() { return mutatorGroup; diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MethodToMutateCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MethodToMutateCommand.java index 546e10e..3096ead 100644 --- a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MethodToMutateCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MethodToMutateCommand.java @@ -1,6 +1,8 @@ package com.github.jaksonlin.testcraft.infrastructure.commands.pitest; +import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -28,21 +30,29 @@ public void execute() { } private List getTargetClassMethods() { - ClassFileInfo classFileInfo = javaFileProcessor.getFullyQualifiedName(getContext().getTargetClassFilePath()); - List methods = classFileInfo.getMethods(); - ItemSelectionComponent itemSelectionComponent = new ItemSelectionComponent(getProject(), "Select the methods to mutate"); - itemSelectionComponent.setItems(methods); - AtomicReference> 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()); + try { + Optional classFileInfo = javaFileProcessor.getFullyQualifiedName(getContext().getTargetClassFilePath()); + if (!classFileInfo.isPresent()) { + showError("Cannot get fully qualified name for target class"); + throw new IllegalStateException("Cannot get fully qualified name for target class"); + } + List methods = classFileInfo.get().getMethods(); + ItemSelectionComponent itemSelectionComponent = new ItemSelectionComponent(getProject(), "Select the methods to mutate"); + itemSelectionComponent.setItems(methods); + AtomicReference> 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.get().getFullyQualifiedName() + "::" + method) + .collect(Collectors.toList()); + } catch(IOException ex){ + showError("Error getting fully qualified name for target class: " + ex.getMessage()); + throw new IllegalStateException("Error getting fully qualified name for target class", ex); + } + } } diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MultiTargetClassCheckCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MultiTargetClassCheckCommand.java index 43cf350..c693e4d 100644 --- a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MultiTargetClassCheckCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/MultiTargetClassCheckCommand.java @@ -11,8 +11,10 @@ import com.github.jaksonlin.testcraft.util.TargetClassInfo; import com.github.jaksonlin.testcraft.util.JavaFileProcessor; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; public class MultiTargetClassCheckCommand extends PitestCommand { @@ -47,15 +49,20 @@ private List getTargetClassFullyQualifiedName(String classCandidateName) showError("Cannot find target class file"); throw new IllegalStateException("Cannot find target class file"); } - ClassFileInfo classInfo = javaFileProcessor.getFullyQualifiedName(targetClassInfo.getFile().toString()); + try { + Optional 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"); + if (!classInfo.isPresent()) { + showError("Cannot get fully qualified name for target class"); + throw new IllegalStateException("Cannot get fully qualified name for target class"); + } + List fullyQualifiedNames = new ArrayList<>(); + fullyQualifiedNames.add(classInfo.get().getFullyQualifiedName()); + return fullyQualifiedNames; + } catch (IOException e) { + showError("Error getting fully qualified name for target class: " + e.getMessage()); + throw new IllegalStateException("Error getting fully qualified name for target class", e); } - List fullyQualifiedNames = new ArrayList<>(); - fullyQualifiedNames.add(classInfo.getFullyQualifiedName()); - return fullyQualifiedNames; } private String displayCandidateClass(String classCandidateName) { diff --git a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java index b7d8b30..e40ecda 100644 --- a/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java +++ b/src/main/java/com/github/jaksonlin/testcraft/infrastructure/commands/pitest/PrepareEnvironmentCommand.java @@ -16,10 +16,14 @@ import java.io.File; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.github.jaksonlin.testcraft.infrastructure.services.system.I18nService; import com.github.jaksonlin.testcraft.infrastructure.services.config.MutationConfigService; @@ -40,11 +44,13 @@ public void execute() { throw new IllegalStateException("Cannot find test file"); } - collectTargetTestClassName(getContext().getTestFilePath()); - collectJavaInfo(testVirtualFile); + + collectTargetTestClassInfo(getContext().getTestFilePath()); + collectJavaHome(testVirtualFile); collectSourceRoots(); - collectResourceDirectories(); + setWorkingDirectory(); + collectResourceDirectories(); collectMutatorGroup(); if (getContext().getSourceRoots() != null) { @@ -74,18 +80,23 @@ public void execute() { } } - private void collectTargetTestClassName(String targetTestClassFilePath) { - ClassFileInfo testClassInfo = javaFileProcessor.getFullyQualifiedName(targetTestClassFilePath); + private void collectTargetTestClassInfo(String targetTestClassFilePath) { + try { + Optional testClassInfo = javaFileProcessor.getFullyQualifiedName(targetTestClassFilePath); - if (testClassInfo == null) { - showError("Cannot get fully qualified name for target test class"); - throw new IllegalStateException("Cannot get fully qualified name for target test class"); + if (!testClassInfo.isPresent()) { + showError("Cannot get fully qualified name for target test class"); + throw new IllegalStateException("Cannot get fully qualified name for target test class"); + } + getContext().setFullyQualifiedTargetTestClassName(testClassInfo.get().getFullyQualifiedName()); + getContext().setIsJunit5(testClassInfo.get().getImports().contains("org.junit.jupiter.api.Test")); + } catch (IOException e) { + showError("Error getting fully qualified name for target test class: " + e.getMessage()); + throw new IllegalStateException("Error getting fully qualified name for target test class", e); } - - getContext().setFullyQualifiedTargetTestClassName(testClassInfo.getFullyQualifiedName()); } - private void collectJavaInfo(VirtualFile testVirtualFile) { + private void collectJavaHome(VirtualFile testVirtualFile) { ReadAction.run(() -> { Module projectModule = ProjectRootManager.getInstance(getProject()).getFileIndex().getModuleForFile(testVirtualFile); if (projectModule == null) { @@ -96,6 +107,7 @@ private void collectJavaInfo(VirtualFile testVirtualFile) { ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(projectModule); if (moduleRootManager.getSdk() != null) { getContext().setJavaHome(moduleRootManager.getSdk().getHomePath()); + getContext().setJavaVersion(moduleRootManager.getSdk().getVersionString()); } }); if (getContext().getJavaHome() == null || getContext().getJavaHome().isEmpty()) { @@ -142,9 +154,31 @@ private void setWorkingDirectory() { getContext().setWorkingDirectory(sourceRoot); } + private String findToolsJarForJDK8() { + String javaHome = getContext().getJavaHome(); + File javaHomeFile = new File(javaHome); + File toolsJarFile = new File(javaHomeFile, "lib/tools.jar"); + return toolsJarFile.getAbsolutePath(); + } + private void collectResourceDirectories() { List resourceDirectories = ReadAction.compute(() -> GradleUtils.getResourceDirectories(getProject())); - getContext().setResourceDirectories(resourceDirectories); + String workingDirectory = getContext().getWorkingDirectory(); + // the order of the resource directories is important, the first one should share the same parent directory as working dir + List newResourceDirectories = new ArrayList<>(); + if (!resourceDirectories.isEmpty()) { + for (String resourceDirectory : resourceDirectories) { + if (resourceDirectory.replace("\\", "/").startsWith(workingDirectory.replace("\\", "/"))) { + newResourceDirectories.add(resourceDirectory); + } + } + for (String resourceDirectory : resourceDirectories) { + if (!newResourceDirectories.contains(resourceDirectory)) { + newResourceDirectories.add(resourceDirectory); + } + } + } + getContext().setResourceDirectories(newResourceDirectories); } private void collectTargetClassThatWeTest(List sourceRoots) { @@ -161,17 +195,21 @@ private void collectTargetClassThatWeTest(List sourceRoots) { 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"); + try { + Optional classInfo = javaFileProcessor.getFullyQualifiedName(targetClassInfo.getFile().toString()); + if (!classInfo.isPresent()) { + showError("Cannot get fully qualified name for target class"); + throw new IllegalStateException("Cannot get fully qualified name for target class"); + } + getContext().setTargetClassFullyQualifiedName(classInfo.get().getFullyQualifiedName()); + getContext().setTargetClassPackageName(classInfo.get().getPackageName()); + getContext().setTargetClassName(classInfo.get().getClassName()); + getContext().setTargetClassSourceRoot(targetClassInfo.getSourceRoot().toString()); + getContext().setTargetClassFilePath(targetClassInfo.getFile().normalize().toString().replace("\\", "/")); + } catch (IOException e) { + showError("Error getting fully qualified name for target class: " + e.getMessage()); + throw new IllegalStateException("Error getting fully qualified name for target class", e); } - getContext().setTargetClassFullyQualifiedName(classInfo.getFullyQualifiedName()); - getContext().setTargetClassPackageName(classInfo.getPackageName()); - getContext().setTargetClassName(classInfo.getClassName()); - getContext().setTargetClassSourceRoot(targetClassInfo.getSourceRoot().toString()); - getContext().setTargetClassFilePath(targetClassInfo.getFile().normalize().toString().replace("\\", "/")); } private void prepareReportDirectory(VirtualFile testVirtualFile, String className) { @@ -196,6 +234,68 @@ private void prepareReportDirectory(VirtualFile testVirtualFile, String classNam } } + private List getJunit5PitestPluginJars(List testDependencies) { + // use regex to match version string like "junit-jupiter-5.7.0.jar" or "junit-jupiter-5.8.1.jar" + Pattern pattern = Pattern.compile("(\\d+\\.\\d+\\.\\d+)\\.jar"); + for (String dependency : testDependencies) { + if (!dependency.contains("junit-jupiter-")) { + continue; + } + String fileName = dependency.substring(dependency.lastIndexOf(File.separator) + 1); + Matcher matcher = pattern.matcher(fileName); + if (!matcher.find()) { + continue; + } + String version = matcher.group(1); + String[] versionParts = version.split("\\."); + if (versionParts.length < 3) { + continue; + } + int middleVersionInt = Integer.parseInt(versionParts[1]); + if (middleVersionInt >= 7 && middleVersionInt <= 11) { + return getPitestJunit5PluginFile("junit-platform-launcher-1.9.2.jar"); + } else if (middleVersionInt == 12) { + return getPitestJunit5PluginFile("junit-platform-launcher-1.12.2.jar"); + } else if (middleVersionInt == 13) { + return getPitestJunit5PluginFile("junit-platform-launcher-1.13.0.jar"); + } else { + continue; + } + + } + throw new IllegalStateException("Cannot find JUnit 5 version in dependency: " + testDependencies); + } + + private List getPitestJunit5PluginFile(String junitPlatformLauncherJar) { + String pluginLibDir = ReadAction.compute(() -> PathManager.getPluginsPath() + "/TestCraft-Pro/lib"); + + List junit5PitestPluginJars = new ArrayList<>(); + // check if the jar file exists in the plugin lib directory + File junitPlatformLauncherFile = new File(pluginLibDir, junitPlatformLauncherJar); + if (junitPlatformLauncherFile.exists()) { + junit5PitestPluginJars.add(junitPlatformLauncherFile.getAbsolutePath()); + + } else { + showError("Cannot find JUnit Platform Launcher jar: " + junitPlatformLauncherJar); + throw new IllegalStateException("Cannot find JUnit Platform Launcher jar: " + junitPlatformLauncherJar); + } + File apiGuardianFile = new File(pluginLibDir, "apiguardian-api-1.1.2.jar"); + if (apiGuardianFile.exists()) { + junit5PitestPluginJars.add(apiGuardianFile.getAbsolutePath()); + } else { + showError("Cannot find API Guardian jar: " + "apiguardian-api-1.1.2.jar"); + throw new IllegalStateException("Cannot find API Guardian jar: " + "apiguardian-api-1.1.2.jar"); + } + File pitestJunit5PluginFile = new File(pluginLibDir, "pitest-junit5-plugin-1.2.2.jar"); + if (pitestJunit5PluginFile.exists()) { + junit5PitestPluginJars.add(pitestJunit5PluginFile.getAbsolutePath()); + } else { + showError("Cannot find PITest JUnit 5 plugin jar: " + "pitest-junit5-plugin-1.2.2.jar"); + throw new IllegalStateException("Cannot find PITest JUnit 5 plugin jar: " + "pitest-junit5-plugin-1.2.2.jar"); + } + return junit5PitestPluginJars; + } + private void collectClassPathFileForPitest(String reportDirectory, String targetPackageName, List resourceDirectories) { String classPathFileContent = ReadAction.compute(() -> { List classpath = GradleUtils.getCompilationOutputPaths(getProject()); @@ -205,6 +305,12 @@ private void collectClassPathFileForPitest(String reportDirectory, String target allDependencies.addAll(resourceDirectories); } allDependencies.addAll(testDependencies); + if (getContext().getIsJunit5()) { + allDependencies.addAll(getJunit5PitestPluginJars(testDependencies)); + } + if (getContext().getJavaVersion().contains("1.8.")) { + allDependencies.add(findToolsJarForJDK8()); + } return String.join("\n", allDependencies); }); showOutput("Classpath file content: " + classPathFileContent, "Classpath file content"); @@ -230,7 +336,11 @@ private void setupPitestLibDependencies(List resourceDirectories) { if (files != null) { for (File file : files) { if (file.getName().endsWith(".jar")) { - if (file.getName().startsWith("pitest") || file.getName().startsWith("commons")) { + String fileName = file.getName(); + if (fileName.startsWith("pitest-command") + || fileName.startsWith("pitest-entry") + || fileName.startsWith("pitest-testcraft-pro") + || file.getName().startsWith("commons")) { dependencies.add(file.getAbsolutePath()); } } diff --git a/src/main/java/com/github/jaksonlin/testcraft/util/ClassFileInfo.java b/src/main/java/com/github/jaksonlin/testcraft/util/ClassFileInfo.java index 9adb416..991a7ea 100644 --- a/src/main/java/com/github/jaksonlin/testcraft/util/ClassFileInfo.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/ClassFileInfo.java @@ -6,12 +6,14 @@ public class ClassFileInfo { private final String className; private final String packageName; private final List methods; + private final List imports; - public ClassFileInfo(String fullyQualifiedName, String className, String packageName, List methods) { + public ClassFileInfo(String fullyQualifiedName, String className, String packageName, List methods, List imports) { this.fullyQualifiedName = fullyQualifiedName; this.className = className; this.packageName = packageName; this.methods = methods; + this.imports = imports; } public String getFullyQualifiedName() { @@ -29,4 +31,8 @@ public String getPackageName() { public List getMethods() { return methods; } + + public List getImports() { + return imports; + } } diff --git a/src/main/java/com/github/jaksonlin/testcraft/util/JavaFileProcessor.java b/src/main/java/com/github/jaksonlin/testcraft/util/JavaFileProcessor.java index 734ae4b..39b01a0 100644 --- a/src/main/java/com/github/jaksonlin/testcraft/util/JavaFileProcessor.java +++ b/src/main/java/com/github/jaksonlin/testcraft/util/JavaFileProcessor.java @@ -3,6 +3,7 @@ import com.github.javaparser.JavaParser; import com.github.javaparser.ParseResult; import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.PackageDeclaration; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; @@ -16,39 +17,39 @@ import java.util.stream.Collectors; public class JavaFileProcessor { // Read the Java File and get the main class's fully qualified name - public ClassFileInfo getFullyQualifiedName(String file) { - try { - byte[] bytes = Files.readAllBytes(Paths.get(file)); - String content = new String(bytes, StandardCharsets.UTF_8); - JavaParser parser = new JavaParser(); - ParseResult parseResult = parser.parse(content); + public Optional getFullyQualifiedName(String file) throws IOException { + + byte[] bytes = Files.readAllBytes(Paths.get(file)); + String content = new String(bytes, StandardCharsets.UTF_8); + JavaParser parser = new JavaParser(); + ParseResult parseResult = parser.parse(content); - if (parseResult.isSuccessful()) { - CompilationUnit compilationUnit = parseResult.getResult().orElse(null); - if (compilationUnit != null) { - Optional classDeclarationOptional = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class); - if (classDeclarationOptional.isPresent()) { - ClassOrInterfaceDeclaration classDeclaration = classDeclarationOptional.get(); - String packageName = compilationUnit.getPackageDeclaration() - .map(PackageDeclaration::getNameAsString) - .orElse(""); - String className = classDeclaration.getNameAsString(); - String fullyQualifiedName = packageName.isEmpty() ? className : packageName + "." + className; - List methods = classDeclaration.getMethods().stream() - .map(MethodDeclaration::getNameAsString) - .collect(Collectors.toList()); - return new ClassFileInfo(fullyQualifiedName, className, packageName, methods); - } - } - } else { - // Handle parsing error, maybe log it - System.err.println("Error parsing file: " + file); - } - } catch (IOException e) { - // Handle file reading error, maybe log it - System.err.println("Error reading file: " + file); - e.printStackTrace(); + if (!parseResult.isSuccessful()) { + return Optional.empty(); } - return null; + + CompilationUnit compilationUnit = parseResult.getResult().orElse(null); + if (compilationUnit == null) { + return Optional.empty(); + } + + Optional classDeclarationOptional = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class); + if (classDeclarationOptional.isPresent()) { + ClassOrInterfaceDeclaration classDeclaration = classDeclarationOptional.get(); + String packageName = compilationUnit.getPackageDeclaration() + .map(PackageDeclaration::getNameAsString) + .orElse(""); + String className = classDeclaration.getNameAsString(); + String fullyQualifiedName = packageName.isEmpty() ? className : packageName + "." + className; + List methods = classDeclaration.getMethods().stream() + .map(MethodDeclaration::getNameAsString) + .collect(Collectors.toList()); + List imports = compilationUnit.getImports().stream() + .map(ImportDeclaration::getNameAsString) + .collect(Collectors.toList()); + return Optional.of(new ClassFileInfo(fullyQualifiedName, className, packageName, methods, imports)); + } + + return Optional.empty(); } } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 334dd59..c3f4b6c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -31,6 +31,11 @@ Change Notes +

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.
  • +

1.0.13 - 2025-05-19

  • Added @@ -142,7 +147,7 @@
]]>
- 1.0.13 + 1.0.14 Jason Lam com.github.jaksonlin.testcraftpro @@ -161,15 +166,6 @@ id="TestCraft" icon="/icons/testcraft.svg" anchor="right"/> - - - - + + + + + + + + + + - + @@ -261,5 +257,12 @@ text="$action.CheckInvalidTestCasesAction.text"> + + + + + + + diff --git a/src/main/resources/messages/MyBundle.properties b/src/main/resources/messages/MyBundle.properties index 3fec18e..95cb20c 100644 --- a/src/main/resources/messages/MyBundle.properties +++ b/src/main/resources/messages/MyBundle.properties @@ -166,4 +166,6 @@ testscan.checking_test_cases=Checking test cases... testscan.no_invalid_test_cases_found=No invalid test cases found. testscan.found_invalid_test_cases=Found {0} invalid test cases: testscan.test_case_validation_results=Test Case Validation Results -testscan.test_case_validation_canceled=Test case validation was canceled. \ No newline at end of file +testscan.test_case_validation_canceled=Test case validation was canceled. +testscan.commit_blocked_message=Commit blocked: Invalid test cases found. Please fix the issues before committing. +testscan.validation_timeout_message=Test case validation timed out. Please try again. \ No newline at end of file