diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index 4fff986..edc39f1 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -72,6 +72,8 @@ + + @@ -79,4 +81,4 @@ com.jetbrains.php com.intellij.modules.platform - \ No newline at end of file + diff --git a/src/org/atoum/intellij/plugin/atoum/Utils.java b/src/org/atoum/intellij/plugin/atoum/Utils.java index 1333e8d..8132d3a 100644 --- a/src/org/atoum/intellij/plugin/atoum/Utils.java +++ b/src/org/atoum/intellij/plugin/atoum/Utils.java @@ -1,6 +1,8 @@ package org.atoum.intellij.plugin.atoum; import com.google.common.collect.Lists; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.php.PhpIndex; @@ -98,4 +100,21 @@ public static PhpClass getFirstClassFromFile(PhpFile phpFile) { Collection phpClasses = PsiTreeUtil.collectElementsOfType(phpFile, PhpClass.class); return phpClasses.size() == 0 ? null : phpClasses.iterator().next(); } + + public static void saveFiles(PhpClass currentTestClass, Project project) { + FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); + + Document documentTestClass = fileDocumentManager.getDocument(currentTestClass.getContainingFile().getVirtualFile()); + if (documentTestClass != null) { + fileDocumentManager.saveDocument(documentTestClass); + } + + PhpClass currentTestedClass = Utils.locateTestedClass(project, currentTestClass); + if (currentTestedClass != null) { + Document documentTestedClass = fileDocumentManager.getDocument(currentTestedClass.getContainingFile().getVirtualFile()); + if (documentTestedClass != null) { + fileDocumentManager.saveDocument(documentTestedClass); + } + } + } } diff --git a/src/org/atoum/intellij/plugin/atoum/actions/RerunFailedTestsAction.java b/src/org/atoum/intellij/plugin/atoum/actions/RerunFailedTestsAction.java new file mode 100644 index 0000000..cad34fa --- /dev/null +++ b/src/org/atoum/intellij/plugin/atoum/actions/RerunFailedTestsAction.java @@ -0,0 +1,81 @@ +package org.atoum.intellij.plugin.atoum.actions; + +import com.intellij.execution.Location; +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.jetbrains.php.lang.psi.PhpFile; +import com.jetbrains.php.lang.psi.elements.Method; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import org.atoum.intellij.plugin.atoum.Utils; +import org.atoum.intellij.plugin.atoum.model.RunnerConfiguration; +import org.atoum.intellij.plugin.atoum.run.Runner; + +public class RerunFailedTestsAction extends AnAction +{ + private SMTestProxy.SMRootTestProxy tests; + + // Action class must have a no argument constructor + public RerunFailedTestsAction() { + this.getTemplatePresentation().setText("Rerun Failed Tests"); + this.getTemplatePresentation().setIcon(AllIcons.RunConfigurations.RerunFailedTests); + } + + public RerunFailedTestsAction(SMTestProxy.SMRootTestProxy tests) + { + this(); + this.tests = tests; + } + + @Override + public void update(AnActionEvent e) { + Presentation presentation = e.getPresentation(); + presentation.setEnabled(false); + presentation.setVisible(true); + + if (!this.tests.isInProgress() && !this.tests.isPassed()) { + presentation.setEnabled(true); + } + } + + @Override + public void actionPerformed(AnActionEvent anActionEvent) + { + RunnerConfiguration runConfiguration = new RunnerConfiguration(); + Project project = getEventProject(anActionEvent); + + for (SMTestProxy methodProxy: tests.getAllTests()) { + if (!methodProxy.isPassed()) { + Location loc = methodProxy.getLocation(project, GlobalSearchScope.EMPTY_SCOPE); + + if (loc == null) { + continue; + } + + PsiElement elem = loc.getPsiElement(); + + if (elem instanceof PhpClass) { + Utils.saveFiles((PhpClass) elem, project); + runConfiguration.setFile((PhpFile)elem.getContainingFile()); + continue; + } + + while (elem != null && !(elem instanceof Method)) { + elem = elem.getParent(); + } + + if (elem != null) { + runConfiguration.addMethod((Method) elem); + } + } + } + + Runner runner = new Runner(project); + runner.run(runConfiguration); + } +} diff --git a/src/org/atoum/intellij/plugin/atoum/actions/Run.java b/src/org/atoum/intellij/plugin/atoum/actions/Run.java index 2104e29..a5aa4db 100644 --- a/src/org/atoum/intellij/plugin/atoum/actions/Run.java +++ b/src/org/atoum/intellij/plugin/atoum/actions/Run.java @@ -4,8 +4,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.project.Project; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.jetbrains.php.lang.psi.PhpFile; @@ -42,23 +40,6 @@ public void update(AnActionEvent event) { } } - protected void saveFiles(PhpClass currentTestClass, Project project) { - FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance(); - - Document documentTestClass = fileDocumentManager.getDocument(currentTestClass.getContainingFile().getVirtualFile()); - if (documentTestClass != null) { - fileDocumentManager.saveDocument(documentTestClass); - } - - PhpClass currentTestedClass = Utils.locateTestedClass(project, currentTestClass); - if (currentTestedClass != null) { - Document documentTestedClass = fileDocumentManager.getDocument(currentTestedClass.getContainingFile().getVirtualFile()); - if (documentTestedClass != null) { - fileDocumentManager.saveDocument(documentTestedClass); - } - } - } - public void actionPerformed(final AnActionEvent e) { PhpClass currentTestClass = getCurrentTestClass(e); VirtualFile selectedDir = null; @@ -73,12 +54,12 @@ public void actionPerformed(final AnActionEvent e) { RunnerConfiguration runConfiguration = new RunnerConfiguration(); if (null != currentTestClass) { - saveFiles(currentTestClass, project); + Utils.saveFiles(currentTestClass, project); runConfiguration.setFile((PhpFile)currentTestClass.getContainingFile()); Method currentTestMethod = getCurrentTestMethod(e); if (currentTestMethod != null) { - runConfiguration.setMethod(currentTestMethod); + runConfiguration.addMethod(currentTestMethod); } } diff --git a/src/org/atoum/intellij/plugin/atoum/model/MethodResult.java b/src/org/atoum/intellij/plugin/atoum/model/MethodResult.java index 09c49b9..14209af 100644 --- a/src/org/atoum/intellij/plugin/atoum/model/MethodResult.java +++ b/src/org/atoum/intellij/plugin/atoum/model/MethodResult.java @@ -10,10 +10,12 @@ public class MethodResult { protected String name; protected String content; + protected Integer failLineNumber; - public MethodResult(String name, String content) { + public MethodResult(String name, String content, Integer failLineNumber) { this.name = name; this.content = content; + this.failLineNumber = failLineNumber; } public void definedStatePassed() { @@ -42,4 +44,9 @@ public String getState() { return this.state; } + + public Integer getFailLineNumber() + { + return this.failLineNumber; + } } diff --git a/src/org/atoum/intellij/plugin/atoum/model/RunnerConfiguration.java b/src/org/atoum/intellij/plugin/atoum/model/RunnerConfiguration.java index adbc367..ef130bb 100644 --- a/src/org/atoum/intellij/plugin/atoum/model/RunnerConfiguration.java +++ b/src/org/atoum/intellij/plugin/atoum/model/RunnerConfiguration.java @@ -4,13 +4,16 @@ import com.jetbrains.php.lang.psi.PhpFile; import com.jetbrains.php.lang.psi.elements.Method; +import java.util.ArrayList; +import java.util.List; + public class RunnerConfiguration { protected PhpFile file; protected VirtualFile directory; - protected Method method; + protected List methods = new ArrayList<>(); public PhpFile getFile () { return file; @@ -28,11 +31,11 @@ public void setDirectory(VirtualFile directory) { this.directory = directory; } - public void setMethod(Method method) { - this.method = method; + public void addMethod(Method method) { + this.methods.add(method); } - public Method getMethod () { - return method; + public List getMethods () { + return methods; } } diff --git a/src/org/atoum/intellij/plugin/atoum/model/TestsResultFactory.java b/src/org/atoum/intellij/plugin/atoum/model/TestsResultFactory.java index 438cae4..17ead1e 100644 --- a/src/org/atoum/intellij/plugin/atoum/model/TestsResultFactory.java +++ b/src/org/atoum/intellij/plugin/atoum/model/TestsResultFactory.java @@ -13,6 +13,7 @@ public static TestsResult createFromTapOutput(String tapOutput) Pattern statusLinePattern = Pattern.compile("((?:not )?ok) (\\d+)(?: (?:# SKIP|# TODO|-) (.+)::(.+)\\(\\))?$"); Pattern nameLinePattern = Pattern.compile("^# ([\\w\\\\]+)::(.+)\\(\\)$"); Pattern planLinePattern = Pattern.compile("^\\d+\\.\\.\\d+$"); + Pattern failLocationPattern = Pattern.compile("^# .+:([0-9]+)$"); String[] tapOutputLines = tapOutput.split("\n"); @@ -21,6 +22,7 @@ public static TestsResult createFromTapOutput(String tapOutput) String currentContent = ""; String currentClassname = ""; String currentStatus = ""; + Integer currentLineNumber = null; for (Integer i = 0; i < tapOutputLines.length; i++) { String currentLine = tapOutputLines[i]; @@ -33,13 +35,14 @@ public static TestsResult createFromTapOutput(String tapOutput) if (statusLineMatcher.matches()) { if (infosFound) { - flushLine(testsResult, currentClassname, currentMethodName, currentContent, currentStatus); + flushLine(testsResult, currentClassname, currentMethodName, currentContent, currentStatus, currentLineNumber); } currentMethodName = statusLineMatcher.group(4); currentContent = ""; currentClassname = statusLineMatcher.group(3); currentStatus = statusLineMatcher.group(1); + currentLineNumber = null; infosFound = true; } else { @@ -47,21 +50,26 @@ public static TestsResult createFromTapOutput(String tapOutput) if (nameLineMatcher.matches()) { currentClassname = nameLineMatcher.group(1); currentMethodName = nameLineMatcher.group(2); - } else if (currentLine.length() > 0) { - currentContent += currentLine.substring(1) + "\n"; + } else { + Matcher failLocationMatcher = failLocationPattern.matcher(currentLine); + if (failLocationMatcher.matches()) { + currentLineNumber = Integer.parseInt(failLocationMatcher.group(1)); + } else if (currentLine.length() > 0) { + currentContent += currentLine.substring(1) + "\n"; + } } } } if (infosFound) { - flushLine(testsResult, currentClassname, currentMethodName, currentContent, currentStatus); + flushLine(testsResult, currentClassname, currentMethodName, currentContent, currentStatus, currentLineNumber); } return testsResult; } - protected static void flushLine(TestsResult testsResult, String currentClassname, String currentMethodName, String currentContent, String currentStatus) { - MethodResult methodResult = new MethodResult(currentMethodName, currentContent); + protected static void flushLine(TestsResult testsResult, String currentClassname, String currentMethodName, String currentContent, String currentStatus, Integer lineNumber) { + MethodResult methodResult = new MethodResult(currentMethodName, currentContent, lineNumber); if (!testsResult.hasClassResult(currentClassname)) { ClassResult classResult = new ClassResult(); classResult.setName(currentClassname); diff --git a/src/org/atoum/intellij/plugin/atoum/run/CommandLineArgumentsBuilder.java b/src/org/atoum/intellij/plugin/atoum/run/CommandLineArgumentsBuilder.java index 4979cf8..57f4f92 100644 --- a/src/org/atoum/intellij/plugin/atoum/run/CommandLineArgumentsBuilder.java +++ b/src/org/atoum/intellij/plugin/atoum/run/CommandLineArgumentsBuilder.java @@ -1,6 +1,7 @@ package org.atoum.intellij.plugin.atoum.run; import com.jetbrains.php.config.interpreters.PhpConfigurationOptionData; +import com.jetbrains.php.lang.psi.elements.Method; import org.atoum.intellij.plugin.atoum.model.RunnerConfiguration; import java.io.File; @@ -53,9 +54,9 @@ public CommandLineArgumentsBuilder useConfiguration(RunnerConfiguration runnerCo this.commandLineArgs.add("-f"); this.commandLineArgs.add(this.relativizePath(runnerConfiguration.getFile().getVirtualFile().getPath())); } - if (null != runnerConfiguration.getMethod()) { + for (Method method : runnerConfiguration.getMethods()) { this.commandLineArgs.add("-m"); - this.commandLineArgs.add("*::" + runnerConfiguration.getMethod().getName()); + this.commandLineArgs.add("*::" + method.getName()); } return this; diff --git a/src/org/atoum/intellij/plugin/atoum/run/Runner.java b/src/org/atoum/intellij/plugin/atoum/run/Runner.java index d9c520a..d74b5b1 100644 --- a/src/org/atoum/intellij/plugin/atoum/run/Runner.java +++ b/src/org/atoum/intellij/plugin/atoum/run/Runner.java @@ -18,14 +18,17 @@ import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionToolbar; +import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.psi.PsiDirectory; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentManager; import com.jetbrains.php.config.PhpProjectConfigurationFacade; @@ -34,6 +37,7 @@ import com.jetbrains.php.run.PhpRunConfiguration; import com.jetbrains.php.run.PhpRunConfigurationFactoryBase; import org.atoum.intellij.plugin.atoum.AtoumUtils; +import org.atoum.intellij.plugin.atoum.actions.RerunFailedTestsAction; import org.atoum.intellij.plugin.atoum.model.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -137,14 +141,23 @@ public RunConfiguration createTemplateConfiguration(Project project) { commandLineBuilder.useConfigFile(phpstormConfigFile); } + ActionManager am = ActionManager.getInstance(); + DefaultActionGroup buttonGroup = new DefaultActionGroup(); + ActionToolbar viewToolbar = am.createActionToolbar("atoum.ConsoleToolbar", buttonGroup, false); + + SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(false, true); + toolWindowPanel.setContent(testsOutputConsoleView.getComponent()); + toolWindowPanel.setToolbar(viewToolbar.getComponent()); + ContentManager contentManager = toolWindow.getContentManager(); - Content myContent; - myContent = toolWindow.getContentManager().getFactory().createContent(testsOutputConsoleView.getComponent(), "tests results", false); - toolWindow.getContentManager().removeAllContents(true); - toolWindow.getContentManager().addContent(myContent); + Content myContent = contentManager.getFactory().createContent(toolWindowPanel.getComponent(), "tests results", false); + contentManager.removeAllContents(true); + contentManager.addContent(myContent); final SMTRunnerConsoleView console = (SMTRunnerConsoleView)testsOutputConsoleView; + buttonGroup.add(new RerunFailedTestsAction(console.getResultsViewer().getTestsRootNode())); + String[] commandLineArgs = commandLineBuilder.build(); HashMap environnmentVariables = new HashMap(); diff --git a/src/org/atoum/intellij/plugin/atoum/run/SMTRootTestProxyFactory.java b/src/org/atoum/intellij/plugin/atoum/run/SMTRootTestProxyFactory.java index 2566b7e..77c284b 100644 --- a/src/org/atoum/intellij/plugin/atoum/run/SMTRootTestProxyFactory.java +++ b/src/org/atoum/intellij/plugin/atoum/run/SMTRootTestProxyFactory.java @@ -1,23 +1,99 @@ package org.atoum.intellij.plugin.atoum.run; +import com.intellij.execution.Location; +import com.intellij.execution.PsiLocation; +import com.intellij.execution.testframework.sm.runner.SMTestLocator; import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import com.intellij.openapi.editor.LazyRangeMarkerFactory; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.search.GlobalSearchScope; +import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.lang.psi.elements.Method; +import com.jetbrains.php.lang.psi.elements.PhpClass; import org.atoum.intellij.plugin.atoum.model.ClassResult; import org.atoum.intellij.plugin.atoum.model.MethodResult; import org.atoum.intellij.plugin.atoum.model.TestsResult; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; public class SMTRootTestProxyFactory { public static SMTestProxy.SMRootTestProxy createFromClassResult(ClassResult classResult) { + SMTestLocator methodLocator = new SMTestLocator() { + @NotNull + @Override + public List getLocation(@NotNull String protocol, @NotNull String path, @NotNull Project project, @NotNull GlobalSearchScope globalSearchScope) { + String[] parts = path.split(":"); + + Collection classes = PhpIndex.getInstance(project).getClassesByFQN(parts[0]); + if (classes.isEmpty()) { + return new ArrayList<>(); + } + + Method method = classes.iterator().next().findMethodByName(parts[1]); + + if (method == null) { + return new ArrayList<>(); + } + + ArrayList locations = new ArrayList<>(1); + PsiElement elem = null; + + if (parts.length == 3) { + PsiFile file = method.getContainingFile(); + int line = Integer.parseInt(parts[2]); + int offset = LazyRangeMarkerFactory.getInstance(project).createRangeMarker(file.getVirtualFile(), line, 0, false).getStartOffset(); + elem = file.findElementAt(offset); + } + + if (elem == null) { + elem = method; + } + + locations.add(new PsiLocation<>(elem)); + + return locations; + } + }; + SMTestProxy.SMRootTestProxy classNode = new SMTestProxy.SMRootTestProxy(); classNode.setPresentation(classResult.getName()); classNode.setFinished(); + classNode.setRootLocationUrl("atoum://" + classResult.getName()); + + classNode.setLocator(new SMTestLocator() { + @NotNull + @Override + public List getLocation(@NotNull String protocol, @NotNull String path, @NotNull Project project, @NotNull GlobalSearchScope globalSearchScope) { + Collection classes = PhpIndex.getInstance(project).getClassesByFQN(path); + if (classes.isEmpty()) { + return new ArrayList<>(); + } + + ArrayList locations = new ArrayList<>(1); + locations.add(new PsiLocation<>(classes.iterator().next())); + + return locations; + } + }); if (classResult.getState().equals(ClassResult.STATE_FAILED)) { classNode.setTestFailed("", "", true); } for (MethodResult methodsResult: classResult.getMethods()) { - SMTestProxy methodNode = new SMTestProxy(methodsResult.getName(), false, ""); + String url = "atoum://" + classResult.getName() + ":" + methodsResult.getName(); + if (methodsResult.getFailLineNumber() != null) { + url += ":" + methodsResult.getFailLineNumber(); + } + + SMTestProxy methodNode = new SMTestProxy(methodsResult.getName(), false, url); + methodNode.setLocator(methodLocator); if (methodsResult.getState().equals(MethodResult.STATE_FAILED)) { methodNode.setTestFailed(methodsResult.getName() + " Failed", methodsResult.getContent(), true);