diff --git a/bundles/org.pitest.pitclipse.ui/icons/killed.gif b/bundles/org.pitest.pitclipse.ui/icons/killed.gif
new file mode 100644
index 00000000..7b081f57
Binary files /dev/null and b/bundles/org.pitest.pitclipse.ui/icons/killed.gif differ
diff --git a/bundles/org.pitest.pitclipse.ui/icons/noCoverage.gif b/bundles/org.pitest.pitclipse.ui/icons/noCoverage.gif
new file mode 100644
index 00000000..816e12fc
Binary files /dev/null and b/bundles/org.pitest.pitclipse.ui/icons/noCoverage.gif differ
diff --git a/bundles/org.pitest.pitclipse.ui/icons/nonViable.gif b/bundles/org.pitest.pitclipse.ui/icons/nonViable.gif
new file mode 100644
index 00000000..d05b83e0
Binary files /dev/null and b/bundles/org.pitest.pitclipse.ui/icons/nonViable.gif differ
diff --git a/bundles/org.pitest.pitclipse.ui/icons/survived.gif b/bundles/org.pitest.pitclipse.ui/icons/survived.gif
new file mode 100644
index 00000000..b14446ad
Binary files /dev/null and b/bundles/org.pitest.pitclipse.ui/icons/survived.gif differ
diff --git a/bundles/org.pitest.pitclipse.ui/icons/timeout.gif b/bundles/org.pitest.pitclipse.ui/icons/timeout.gif
new file mode 100644
index 00000000..98a30154
Binary files /dev/null and b/bundles/org.pitest.pitclipse.ui/icons/timeout.gif differ
diff --git a/bundles/org.pitest.pitclipse.ui/plugin.xml b/bundles/org.pitest.pitclipse.ui/plugin.xml
index d52728fe..413dc8e6 100644
--- a/bundles/org.pitest.pitclipse.ui/plugin.xml
+++ b/bundles/org.pitest.pitclipse.ui/plugin.xml
@@ -4,8 +4,8 @@
-
+
@@ -36,5 +36,250 @@
point="org.pitest.pitclipse.core.mutations.results">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/core/PitUiActivator.java b/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/core/PitUiActivator.java
index ef7e44d6..901f4be1 100644
--- a/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/core/PitUiActivator.java
+++ b/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/core/PitUiActivator.java
@@ -28,6 +28,7 @@
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
+import org.pitest.pitclipse.ui.mutation.marker.PitclipseMutantMarkerFactory;
/**
* The ui activator class which initializes the icons of the plug in
@@ -41,7 +42,10 @@ public class PitUiActivator extends AbstractUIPlugin {
* Key under which the pit icon is put in the registry
*/
private static final String PIT_ICON = "org.pitest.pitclipse.pitIcon";
-
+ /**
+ * Qualifier of this plug in
+ */
+ public static final String PLUGIN_ID = "org.pitest.pitclipse.ui";
/**
* The shared instance
*/
@@ -61,8 +65,29 @@ private void initIcons(BundleContext context) {
Display.getDefault().syncExec(() -> {
imageRegistry = new ImageRegistry();
Bundle bundle = FrameworkUtil.getBundle(getClass());
- URL url = FileLocator.find(bundle, new Path("icons/pit.gif"), null);
+ final URL url = FileLocator.find(bundle, new Path("icons/pit.gif"), null);
imageRegistry.put(PIT_ICON, ImageDescriptor.createFromURL(url).createImage());
+ // annotation icons
+ final URL killedUrl = FileLocator.find(bundle, new Path("icons/killed.gif"), null);
+ imageRegistry.put(PitclipseMutantMarkerFactory.KILLED_MUTANT_MARKER,
+ ImageDescriptor.createFromURL(killedUrl).createImage());
+ final URL survivingUrl = FileLocator.find(bundle, new Path("icons/survived.gif"), null);
+ imageRegistry.put(PitclipseMutantMarkerFactory.SURVIVING_MUTANT_MARKER,
+ ImageDescriptor.createFromURL(survivingUrl).createImage());
+ final URL noCoverageUrl = FileLocator.find(bundle, new Path("icons/noCoverage.gif"), null);
+ imageRegistry.put(PitclipseMutantMarkerFactory.NO_COVERAGE_MUTANT_MARKER,
+ ImageDescriptor.createFromURL(noCoverageUrl).createImage());
+ final URL timeoutUrl = FileLocator.find(bundle, new Path("icons/timeout.gif"), null);
+ imageRegistry.put(PitclipseMutantMarkerFactory.TIMEOUT_MUTANT_MARKER,
+ ImageDescriptor.createFromURL(timeoutUrl).createImage());
+ // use same icon for these 3, because they are not from huge interest
+ final URL nonViableUrl = FileLocator.find(bundle, new Path("icons/nonViable.gif"), null);
+ imageRegistry.put(PitclipseMutantMarkerFactory.NON_VIABLE_MUTANT_MARKER,
+ ImageDescriptor.createFromURL(nonViableUrl).createImage());
+ imageRegistry.put(PitclipseMutantMarkerFactory.MEMORY_ERROR_MARKER,
+ ImageDescriptor.createFromURL(nonViableUrl).createImage());
+ imageRegistry.put(PitclipseMutantMarkerFactory.RUN_ERROR_MARKER,
+ ImageDescriptor.createFromURL(nonViableUrl).createImage());
});
}
@@ -73,10 +98,22 @@ public Image getPitIcon() {
return imageRegistry.get(PIT_ICON);
}
+ /**
+ * @param imageDescritporId
+ * @return the requested image descriptor or null, if not found
+ */
+ public ImageDescriptor getImageDescriptor(String imageDescritporId) {
+ return imageRegistry.getDescriptor(imageDescritporId);
+ }
+
/**
* @return the shared instance
*/
public static PitUiActivator getDefault() {
return plugin;
}
+
+ public Image getImage(String imageDescriptorId) {
+ return imageRegistry.get(imageDescriptorId);
+ }
}
diff --git a/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/mutation/marker/AnnotationImageProvider.java b/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/mutation/marker/AnnotationImageProvider.java
new file mode 100644
index 00000000..a9061a69
--- /dev/null
+++ b/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/mutation/marker/AnnotationImageProvider.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright 2021 Jonas Kutscha and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ ******************************************************************************/
+
+package org.pitest.pitclipse.ui.mutation.marker;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.texteditor.IAnnotationImageProvider;
+import org.pitest.pitclipse.ui.core.PitUiActivator;
+
+/**
+ * Simple image provider for the mutation marker
+ * @author Jonas Kutscha
+ */
+public class AnnotationImageProvider implements IAnnotationImageProvider {
+ @Override
+ public String getImageDescriptorId(Annotation annotation) {
+ return annotation.getType();
+ }
+
+ @Override
+ public ImageDescriptor getImageDescriptor(String imageDescritporId) {
+ return PitUiActivator.getDefault().getImageDescriptor(imageDescritporId);
+ }
+
+ @Override
+ public Image getManagedImage(Annotation annotation) {
+ return PitUiActivator.getDefault().getImage(getImageDescriptorId(annotation));
+ }
+}
diff --git a/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/mutation/marker/PitclipseMutantMarkerFactory.java b/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/mutation/marker/PitclipseMutantMarkerFactory.java
new file mode 100644
index 00000000..4ac00ada
--- /dev/null
+++ b/bundles/org.pitest.pitclipse.ui/src/org/pitest/pitclipse/ui/mutation/marker/PitclipseMutantMarkerFactory.java
@@ -0,0 +1,274 @@
+/*******************************************************************************
+ * Copyright 2021 Jonas Kutscha and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ ******************************************************************************/
+
+package org.pitest.pitclipse.ui.mutation.marker;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
+import org.pitest.pitclipse.core.extension.point.ResultNotifier;
+import org.pitest.pitclipse.runner.model.ClassMutations;
+import org.pitest.pitclipse.runner.model.Mutation;
+import org.pitest.pitclipse.runner.model.MutationsModel;
+import org.pitest.pitclipse.runner.model.PackageMutations;
+import org.pitest.pitclipse.runner.model.ProjectMutations;
+import org.pitest.pitclipse.runner.model.Status;
+import org.pitest.pitclipse.ui.core.PitUiActivator;
+
+/**
+ * Class which creates mutation markers after a PIT run
+ * @author Jonas Kutscha
+ */
+public class PitclipseMutantMarkerFactory implements ResultNotifier {
+ /**
+ * Id where all Pitclipse markers can be found
+ */
+ public static final String PITCLIPSE_MUTANT_MARKER = PitUiActivator.PLUGIN_ID + ".pitclipsemarker";
+ /**
+ * Id for markers of surviving mutants
+ */
+ public static final String SURVIVING_MUTANT_MARKER = PitUiActivator.PLUGIN_ID + ".survived";
+ /**
+ * Id of the marker attribute fix hint
+ */
+ public static final String SURVIVING_MUTANT_MARKER_ATTRIBUTE = PitUiActivator.PLUGIN_ID + ".fixHint";
+ /**
+ * Id for markers of killed mutants
+ */
+ public static final String KILLED_MUTANT_MARKER = PitUiActivator.PLUGIN_ID + ".killed";
+ /**
+ * Id for markers of no coverage mutants
+ */
+ public static final String NO_COVERAGE_MUTANT_MARKER = PitUiActivator.PLUGIN_ID + ".nocoverage";
+ /**
+ * Id for markers of timeout mutants
+ */
+ public static final String TIMEOUT_MUTANT_MARKER = PitUiActivator.PLUGIN_ID + ".timeout";
+ /**
+ * Id for markers of non viable mutants
+ */
+ public static final String NON_VIABLE_MUTANT_MARKER = PitUiActivator.PLUGIN_ID + ".nonViable";
+ /**
+ * Id for markers of memory error mutants
+ */
+ public static final String MEMORY_ERROR_MARKER = PitUiActivator.PLUGIN_ID + ".memError";
+ /**
+ * Id for markers of run error mutants
+ */
+ public static final String RUN_ERROR_MARKER = PitUiActivator.PLUGIN_ID + ".runError";
+
+ /**
+ * Uses the results to create a marker for each mutant
+ * @param results from pit which holds the information about the mutants
+ */
+ @Override
+ public void handleResults(MutationsModel results) {
+ createMarkers(results);
+ }
+
+ /**
+ * Extracts all mutants of the results to create a marker for each one
+ * @param results which hold the mutants
+ */
+ private void createMarkers(MutationsModel results) {
+ removeOldMarkers();
+ List mutations = ModelsVisitor.VISITOR.extractAllMutations(results);
+ int i = 0;
+ final IResource[] resources = new IResource[mutations.size()];
+ final String[] types = new String[mutations.size()];
+ @SuppressWarnings("unchecked")
+ final Map[] attributes = new Map[mutations.size()];
+
+ for (Mutation m : mutations) {
+ resources[i] = findClass(getProjectName(m), getClassName(m));
+ types[i] = getType(m);
+ attributes[i] = new HashMap<>();
+ attributes[i].put(IMarker.LINE_NUMBER, m.getLineNumber());
+ attributes[i].put(IMarker.MESSAGE, m.getStatus().toString() + ": " + m.getDescription());
+
+ switch (types[i]) {
+ case SURVIVING_MUTANT_MARKER:
+ // TODO: if mutation survived, add hint how to probably kill it
+ attributes[i].put(SURVIVING_MUTANT_MARKER_ATTRIBUTE, "NOT IMPLEMENTED YET");
+ attributes[i].put(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
+ break;
+ case TIMEOUT_MUTANT_MARKER:
+ case NO_COVERAGE_MUTANT_MARKER:
+ attributes[i].put(IMarker.PRIORITY, IMarker.PRIORITY_NORMAL);
+ break;
+ default:
+ // default. Used for killed mutants
+ attributes[i].put(IMarker.DONE, true);
+ attributes[i].put(IMarker.PRIORITY, IMarker.PRIORITY_LOW);
+ }
+ createMarker(resources[i], types[i], attributes[i]);
+ i++;
+ }
+ }
+
+ /**
+ * Removes all old markers, which are Pitclipse markers
+ */
+ private void removeOldMarkers() {
+ try {
+ IMarker[] marker = ResourcesPlugin.getWorkspace().getRoot().findMarkers(PITCLIPSE_MUTANT_MARKER, true,
+ IResource.DEPTH_INFINITE);
+ for (IMarker iMarker : marker) {
+ iMarker.delete();
+ }
+ } catch (CoreException e) {
+ throw new RuntimeException("Error while deleting old markers occured.", e);
+ }
+ }
+
+ /**
+ * Tries to create a marker for the given resource with the given type and
+ * attributes.
+ * @param iResource resource where to create the marker
+ * @param type of the marker to create
+ * @param attributes which should be added to the marker
+ * @return the created marker or null, if the marker could not be created
+ */
+ private IMarker createMarker(IResource iResource, String type, Map attributes) {
+ try {
+ IMarker marker = iResource.createMarker(type);
+ marker.setAttributes(attributes);
+ return marker;
+ } catch (CoreException e) {
+ // could not create marker
+ return null;
+ }
+ }
+
+ /**
+ * Maps the detection status of the given mutation to the corresponding marker
+ * id. If the detection status has no corresponding marker id the non viable
+ * marker is used.
+ * @param mutation which detection status is mapped
+ * @return the marker id which corresponds to the detection status of the mutant
+ */
+ private String getType(Mutation mutation) {
+ switch (mutation.getStatus()) {
+ case SURVIVED:
+ return SURVIVING_MUTANT_MARKER;
+ case KILLED:
+ return KILLED_MUTANT_MARKER;
+ case NO_COVERAGE:
+ return NO_COVERAGE_MUTANT_MARKER;
+ case TIMED_OUT:
+ return TIMEOUT_MUTANT_MARKER;
+ case MEMORY_ERROR:
+ return MEMORY_ERROR_MARKER;
+ case RUN_ERROR:
+ return RUN_ERROR_MARKER;
+ default:
+ return NON_VIABLE_MUTANT_MARKER;
+ }
+ }
+
+ /**
+ * Tries to find the class specified by its name in the project which name is
+ * given.
+ * @param projectName where the class file is located
+ * @param className which identifies the class
+ * @return the file handle or null, if the file was not found
+ */
+ private IFile findClass(final String projectName, final String className) {
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ for (IProject project : root.getProjects()) {
+ if (project.getName().equals(projectName) && project.isOpen()) {
+ IJavaProject javaProject = JavaCore.create(project);
+ if (javaProject != null) {
+ try {
+ IType type = javaProject.findType(className);
+ return root.getFile(type.getPath());
+ } catch (JavaModelException e) {
+ // Maybe type no longer exists. Do nothing
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Extracts the project name from the given mutation where it occured.
+ * @param mutation from which the project is desired
+ * @return the project name where the mutation occured
+ */
+ private String getProjectName(Mutation mutation) {
+ return mutation.getClassMutations().getPackageMutations().getProjectMutations().getProjectName();
+ }
+
+ /**
+ * Extracts the class name from the given mutation where it occured.
+ * @param mutation from which the class is desired
+ * @return the class name where the mutation occured
+ */
+ private String getClassName(Mutation mutation) {
+ return mutation.getClassMutations().getClassName();
+ }
+
+ /**
+ * Visitor which is used to extract all mutations from the mutation model
+ */
+ private enum ModelsVisitor {
+ VISITOR;
+ public List extractAllMutations(MutationsModel mutationsModel) {
+ final LinkedList mutations = new LinkedList<>();
+ for (Status s : mutationsModel.getStatuses()) {
+ VISITOR.visitStatus(s, mutations);
+ }
+ return mutations;
+ }
+
+ public void visitStatus(Status status, List mutations) {
+ for (ProjectMutations p : status.getProjectMutations()) {
+ VISITOR.visitProject(p, mutations);
+ }
+ }
+
+ public void visitProject(ProjectMutations projectMutations, List mutations) {
+ for (PackageMutations p : projectMutations.getPackageMutations()) {
+ VISITOR.visitPackage(p, mutations);
+ }
+ }
+
+ public void visitPackage(PackageMutations packageMutations, List mutations) {
+ for (ClassMutations c : packageMutations.getClassMutations()) {
+ VISITOR.visitClass(c, mutations);
+ }
+ }
+
+ public void visitClass(ClassMutations classMutations, List mutations) {
+ mutations.addAll(classMutations.getMutations());
+ }
+
+ }
+}
diff --git a/tests/org.pitest.pitclipse.ui.tests/src/org/pitest/pitclipse/ui/tests/PitclipsePitMutationsViewTest.java b/tests/org.pitest.pitclipse.ui.tests/src/org/pitest/pitclipse/ui/tests/PitclipsePitMutationsViewTest.java
index 78f73d25..95c6cf89 100644
--- a/tests/org.pitest.pitclipse.ui.tests/src/org/pitest/pitclipse/ui/tests/PitclipsePitMutationsViewTest.java
+++ b/tests/org.pitest.pitclipse.ui.tests/src/org/pitest/pitclipse/ui/tests/PitclipsePitMutationsViewTest.java
@@ -1,12 +1,19 @@
package org.pitest.pitclipse.ui.tests;
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
+import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.pitest.pitclipse.ui.behaviours.steps.PitMutation;
import org.pitest.pitclipse.ui.behaviours.steps.PitclipseSteps;
+import org.pitest.pitclipse.ui.mutation.marker.PitclipseMutantMarkerFactory;
/**
* @author Lorenzo Bettini
@@ -45,4 +52,49 @@ public void selectMutationOpensTheClassAtTheRightLineNumber() throws CoreExcepti
pitclipseSteps.doubleClickMutationInMutationsView(mutation);
pitclipseSteps.mutationIsOpened(BAR_CLASS + ".java", 7);
}
+
+ @Test
+ public void checkForExistingMutationMarkers() throws CoreException {
+ runPackageTest(FOO_BAR_PACKAGE, TEST_PROJECT);
+ coverageReportGenerated(2, 80, 0, 6, 0);
+ bot.waitUntil(new DefaultCondition() {
+ @Override
+ public boolean test() throws Exception {
+ return getPitclipseMarker().length > 0;
+ }
+ @Override
+ public String getFailureMessage() {
+ return "No pitclipse marker were found!";
+ }
+ });
+ IMarker[] marker = getPitclipseMarker();
+ assertEquals(6, marker.length);
+ int numberOfKilled = 0, numberOfSurvived = 0, numberOfNoCoverage = 0, numberOfOthers = 0;
+ for (IMarker iMarker : marker) {
+ switch (iMarker.getType()) {
+ case PitclipseMutantMarkerFactory.KILLED_MUTANT_MARKER:
+ numberOfKilled++;
+ break;
+ case PitclipseMutantMarkerFactory.SURVIVING_MUTANT_MARKER:
+ numberOfSurvived++;
+ break;
+ case PitclipseMutantMarkerFactory.NO_COVERAGE_MUTANT_MARKER:
+ numberOfNoCoverage++;
+ break;
+ default:
+ numberOfOthers++;
+ break;
+ }
+ }
+ assertEquals(0, numberOfKilled);
+ assertEquals(2, numberOfSurvived);
+ assertEquals(4, numberOfNoCoverage);
+ assertEquals(0, numberOfOthers);
+ }
+
+ private IMarker[] getPitclipseMarker() throws CoreException {
+ return ResourcesPlugin.getWorkspace().getRoot().findMarkers(
+ PitclipseMutantMarkerFactory.PITCLIPSE_MUTANT_MARKER, true,
+ IResource.DEPTH_INFINITE);
+ }
}