From 453c95b2809cf35ea2d4a7eda6cd3eed699838b5 Mon Sep 17 00:00:00 2001 From: "Vladislav.Soroka" Date: Tue, 18 Mar 2014 12:35:50 +0400 Subject: [PATCH] IDEA-116904 Gradle: Expand ide junit execution/report facilities to tests run via gradle --- ...ExternalSystemExecutionConsoleManager.java | 53 +++++ .../util/ExternalSystemApiUtil.java | 39 +++- .../external-system-impl.iml | 2 + ...ExternalSystemExecutionConsoleManager.java | 70 +++++++ .../ExternalSystemRunConfiguration.java | 59 ++++-- .../ExternalSystemExecuteTaskTask.java | 5 + .../ExternalSystemExtensionPoints.xml | 2 + plugins/gradle/gradle.iml | 2 + .../resources/i18n/GradleBundle.properties | 7 +- plugins/gradle/src/META-INF/plugin.xml | 8 + ...lInPackageGradleConfigurationProducer.java | 112 ++++++++++ .../test/runner/GradleConsoleProperties.java | 44 ++++ .../test/runner/GradleSMTestProxy.java | 84 ++++++++ .../GradleTestsExecutionConsoleManager.java | 196 ++++++++++++++++++ .../test/runner/GradleUrlProvider.java | 93 +++++++++ .../OpenGradleTestResultActionProvider.java | 80 +++++++ .../TestClassGradleConfigurationProducer.java | 188 +++++++++++++++++ ...TestMethodGradleConfigurationProducer.java | 173 ++++++++++++++++ .../test/runner/TestRunnerUtils.java | 56 +++++ .../test/runner/events/AbstractTestEvent.java | 82 ++++++++ .../test/runner/events/AfterSuiteEvent.java | 63 ++++++ .../test/runner/events/AfterTestEvent.java | 126 +++++++++++ .../test/runner/events/BeforeSuiteEvent.java | 60 ++++++ .../test/runner/events/BeforeTestEvent.java | 65 ++++++ .../events/ConfigurationErrorEvent.java | 78 +++++++ .../test/runner/events/OnOutputEvent.java | 44 ++++ .../runner/events/ReportLocationEvent.java | 39 ++++ .../test/runner/events/TestEvent.java | 26 +++ .../test/runner/events/TestEventResult.java | 45 ++++ .../test/runner/events/TestEventType.java | 49 +++++ .../GradleExternalTaskConfigurationType.java | 7 +- .../project/GradleExecutionHelper.java | 52 ++++- .../service/task/GradleTaskManager.java | 7 +- .../plugins/gradle/ui/GradleIcons.java | 30 --- .../plugins/gradle/util/XmlXpathHelper.java | 106 ++++++++++ .../tooling/internal/testFilterInit.gradle | 163 +++++++++++++++ 36 files changed, 2258 insertions(+), 57 deletions(-) create mode 100644 platform/external-system-api/src/com/intellij/openapi/externalSystem/execution/ExternalSystemExecutionConsoleManager.java create mode 100644 platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/DefaultExternalSystemExecutionConsoleManager.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/AllInPackageGradleConfigurationProducer.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleConsoleProperties.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleSMTestProxy.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleUrlProvider.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/OpenGradleTestResultActionProvider.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestRunnerUtils.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AbstractTestEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterSuiteEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterTestEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeSuiteEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeTestEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ConfigurationErrorEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/OnOutputEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ReportLocationEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEvent.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventResult.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventType.java delete mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleIcons.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/util/XmlXpathHelper.java create mode 100644 plugins/gradle/tooling-extension/src/org/jetbrains/plugins/gradle/tooling/internal/testFilterInit.gradle diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/execution/ExternalSystemExecutionConsoleManager.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/execution/ExternalSystemExecutionConsoleManager.java new file mode 100644 index 000000000000..6ab3679a6f98 --- /dev/null +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/execution/ExternalSystemExecutionConsoleManager.java @@ -0,0 +1,53 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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 com.intellij.openapi.externalSystem.execution; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ExecutionConsole; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.openapi.externalSystem.model.ProjectSystemId; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +/** + * @author Vladislav.Soroka + * @since 2/18/14 + */ +public interface ExternalSystemExecutionConsoleManager { + ExtensionPointName EP_NAME + = ExtensionPointName.create("com.intellij.externalSystemExecutionConsoleManager"); + + @NotNull + ProjectSystemId getExternalSystemId(); + + @NotNull + ExecutionConsole attachExecutionConsole(@NotNull ExternalSystemTask task, + @NotNull Project project, + @NotNull ExternalSystemRunConfiguration configuration, + @NotNull Executor executor, + @NotNull ExecutionEnvironment env, + @NotNull ProcessHandler processHandler) throws ExecutionException; + + void onOutput(@NotNull String text, @NotNull Key processOutputType); + + boolean isApplicableFor(@NotNull ExternalSystemTask task); +} diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java index af0772dd7b31..6ed82492ce74 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/util/ExternalSystemApiUtil.java @@ -17,6 +17,7 @@ package com.intellij.openapi.externalSystem.util; import com.intellij.execution.rmi.RemoteUtil; import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; @@ -27,7 +28,6 @@ import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.Key; import com.intellij.openapi.externalSystem.model.ProjectSystemId; import com.intellij.openapi.externalSystem.model.project.LibraryData; -import com.intellij.openapi.externalSystem.model.project.LibraryDependencyData; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.service.ParametersEnhancer; import com.intellij.openapi.externalSystem.settings.AbstractExternalSystemLocalSettings; @@ -37,22 +37,22 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.BooleanFunction; -import com.intellij.util.NullableFunction; -import com.intellij.util.PathUtil; -import com.intellij.util.PathsList; +import com.intellij.util.*; import com.intellij.util.containers.ContainerUtilRt; +import com.intellij.util.containers.TransferToEDTQueue; import com.intellij.util.lang.UrlClassLoader; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.*; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; @@ -120,6 +120,15 @@ public class ExternalSystemApiUtil { } }; + @NotNull private static final TransferToEDTQueue TRANSFER_TO_EDT_QUEUE = + new TransferToEDTQueue("External System queue", new Processor() { + @Override + public boolean process(Runnable runnable) { + runnable.run(); + return true; + } + }, Condition.FALSE, 300); + private ExternalSystemApiUtil() { } @@ -382,6 +391,26 @@ public class ExternalSystemApiUtil { } } + /** + * Adds runnable to Event Dispatch Queue + * if we aren't in UnitTest of Headless environment mode + * + * @param runnable Runnable + */ + public static void addToInvokeLater(final Runnable runnable) { + final Application application = ApplicationManager.getApplication(); + final boolean unitTestMode = application.isUnitTestMode(); + if (unitTestMode) { + UIUtil.invokeLaterIfNeeded(runnable); + } + else if (application.isHeadlessEnvironment() || SwingUtilities.isEventDispatchThread()) { + runnable.run(); + } + else { + TRANSFER_TO_EDT_QUEUE.offer(runnable); + } + } + /** * Configures given classpath to reference target i18n bundle file(s). * diff --git a/platform/external-system-impl/external-system-impl.iml b/platform/external-system-impl/external-system-impl.iml index 7743115c2571..6c4570778dea 100644 --- a/platform/external-system-impl/external-system-impl.iml +++ b/platform/external-system-impl/external-system-impl.iml @@ -19,6 +19,8 @@ + + diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/DefaultExternalSystemExecutionConsoleManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/DefaultExternalSystemExecutionConsoleManager.java new file mode 100644 index 000000000000..b2a6bfd99b68 --- /dev/null +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/DefaultExternalSystemExecutionConsoleManager.java @@ -0,0 +1,70 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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 com.intellij.openapi.externalSystem.service.execution; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.filters.TextConsoleBuilderImpl; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.ui.ConsoleView; +import com.intellij.execution.ui.ExecutionConsole; +import com.intellij.openapi.externalSystem.execution.ExternalSystemExecutionConsoleManager; +import com.intellij.openapi.externalSystem.model.ProjectSystemId; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import org.jetbrains.annotations.NotNull; + +/** + * @author Vladislav.Soroka + * @since 2/18/14 + */ +public class DefaultExternalSystemExecutionConsoleManager implements ExternalSystemExecutionConsoleManager { + + private ProcessHandler myProcessHandler; + + @NotNull + @Override + public ProjectSystemId getExternalSystemId() { + return ProjectSystemId.IDE; + } + + @NotNull + @Override + public ExecutionConsole attachExecutionConsole(@NotNull ExternalSystemTask task, + @NotNull Project project, + @NotNull ExternalSystemRunConfiguration configuration, + @NotNull Executor executor, + @NotNull ExecutionEnvironment env, + @NotNull ProcessHandler processHandler) throws ExecutionException { + myProcessHandler = processHandler; + ConsoleView executionConsole = new TextConsoleBuilderImpl(project).getConsole(); + executionConsole.attachToProcess(processHandler); + return executionConsole; + } + + @Override + public void onOutput(@NotNull String text, @NotNull Key processOutputType) { + assert myProcessHandler != null; + myProcessHandler.notifyTextAvailable(text, processOutputType); + } + + @Override + public boolean isApplicableFor(@NotNull ExternalSystemTask task) { + return true; + } +} diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java index 71fc6e49a407..f9996a9d9ae5 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java @@ -9,18 +9,19 @@ import com.intellij.execution.configurations.LocatableConfigurationBase; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.executors.DefaultDebugExecutor; -import com.intellij.execution.filters.TextConsoleBuilderImpl; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ProgramRunner; -import com.intellij.execution.ui.ConsoleView; +import com.intellij.execution.ui.ExecutionConsole; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.externalSystem.execution.ExternalSystemExecutionConsoleManager; import com.intellij.openapi.externalSystem.model.ProjectSystemId; import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings; import com.intellij.openapi.externalSystem.model.execution.ExternalTaskExecutionInfo; import com.intellij.openapi.externalSystem.model.execution.ExternalTaskPojo; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter; import com.intellij.openapi.externalSystem.service.internal.ExternalSystemExecuteTaskTask; @@ -29,6 +30,7 @@ import com.intellij.openapi.externalSystem.util.ExternalSystemUtil; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; @@ -58,8 +60,7 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { public ExternalSystemRunConfiguration(@NotNull ProjectSystemId externalSystemId, Project project, ConfigurationFactory factory, - String name) - { + String name) { super(project, factory, name); mySettings.setExternalSystemIdString(externalSystemId.getId()); } @@ -105,19 +106,27 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { @Nullable @Override public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment env) throws ExecutionException { - return new MyRunnableState(mySettings, getProject(), DefaultDebugExecutor.EXECUTOR_ID.equals(executor.getId())); + return new MyRunnableState(mySettings, getProject(), DefaultDebugExecutor.EXECUTOR_ID.equals(executor.getId()), this, env); } public static class MyRunnableState implements RunProfileState { @NotNull private final ExternalSystemTaskExecutionSettings mySettings; @NotNull private final Project myProject; + @NotNull private final ExternalSystemRunConfiguration myConfiguration; + @NotNull private final ExecutionEnvironment myEnv; private final int myDebugPort; - public MyRunnableState(@NotNull ExternalSystemTaskExecutionSettings settings, @NotNull Project project, boolean debug) { + public MyRunnableState(@NotNull ExternalSystemTaskExecutionSettings settings, + @NotNull Project project, + boolean debug, + @NotNull ExternalSystemRunConfiguration configuration, + @NotNull ExecutionEnvironment env) { mySettings = settings; myProject = project; + myConfiguration = configuration; + myEnv = env; int port; if (debug) { try { @@ -141,10 +150,9 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { @Nullable @Override public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { - if(myProject.isDisposed()) return null; + if (myProject.isDisposed()) return null; ExternalSystemUtil.updateRecentTasks(new ExternalTaskExecutionInfo(mySettings.clone(), executor.getId()), myProject); - ConsoleView console = new TextConsoleBuilderImpl(myProject).getConsole(); final List tasks = ContainerUtilRt.newArrayList(); for (String taskName : mySettings.getTaskNames()) { tasks.add(new ExternalTaskPojo(taskName, mySettings.getExternalProjectPath(), null)); @@ -168,7 +176,11 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { debuggerSetup); final MyProcessHandler processHandler = new MyProcessHandler(task); - console.attachToProcess(processHandler); + final ExternalSystemExecutionConsoleManager consoleManager = getConsoleManagerFor(task); + + final ExecutionConsole consoleView = + consoleManager.attachExecutionConsole(task, myProject, myConfiguration, executor, myEnv, processHandler); + Disposer.register(myProject, consoleView); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override @@ -176,10 +188,12 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { final String startDateTime = DateFormatUtil.formatTimeWithSeconds(System.currentTimeMillis()); final String greeting; if (mySettings.getTaskNames().size() > 1) { - greeting = ExternalSystemBundle.message("run.text.starting.multiple.task", startDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); + greeting = ExternalSystemBundle + .message("run.text.starting.multiple.task", startDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); } else { - greeting = ExternalSystemBundle.message("run.text.starting.single.task", startDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); + greeting = + ExternalSystemBundle.message("run.text.starting.single.task", startDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); } processHandler.notifyTextAvailable(greeting, ProcessOutputTypes.SYSTEM); task.execute(new ExternalSystemTaskNotificationListenerAdapter() { @@ -192,7 +206,8 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { processHandler.notifyTextAvailable("\r", ProcessOutputTypes.SYSTEM); myResetGreeting = false; } - processHandler.notifyTextAvailable(text, stdOut ? ProcessOutputTypes.STDOUT : ProcessOutputTypes.STDERR); + + consoleManager.onOutput(text, stdOut ? ProcessOutputTypes.STDOUT : ProcessOutputTypes.STDERR); } @Override @@ -208,10 +223,12 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { final String endDateTime = DateFormatUtil.formatTimeWithSeconds(System.currentTimeMillis()); final String farewell; if (mySettings.getTaskNames().size() > 1) { - farewell = ExternalSystemBundle.message("run.text.ended.multiple.task", endDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); + farewell = ExternalSystemBundle + .message("run.text.ended.multiple.task", endDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); } else { - farewell = ExternalSystemBundle.message("run.text.ended.single.task", endDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); + farewell = + ExternalSystemBundle.message("run.text.ended.single.task", endDateTime, StringUtil.join(mySettings.getTaskNames(), " ")); } processHandler.notifyTextAvailable(farewell, ProcessOutputTypes.SYSTEM); processHandler.notifyProcessTerminated(0); @@ -219,7 +236,7 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { }); } }); - return new DefaultExecutionResult(console, processHandler); + return new DefaultExecutionResult(consoleView, processHandler); } } @@ -268,4 +285,16 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { super.notifyProcessTerminated(exitCode); } } + + @NotNull + private static ExternalSystemExecutionConsoleManager getConsoleManagerFor(@NotNull ExternalSystemTask task) { + for (ExternalSystemExecutionConsoleManager executionConsoleManager : ExternalSystemExecutionConsoleManager.EP_NAME.getExtensions()) { + if (executionConsoleManager.isApplicableFor(task)) + //noinspection unchecked + return executionConsoleManager; + } + + return new DefaultExternalSystemExecutionConsoleManager(); + } + } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java index f44bc55714fe..932e73ad98ea 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java @@ -66,6 +66,11 @@ public class ExternalSystemExecuteTaskTask extends AbstractExternalSystemTask { myDebuggerSetup = debuggerSetup; } + @NotNull + public List getTasksToExecute() { + return myTasksToExecute; + } + @NotNull private static String getLinkedExternalProjectPath(@NotNull Collection tasks) throws IllegalArgumentException { if (tasks.isEmpty()) { diff --git a/platform/platform-resources/src/META-INF/ExternalSystemExtensionPoints.xml b/platform/platform-resources/src/META-INF/ExternalSystemExtensionPoints.xml index e19ef8af6634..c2bb2b5bd819 100644 --- a/platform/platform-resources/src/META-INF/ExternalSystemExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/ExternalSystemExtensionPoints.xml @@ -9,5 +9,7 @@ interface="com.intellij.openapi.externalSystem.service.notification.ExternalSystemNotificationExtension"/> + \ No newline at end of file diff --git a/plugins/gradle/gradle.iml b/plugins/gradle/gradle.iml index 4f67303c2ac0..c5f5d1fcf16c 100644 --- a/plugins/gradle/gradle.iml +++ b/plugins/gradle/gradle.iml @@ -115,6 +115,8 @@ + + diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties index fddd05800345..88dfd899deed 100644 --- a/plugins/gradle/resources/i18n/GradleBundle.properties +++ b/plugins/gradle/resources/i18n/GradleBundle.properties @@ -33,4 +33,9 @@ gradle.codeInsight.action.apply_plugin.description=Add selected gradle plugin to gradle.codeInsight.action.apply_plugin.popup.title=Choose plugin gradle.codeInsight.action.add_maven_dependency.text=Add maven artifact dependency -gradle.codeInsight.action.add_maven_dependency.description=Add selected maven artifact dependency to the project \ No newline at end of file +gradle.codeInsight.action.add_maven_dependency.description=Add selected maven artifact dependency to the project + +gradle.test.runner.ui.tests.tree.presentation.labels.waiting.tests=Waiting for tests... +gradle.test.runner.ui.tests.tree.presentation.labels.no.tests.were.found=Test events were not received +gradle.test.runner.ui.tests.actions.open.gradle.report.text=Open Gradle test report +gradle.test.runner.ui.tests.actions.open.gradle.report.desc=Open Gradle reports produced by the Test task \ No newline at end of file diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index ef28a82b7071..815ecc15a919 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -69,6 +69,14 @@ + + + + + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/AllInPackageGradleConfigurationProducer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/AllInPackageGradleConfigurationProducer.java new file mode 100644 index 000000000000..eedf66f7b28f --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/AllInPackageGradleConfigurationProducer.java @@ -0,0 +1,112 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.execution.actions.RunConfigurationProducer; +import com.intellij.execution.junit.JavaRuntimeConfigurationProducerBase; +import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiPackage; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.service.GradleInstallationManager; +import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.io.File; +import java.util.List; +import java.util.regex.Matcher; + +/** + * @author Vladislav.Soroka + * @since 2/14/14 + */ +public class AllInPackageGradleConfigurationProducer extends RunConfigurationProducer { + + private static final List TASKS_TO_RUN = ContainerUtil.newArrayList("cleanTest", "test"); + + public AllInPackageGradleConfigurationProducer() { + super(GradleExternalTaskConfigurationType.getInstance()); + } + + @Override + protected boolean setupConfigurationFromContext(ExternalSystemRunConfiguration configuration, + ConfigurationContext context, + Ref sourceElement) { + + final PsiPackage psiPackage = JavaRuntimeConfigurationProducerBase.checkPackage(context.getPsiLocation()); + if (psiPackage == null) return false; + sourceElement.set(psiPackage); + + final Module module = context.getModule(); + if (module == null) return false; + + if (!StringUtil.equals( + module.getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return false; + } + + final String linkedGradleProject = module.getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY); + if (linkedGradleProject == null) return false; + configuration.getSettings().setExternalProjectPath(linkedGradleProject); + configuration.getSettings().setTaskNames(TASKS_TO_RUN); + if (psiPackage.getQualifiedName().isEmpty()) { + configuration.getSettings().setScriptParameters("--tests *"); + } + else { + configuration.getSettings() + .setScriptParameters(String.format("--tests %s.*", psiPackage.getQualifiedName())); + } + configuration.setName(suggestName(psiPackage, module)); + return true; + } + + @Override + public boolean isConfigurationFromContext(ExternalSystemRunConfiguration configuration, ConfigurationContext context) { + if (configuration == null) return false; + if (!GradleConstants.SYSTEM_ID.equals(configuration.getSettings().getExternalSystemId())) return false; + + final PsiPackage psiPackage = JavaRuntimeConfigurationProducerBase.checkPackage(context.getPsiLocation()); + if (psiPackage == null) return false; + + if (context.getModule() == null) return false; + + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY), + configuration.getSettings().getExternalProjectPath())) { + return false; + } + if (!configuration.getSettings().getTaskNames().containsAll(TASKS_TO_RUN)) return false; + + final String scriptParameters = configuration.getSettings().getScriptParameters() + ' '; + return psiPackage.getQualifiedName().isEmpty() + ? scriptParameters.contains("--tests * ") + : scriptParameters.contains(String.format("--tests %s.* ", psiPackage.getQualifiedName())); + } + + private static String suggestName(@NotNull PsiPackage aPackage, @NotNull Module module) { + return aPackage.getQualifiedName().isEmpty() + ? ExecutionBundle.message("test.in.scope.presentable.text", module.getName()) + : ExecutionBundle.message("test.in.scope.presentable.text", aPackage.getQualifiedName()); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleConsoleProperties.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleConsoleProperties.java new file mode 100644 index 000000000000..99f0fc4e0949 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleConsoleProperties.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.Executor; +import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties; +import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; +import org.jetbrains.annotations.Nullable; + +import java.io.File; + +/** + * @author Vladislav.Soroka + * @since 2/17/14 + */ +public class GradleConsoleProperties extends SMTRunnerConsoleProperties { + @Nullable private File gradleTestReport; + + public GradleConsoleProperties(final ExternalSystemRunConfiguration configuration, Executor executor) { + super(configuration, configuration.getSettings().getExternalSystemId().getReadableName(), executor); + } + + public void setGradleTestReport(@Nullable File gradleTestReport) { + this.gradleTestReport = gradleTestReport; + } + + @Nullable + public File getGradleTestReport() { + return gradleTestReport; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleSMTestProxy.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleSMTestProxy.java new file mode 100644 index 000000000000..4b89f3c758ee --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleSMTestProxy.java @@ -0,0 +1,84 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.Location; +import com.intellij.execution.stacktrace.StackTraceLine; +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import com.intellij.openapi.diff.LineTokenizer; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Vladislav.Soroka + * @since 2/25/14 + */ +public class GradleSMTestProxy extends SMTestProxy { + + @Nullable private final String myClassName; + @Nullable private String myStacktrace; + + public GradleSMTestProxy(String testName, boolean isSuite, @Nullable String locationUrl, @Nullable String className) { + super(testName, isSuite, locationUrl); + myClassName = className; + } + + @Override + public void setTestFailed(@NotNull String localizedMessage, @Nullable String stackTrace, boolean testError) { + setStacktraceIfNotSet(stackTrace); + super.setTestFailed(localizedMessage, stackTrace, testError); + } + + @Override + public void setTestComparisonFailed(@NotNull String localizedMessage, + @Nullable String stackTrace, + @NotNull String actualText, + @NotNull String expectedText) { + setStacktraceIfNotSet(stackTrace); + super.setTestComparisonFailed(localizedMessage, stackTrace, actualText, expectedText); + } + + @Override + public void setTestIgnored(@Nullable String ignoreComment, @Nullable String stackTrace) { + setStacktraceIfNotSet(stackTrace); + super.setTestIgnored(ignoreComment, stackTrace); + } + + @Nullable + @Override + public Location getLocation(Project project, GlobalSearchScope searchScope) { + if (getLocationUrl() != null) { + if (isDefect() && myStacktrace != null) { + final String[] stackTrace = new LineTokenizer(myStacktrace).execute(); + for (String aStackTrace : stackTrace) { + final StackTraceLine line = new StackTraceLine(project, aStackTrace); + if (getName().equals(line.getMethodName()) && StringUtil.equals(myClassName, line.getClassName())) { + return line.getMethodLocation(project); + } + } + } + } + + return super.getLocation(project, searchScope); + } + + private void setStacktraceIfNotSet(@Nullable String stacktrace) { + if (myStacktrace == null) myStacktrace = stacktrace; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java new file mode 100644 index 000000000000..77e6333ba019 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleTestsExecutionConsoleManager.java @@ -0,0 +1,196 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.ExecutionException; +import com.intellij.execution.Executor; +import com.intellij.execution.process.ProcessHandler; +import com.intellij.execution.runners.ExecutionEnvironment; +import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import com.intellij.execution.testframework.sm.runner.states.TestStateInfo; +import com.intellij.execution.testframework.sm.runner.ui.SMRootTestProxyFormatter; +import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView; +import com.intellij.execution.testframework.sm.runner.ui.TestTreeRenderer; +import com.intellij.execution.ui.ConsoleViewContentType; +import com.intellij.execution.ui.ExecutionConsole; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.externalSystem.execution.ExternalSystemExecutionConsoleManager; +import com.intellij.openapi.externalSystem.model.ProjectSystemId; +import com.intellij.openapi.externalSystem.model.execution.ExternalTaskPojo; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask; +import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; +import com.intellij.openapi.externalSystem.service.internal.ExternalSystemExecuteTaskTask; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.events.*; +import org.jetbrains.plugins.gradle.util.GradleBundle; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.util.Map; + +/** + * @author Vladislav.Soroka + * @since 2/18/14 + */ +public class GradleTestsExecutionConsoleManager implements ExternalSystemExecutionConsoleManager { + private static final Logger LOG = Logger.getInstance(GradleTestsExecutionConsoleManager.class); + + private Map testsMap = ContainerUtil.newHashMap(); + + private StringBuilder myBuffer = new StringBuilder(); + private final GradleUrlProvider myUrlProvider = new GradleUrlProvider(); + + private SMTRunnerConsoleView myExecutionConsole; + + @NotNull + @Override + public ProjectSystemId getExternalSystemId() { + return GradleConstants.SYSTEM_ID; + } + + public Map getTestsMap() { + return testsMap; + } + + public GradleUrlProvider getUrlProvider() { + return myUrlProvider; + } + + public SMTRunnerConsoleView getExecutionConsole() { + return myExecutionConsole; + } + + @NotNull + @Override + public ExecutionConsole attachExecutionConsole(@NotNull final ExternalSystemTask task, + @NotNull final Project project, + @NotNull final ExternalSystemRunConfiguration configuration, + @NotNull final Executor executor, + @NotNull final ExecutionEnvironment env, + @NotNull final ProcessHandler processHandler) throws ExecutionException { + final GradleConsoleProperties properties = new GradleConsoleProperties(configuration, executor); + myExecutionConsole = (SMTRunnerConsoleView)SMTestRunnerConnectionUtil.createAndAttachConsole( + configuration.getSettings().getExternalSystemId().getReadableName(), processHandler, properties, env); + + TestTreeRenderer originalRenderer = + ObjectUtils.tryCast(myExecutionConsole.getResultsViewer().getTreeView().getCellRenderer(), TestTreeRenderer.class); + if (originalRenderer != null) { + originalRenderer.setAdditionalRootFormatter(new SMRootTestProxyFormatter() { + @Override + public void format(@NotNull SMTestProxy.SMRootTestProxy testProxy, @NotNull TestTreeRenderer renderer) { + final TestStateInfo.Magnitude magnitude = testProxy.getMagnitudeInfo(); + if (magnitude == TestStateInfo.Magnitude.RUNNING_INDEX) { + renderer.clear(); + renderer.append(GradleBundle.message( + "gradle.test.runner.ui.tests.tree.presentation.labels.waiting.tests"), + SimpleTextAttributes.REGULAR_ATTRIBUTES + ); + } + else if (!testProxy.isInProgress() && testProxy.isEmptySuite()) { + renderer.clear(); + renderer.append(GradleBundle.message( + "gradle.test.runner.ui.tests.tree.presentation.labels.no.tests.were.found"), + SimpleTextAttributes.REGULAR_ATTRIBUTES + ); + } + } + }); + } + return myExecutionConsole; + } + + @Override + public void onOutput(@NotNull String text, @NotNull Key processOutputType) { + if (StringUtil.endsWith(text, "\n")) { + myBuffer.append(StringUtil.trimEnd(text, "\n")).append('\n'); + return; + } + else { + myBuffer.append(text); + } + + String trimmedText = myBuffer.toString().trim(); + myBuffer.setLength(0); + + if (!StringUtil.startsWith(trimmedText, "") || !StringUtil.endsWith(trimmedText, "")) { + if (text.trim().isEmpty()) return; + myExecutionConsole.print(text, ConsoleViewContentType.getConsoleViewType(processOutputType)); + return; + } + + try { + final XmlXpathHelper xml = new XmlXpathHelper(trimmedText); + + final TestEventType eventType = TestEventType.fromValue(xml.queryXml("/ijLog/event/@type")); + TestEvent testEvent = null; + switch (eventType) { + case CONFIGURATION_ERROR: + testEvent = new ConfigurationErrorEvent(this); + break; + case REPORT_LOCATION: + testEvent = new ReportLocationEvent(this); + break; + case BEFORE_TEST: + testEvent = new BeforeTestEvent(this); + break; + case ON_OUTPUT: + testEvent = new OnOutputEvent(this); + break; + case AFTER_TEST: + testEvent = new AfterTestEvent(this); + break; + case BEFORE_SUITE: + testEvent = new BeforeSuiteEvent(this); + break; + case AFTER_SUITE: + testEvent = new AfterSuiteEvent(this); + break; + case UNKNOWN_EVENT: + break; + } + if (testEvent != null) { + testEvent.process(xml); + } + } + catch (XmlXpathHelper.XmlParserException e) { + LOG.error("Gradle test events parser error", e); + } + } + + @Override + public boolean isApplicableFor(@NotNull ExternalSystemTask task) { + if (task instanceof ExternalSystemExecuteTaskTask) { + ExternalSystemExecuteTaskTask taskTask = (ExternalSystemExecuteTaskTask)task; + if (!StringUtil.equals(taskTask.getExternalSystemId().getId(), GradleConstants.SYSTEM_ID.getId())) return false; + + return ContainerUtil.find(taskTask.getTasksToExecute(), new Condition() { + @Override + public boolean value(ExternalTaskPojo pojo) { + return "test".equals(pojo.getName()); + } + }) != null; + } + return false; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleUrlProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleUrlProvider.java new file mode 100644 index 000000000000..1d073f9ab6df --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/GradleUrlProvider.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.Location; +import com.intellij.execution.PsiLocation; +import com.intellij.openapi.project.Project; +import com.intellij.psi.JavaPsiFacade; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.testIntegration.TestLocationProvider; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Vladislav.Soroka + * @since 2/24/14 + */ +public class GradleUrlProvider implements TestLocationProvider { + public static final String PROTOCOL_ID = "gradle"; + public static final String METHOD_PREF = "methodName"; + public static final String CLASS_PREF = "className"; + + @NotNull + public List getLocation(@NotNull String protocolId, @NotNull String locationData, Project project) { + if (!PROTOCOL_ID.equals(protocolId)) return Collections.emptyList(); + + final String className = extractFullClassName(locationData); + if (className == null) return Collections.emptyList(); + final PsiClass testClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project)); + if (testClass == null) return Collections.emptyList(); + + final String methodName = extractMethodName(locationData); + if (methodName == null) { + return Collections.singletonList(new PsiLocation(project, testClass)); + } + + final PsiMethod[] methods = testClass.findMethodsByName(methodName, true); + + List list = new ArrayList(methods.length); + for (PsiMethod method : methods) { + list.add(new PsiLocation(project, method)); + } + return list; + } + + @Nullable + private static String extractFullClassName(String locationData) { + final int i = locationData.indexOf("::"); + final String pref = locationData.substring(0, i); + final String qualifiedName = locationData.substring(i + 2); + if (METHOD_PREF.equals(pref)) { + final int dot = qualifiedName.lastIndexOf('.'); + return qualifiedName.substring(0, dot); + } + else if (CLASS_PREF.equals(pref)) { + return qualifiedName; + } + return null; + } + + @Nullable + private static String extractMethodName(String locationData) { + final int i = locationData.indexOf("::"); + final String pref = locationData.substring(0, i); + final String qualifiedName = locationData.substring(i + 2); + if (METHOD_PREF.equals(pref)) { + final int dot = qualifiedName.lastIndexOf('.'); + return qualifiedName.substring(dot+1); + } + return null; + } + +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/OpenGradleTestResultActionProvider.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/OpenGradleTestResultActionProvider.java new file mode 100644 index 000000000000..6ae3a2cfb8b6 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/OpenGradleTestResultActionProvider.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.testframework.TestConsoleProperties; +import com.intellij.execution.testframework.TestFrameworkRunningModel; +import com.intellij.execution.testframework.ToggleModelAction; +import com.intellij.execution.testframework.ToggleModelActionProvider; +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.util.config.AbstractProperty; +import com.intellij.util.config.BooleanProperty; +import icons.GradleIcons; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.util.GradleBundle; + +import java.io.File; + + +/** + * @author Vladislav.Soroka + * @since 2/26/14 + */ +public class OpenGradleTestResultActionProvider implements ToggleModelActionProvider { + public static final BooleanProperty OPEN_GRADLE_REPORT = new BooleanProperty("openGradleReport", false); + + public ToggleModelAction createToggleModelAction(TestConsoleProperties properties) { + return new MyToggleModelAction(properties); + } + + private static class MyToggleModelAction extends ToggleModelAction { + public MyToggleModelAction(TestConsoleProperties properties) { + super(GradleBundle.message("gradle.test.runner.ui.tests.actions.open.gradle.report.text"), + GradleBundle.message("gradle.test.runner.ui.tests.actions.open.gradle.report.desc"), + GradleIcons.GradleNavigate, properties, OPEN_GRADLE_REPORT); + } + + @Override + public void setModel(TestFrameworkRunningModel model) { + } + + @Override + public void setSelected(AnActionEvent e, boolean state) { + final String reportFilePath = getReportFilePath(); + if (reportFilePath != null) { + BrowserUtil.browse(reportFilePath); + } + } + + @Override + protected boolean isEnabled() { + final String reportFilePath = getReportFilePath(); + return reportFilePath != null; + } + + @Nullable + private String getReportFilePath() { + final AbstractProperty.AbstractPropertyContainer properties = getProperties(); + if (properties instanceof GradleConsoleProperties) { + GradleConsoleProperties gradleConsoleProperties = (GradleConsoleProperties)properties; + final File testReport = gradleConsoleProperties.getGradleTestReport(); + if (testReport != null && testReport.isFile()) return testReport.getPath(); + } + return null; + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java new file mode 100644 index 000000000000..47273d5836cb --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestClassGradleConfigurationProducer.java @@ -0,0 +1,188 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.JavaExecutionUtil; +import com.intellij.execution.JavaRunConfigurationExtensionManager; +import com.intellij.execution.Location; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.execution.actions.ConfigurationFromContext; +import com.intellij.execution.actions.RunConfigurationProducer; +import com.intellij.execution.junit.InheritorChooser; +import com.intellij.execution.junit.JUnitUtil; +import com.intellij.execution.junit.PatternConfigurationProducer; +import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.util.List; + +import static org.jetbrains.plugins.gradle.execution.test.runner.TestRunnerUtils.getMethodLocation; + +/** + * @author Vladislav.Soroka + * @since 2/14/14 + */ +public class TestClassGradleConfigurationProducer extends RunConfigurationProducer { + + private static final List TASKS_TO_RUN = ContainerUtil.newArrayList("cleanTest", "test"); + + public TestClassGradleConfigurationProducer() { + super(GradleExternalTaskConfigurationType.getInstance()); + } + + @Override + protected boolean setupConfigurationFromContext(ExternalSystemRunConfiguration configuration, + ConfigurationContext context, + Ref sourceElement) { + + final Location contextLocation = context.getLocation(); + assert contextLocation != null; + final Location location = JavaExecutionUtil.stepIntoSingleClass(contextLocation); + if (location == null) return false; + + if (PatternConfigurationProducer.isMultipleElementsSelected(context)) { + return false; + } + PsiClass testClass = JUnitUtil.getTestClass(location); + if (testClass == null) return false; + sourceElement.set(testClass); + + if (context.getModule() == null) return false; + + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return false; + } + + configuration.getSettings().setExternalProjectPath(context.getModule().getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY)); + configuration.getSettings().setTaskNames(TASKS_TO_RUN); + configuration.getSettings() + .setScriptParameters(String.format("--tests %s", testClass.getQualifiedName())); + configuration.setName(testClass.getName()); + + JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, contextLocation); + return true; + } + + @Override + public boolean isConfigurationFromContext(ExternalSystemRunConfiguration configuration, ConfigurationContext context) { + if (configuration == null) return false; + if (!GradleConstants.SYSTEM_ID.equals(configuration.getSettings().getExternalSystemId())) return false; + + final Location contextLocation = context.getLocation(); + assert contextLocation != null; + final Location location = JavaExecutionUtil.stepIntoSingleClass(contextLocation); + if (location == null) return false; + + if (PatternConfigurationProducer.isMultipleElementsSelected(context)) { + return false; + } + + Location methodLocation = getMethodLocation(contextLocation); + if (methodLocation != null) return false; + + PsiClass testClass = JUnitUtil.getTestClass(location); + if (testClass == null) return false; + + if (context.getModule() == null) return false; + + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY), + configuration.getSettings().getExternalProjectPath())) { + return false; + } + if (!configuration.getSettings().getTaskNames().containsAll(TASKS_TO_RUN)) return false; + + final String scriptParameters = configuration.getSettings().getScriptParameters() + ' '; + return scriptParameters.contains(String.format("--tests %s ", testClass.getQualifiedName())); + } + + @Override + public void onFirstRun(final ConfigurationFromContext fromContext, final ConfigurationContext context, final Runnable performRunnable) { + final PsiClass psiClass = (PsiClass)fromContext.getSourceElement(); + final InheritorChooser inheritorChooser = new InheritorChooser() { + @Override + protected void runForClasses(List classes, PsiMethod method, ConfigurationContext context, Runnable performRunnable) { + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return; + } + + ExternalSystemRunConfiguration configuration = (ExternalSystemRunConfiguration)fromContext.getConfiguration(); + if (!applyTestConfiguration(configuration, context, ArrayUtil.toObjectArray(classes, PsiClass.class))) return; + super.runForClasses(classes, method, context, performRunnable); + } + + @Override + protected void runForClass(PsiClass aClass, + PsiMethod psiMethod, + ConfigurationContext context, + Runnable performRunnable) { + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return; + } + + ExternalSystemRunConfiguration configuration = (ExternalSystemRunConfiguration)fromContext.getConfiguration(); + if (!applyTestConfiguration(configuration, context, aClass)) return; + super.runForClass(aClass, psiMethod, context, performRunnable); + } + }; + if (inheritorChooser.runMethodInAbstractClass(context, performRunnable, null, (PsiClass)fromContext.getSourceElement())) return; + super.onFirstRun(fromContext, context, performRunnable); + } + + private static boolean applyTestConfiguration(@NotNull ExternalSystemRunConfiguration configuration, + @NotNull ConfigurationContext context, + @NotNull PsiClass... containingClasses) { + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return false; + } + + configuration.getSettings().setExternalProjectPath(context.getModule().getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY)); + configuration.getSettings().setTaskNames(TASKS_TO_RUN); + + StringBuilder buf = new StringBuilder(); + for (PsiClass aClass : containingClasses) { + buf.append(String.format("--tests %s ", aClass.getQualifiedName())); + } + + configuration.getSettings().setScriptParameters(buf.toString()); + configuration.setName(StringUtil.join(containingClasses, new Function() { + @Override + public String fun(PsiClass aClass) { + return aClass.getName(); + } + },"|")); + return true; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java new file mode 100644 index 000000000000..5993480333e5 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestMethodGradleConfigurationProducer.java @@ -0,0 +1,173 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.JavaRunConfigurationExtensionManager; +import com.intellij.execution.Location; +import com.intellij.execution.actions.ConfigurationContext; +import com.intellij.execution.actions.ConfigurationFromContext; +import com.intellij.execution.actions.RunConfigurationProducer; +import com.intellij.execution.junit.InheritorChooser; +import com.intellij.execution.junit.PatternConfigurationProducer; +import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.util.ArrayUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.service.execution.GradleExternalTaskConfigurationType; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import java.util.List; + +import static org.jetbrains.plugins.gradle.execution.test.runner.TestRunnerUtils.getMethodLocation; + +/** + * @author Vladislav.Soroka + * @since 2/14/14 + */ +public class TestMethodGradleConfigurationProducer extends RunConfigurationProducer { + + private static final List TASKS_TO_RUN = ContainerUtil.newArrayList("cleanTest", "test"); + + public TestMethodGradleConfigurationProducer() { + super(GradleExternalTaskConfigurationType.getInstance()); + } + + @Override + protected boolean setupConfigurationFromContext(ExternalSystemRunConfiguration configuration, + ConfigurationContext context, + Ref sourceElement) { + if (PatternConfigurationProducer.isMultipleElementsSelected(context)) { + return false; + } + final Location contextLocation = context.getLocation(); + assert contextLocation != null; + Location methodLocation = getMethodLocation(contextLocation); + if (methodLocation == null) return false; + + final PsiMethod psiMethod = methodLocation.getPsiElement(); + sourceElement.set(psiMethod); + + final PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass == null) return false; + + if (context.getModule() == null) return false; + + if (!applyTestMethodConfiguration(configuration, context, psiMethod, containingClass)) return false; + + JavaRunConfigurationExtensionManager.getInstance().extendCreatedConfiguration(configuration, contextLocation); + return true; + } + + @Override + public boolean isConfigurationFromContext(ExternalSystemRunConfiguration configuration, ConfigurationContext context) { + if (configuration == null) return false; + if (!GradleConstants.SYSTEM_ID.equals(configuration.getSettings().getExternalSystemId())) return false; + + if (PatternConfigurationProducer.isMultipleElementsSelected(context)) { + return false; + } + + final Location contextLocation = context.getLocation(); + assert contextLocation != null; + + Location methodLocation = getMethodLocation(contextLocation); + if (methodLocation == null) return false; + + final PsiMethod psiMethod = methodLocation.getPsiElement(); + + final PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass == null) return false; + + if (context.getModule() == null) return false; + + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY), + configuration.getSettings().getExternalProjectPath())) { + return false; + } + if (!configuration.getSettings().getTaskNames().containsAll(TASKS_TO_RUN)) return false; + + final String scriptParameters = configuration.getSettings().getScriptParameters() + ' '; + return scriptParameters.contains(String.format("--tests %s.%s ", containingClass.getQualifiedName(), psiMethod.getName())); + } + + @Override + public void onFirstRun(final ConfigurationFromContext fromContext, final ConfigurationContext context, final Runnable performRunnable) { + final PsiMethod psiMethod = (PsiMethod)fromContext.getSourceElement(); + final PsiClass containingClass = psiMethod.getContainingClass(); + final InheritorChooser inheritorChooser = new InheritorChooser() { + @Override + protected void runForClasses(List classes, PsiMethod method, ConfigurationContext context, Runnable performRunnable) { + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return; + } + + ExternalSystemRunConfiguration configuration = (ExternalSystemRunConfiguration)fromContext.getConfiguration(); + if (!applyTestMethodConfiguration(configuration, context, psiMethod, ArrayUtil.toObjectArray(classes, PsiClass.class))) return; + super.runForClasses(classes, method, context, performRunnable); + } + + @Override + protected void runForClass(PsiClass aClass, + PsiMethod psiMethod, + ConfigurationContext context, + Runnable performRunnable) { + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return; + } + + ExternalSystemRunConfiguration configuration = (ExternalSystemRunConfiguration)fromContext.getConfiguration(); + if (!applyTestMethodConfiguration(configuration, context, psiMethod, aClass)) return; + super.runForClass(aClass, psiMethod, context, performRunnable); + } + }; + if (inheritorChooser.runMethodInAbstractClass(context, performRunnable, psiMethod, containingClass)) return; + super.onFirstRun(fromContext, context, performRunnable); + } + + private static boolean applyTestMethodConfiguration(@NotNull ExternalSystemRunConfiguration configuration, + @NotNull ConfigurationContext context, + @NotNull PsiMethod psiMethod, + @NotNull PsiClass... containingClasses) { + if (!StringUtil.equals( + context.getModule().getOptionValue(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY), + GradleConstants.SYSTEM_ID.toString())) { + return false; + } + + configuration.getSettings().setExternalProjectPath(context.getModule().getOptionValue(ExternalSystemConstants.LINKED_PROJECT_PATH_KEY)); + configuration.getSettings().setTaskNames(TASKS_TO_RUN); + + StringBuilder buf = new StringBuilder(); + for (PsiClass aClass : containingClasses) { + buf.append(String.format("--tests %s.%s ", aClass.getQualifiedName(), psiMethod.getName())); + } + + configuration.getSettings().setScriptParameters(buf.toString()); + configuration.setName(psiMethod.getName()); + return true; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestRunnerUtils.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestRunnerUtils.java new file mode 100644 index 000000000000..3f9004236934 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/TestRunnerUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner; + +import com.intellij.execution.Location; +import com.intellij.execution.junit.JUnitUtil; +import com.intellij.execution.junit2.PsiMemberParameterizedLocation; +import com.intellij.execution.junit2.info.MethodLocation; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; + +/** + * @author Vladislav.Soroka + * @since 2/24/14 + */ +public class TestRunnerUtils { + @Nullable + static Location getMethodLocation(@NotNull Location contextLocation) { + Location methodLocation = getTestMethod(contextLocation); + if (methodLocation == null) return null; + + if (contextLocation instanceof PsiMemberParameterizedLocation) { + PsiClass containingClass = ((PsiMemberParameterizedLocation)contextLocation).getContainingClass(); + if (containingClass != null) { + methodLocation = MethodLocation.elementInClass(methodLocation.getPsiElement(), containingClass); + } + } + return methodLocation; + } + + @Nullable + static Location getTestMethod(final Location location) { + for (Iterator> iterator = location.getAncestors(PsiMethod.class, false); iterator.hasNext(); ) { + final Location methodLocation = iterator.next(); + if (JUnitUtil.isTestMethod(methodLocation, false)) return methodLocation; + } + return null; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AbstractTestEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AbstractTestEvent.java new file mode 100644 index 000000000000..725a6e62a62f --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AbstractTestEvent.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm; +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleConsoleProperties; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public abstract class AbstractTestEvent implements TestEvent { + private final GradleTestsExecutionConsoleManager myConsoleManager; + + public AbstractTestEvent(GradleTestsExecutionConsoleManager consoleManager) { + this.myConsoleManager = consoleManager; + } + + public GradleTestsExecutionConsoleManager getConsoleManager() { + return myConsoleManager; + } + + protected SMTestRunnerResultsForm getResultsViewer() { + return myConsoleManager.getExecutionConsole().getResultsViewer(); + } + + protected Project getProject() { + return myConsoleManager.getExecutionConsole().getProperties().getProject(); + } + + protected GradleConsoleProperties getProperties() { + return (GradleConsoleProperties)getConsoleManager().getExecutionConsole().getProperties(); + } + + @Nullable + protected String findLocationUrl(@Nullable String name, @NotNull String fqClassName) { + return name == null ? "gradle://className::" + fqClassName : "gradle://methodName::" + fqClassName + '.' + name; + } + + protected String getTestName(@NotNull XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + return eventXml.queryXml("/ijLog/event/test/descriptor/@name"); + } + + protected String getParentTestId(@NotNull XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + return eventXml.queryXml("/ijLog/event/test/@parentId"); + } + + protected String getTestId(@NotNull XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + return eventXml.queryXml("/ijLog/event/test/@id"); + } + + protected String getTestClassName(@NotNull XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + return eventXml.queryXml("/ijLog/event/test/descriptor/@className"); + } + + protected TestEventResult getTestEventResultType(@NotNull XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + return TestEventResult.fromValue(eventXml.queryXml("/ijLog/event/test/result/@resultType")); + } + + protected void addToInvokeLater(final Runnable runnable) { + ExternalSystemApiUtil.addToInvokeLater(runnable); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterSuiteEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterSuiteEvent.java new file mode 100644 index 000000000000..43ffcd777a11 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterSuiteEvent.java @@ -0,0 +1,63 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public class AfterSuiteEvent extends AbstractTestEvent { + public AfterSuiteEvent(GradleTestsExecutionConsoleManager consoleManager) { + super(consoleManager); + } + + @Override + public void process(XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + final String testId = getTestId(eventXml); + + final SMTestProxy testProxy = getConsoleManager().getTestsMap().get(testId); + if (testProxy == null) return; + + final TestEventResult result = getTestEventResultType(eventXml); + switch (result) { + case SUCCESS: + testProxy.setFinished(); + break; + case FAILURE: + testProxy.setTestFailed("", null, false); + break; + case SKIPPED: + testProxy.setTestIgnored(null, null); + break; + case UNKNOWN_RESULT: + break; + } + + if (testProxy.isEmptySuite() && !(testProxy instanceof SMTestProxy.SMRootTestProxy)) { + testProxy.getParent().getChildren().remove(testProxy); + } + addToInvokeLater(new Runnable() { + @Override + public void run() { + getResultsViewer().onSuiteFinished(testProxy); + } + }); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterTestEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterTestEvent.java new file mode 100644 index 000000000000..7f66e57e8ef1 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/AfterTestEvent.java @@ -0,0 +1,126 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import com.intellij.openapi.util.Pair; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public class AfterTestEvent extends AbstractTestEvent { + public AfterTestEvent(GradleTestsExecutionConsoleManager consoleManager) { + super(consoleManager); + } + + @Override + public void process(XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + + final String testId = getTestId(eventXml); + + final String startTime = eventXml.queryXml("/ijLog/event/test/result/@startTime"); + final String endTime = eventXml.queryXml("/ijLog/event/test/result/@endTime"); + final String exceptionMsg = eventXml.queryXml("/ijLog/event/test/result/errorMsg"); + final String stackTrace = eventXml.queryXml("/ijLog/event/test/result/stackTrace"); + + final SMTestProxy testProxy = getConsoleManager().getTestsMap().get(testId); + if (testProxy == null) return; + + try { + testProxy.setDuration(Long.valueOf(endTime) - Long.valueOf(startTime)); + } + catch (NumberFormatException ignored) { + } + + final TestEventResult result = getTestEventResultType(eventXml); + switch (result) { + case SUCCESS: + testProxy.setFinished(); + addToInvokeLater(new Runnable() { + @Override + public void run() { + getResultsViewer().onTestFinished(testProxy); + } + }); + break; + case FAILURE: + final String failureType = eventXml.queryXml("/ijLog/event/test/result/failureType"); + if ("comparison".equals(failureType)) { + String actualText = eventXml.queryXml("/ijLog/event/test/result/actual"); + String expectedText = eventXml.queryXml("/ijLog/event/test/result/expected"); + testProxy.setTestComparisonFailed(exceptionMsg, stackTrace, actualText, expectedText); + } + else { + Pair comparisonPair = + parseComparisonMessage(exceptionMsg, "\nExpected: is \"(.*)\"\n\\s*got: \"(.*)\"\n"); + if (comparisonPair == null) { + comparisonPair = parseComparisonMessage(exceptionMsg, "\nExpected: is \"(.*)\"\n\\s*but: was \"(.*)\""); + } + if (comparisonPair == null) { + comparisonPair = parseComparisonMessage(exceptionMsg, "\nExpected: (.*)\n\\s*got: (.*)"); + } + if (comparisonPair == null) { + comparisonPair = parseComparisonMessage(exceptionMsg, "\\s*expected same:<(.*)> was not:<(.*)>"); + } + if (comparisonPair == null) { + comparisonPair = parseComparisonMessage(exceptionMsg, ".*\\s*expected:<(.*)> but was:<(.*)>"); + } + if (comparisonPair == null) { + comparisonPair = parseComparisonMessage(exceptionMsg, "\nExpected: \"(.*)\"\n\\s*but: was \"(.*)\""); + } + + if (comparisonPair != null) { + testProxy.setTestComparisonFailed(exceptionMsg, stackTrace, comparisonPair.second, comparisonPair.first); + } + else { + testProxy.setTestFailed(exceptionMsg, stackTrace, "error".equals(failureType)); + } + } + addToInvokeLater(new Runnable() { + @Override + public void run() { + getResultsViewer().onTestFailed(testProxy); + } + }); + break; + case SKIPPED: + testProxy.setTestIgnored(null, null); + addToInvokeLater(new Runnable() { + @Override + public void run() { + getResultsViewer().onTestIgnored(testProxy); + } + }); + break; + case UNKNOWN_RESULT: + break; + } + } + + private static Pair parseComparisonMessage(String message, final String regex) { + final Matcher matcher = Pattern.compile(regex, Pattern.DOTALL | Pattern.CASE_INSENSITIVE).matcher(message); + if (matcher.matches()) { + return Pair.create(matcher.group(1).replaceAll("\\\\n", "\n"), matcher.group(2).replaceAll("\\\\n", "\n")); + } + return null; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeSuiteEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeSuiteEvent.java new file mode 100644 index 000000000000..85e06cdc7dda --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeSuiteEvent.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public class BeforeSuiteEvent extends AbstractTestEvent { + public BeforeSuiteEvent(GradleTestsExecutionConsoleManager consoleManager) { + super(consoleManager); + } + + @Override + public void process(XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + final String testId = getTestId(eventXml); + final String parentTestId = getParentTestId(eventXml); + final String name = getTestName(eventXml); + final String fqClassName = getTestClassName(eventXml); + + if (StringUtil.isEmpty(parentTestId)) { + getConsoleManager().getTestsMap().put(testId, getResultsViewer().getTestsRootNode()); + } + else { + String locationUrl = findLocationUrl(null, fqClassName); + final SMTestProxy testProxy = new SMTestProxy(name, true, locationUrl); + testProxy.setLocator(getConsoleManager().getUrlProvider()); + testProxy.setStarted(); + getConsoleManager().getTestsMap().put(testId, testProxy); + final SMTestProxy parentTestProxy = getConsoleManager().getTestsMap().get(parentTestId); + if (parentTestProxy != null) { + parentTestProxy.addChild(testProxy); + addToInvokeLater(new Runnable() { + @Override + public void run() { + getResultsViewer().onSuiteStarted(testProxy); + } + }); + } + } + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeTestEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeTestEvent.java new file mode 100644 index 000000000000..bd512bc65b8a --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/BeforeTestEvent.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleSMTestProxy; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public class BeforeTestEvent extends AbstractTestEvent { + + public BeforeTestEvent(GradleTestsExecutionConsoleManager consoleManager) { + super(consoleManager); + } + + @Override + public void process(XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + final String testId = getTestId(eventXml); + final String parentTestId = getParentTestId(eventXml); + final String name = getTestName(eventXml); + final String fqClassName = getTestClassName(eventXml); + + String locationUrl = findLocationUrl(name, fqClassName); + final GradleSMTestProxy testProxy = new GradleSMTestProxy(name, false, locationUrl, fqClassName); + + testProxy.setStarted(); + testProxy.setLocator(getConsoleManager().getUrlProvider()); + getConsoleManager().getTestsMap().put(testId, testProxy); + + if (StringUtil.isEmpty(parentTestId)) { + getResultsViewer().getTestsRootNode().addChild(testProxy); + } + else { + final SMTestProxy parentTestProxy = getConsoleManager().getTestsMap().get(parentTestId); + if (parentTestProxy != null) { + parentTestProxy.addChild(testProxy); + } + } + + addToInvokeLater(new Runnable() { + @Override + public void run() { + getResultsViewer().onTestStarted(testProxy); + } + }); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ConfigurationErrorEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ConfigurationErrorEvent.java new file mode 100644 index 000000000000..bc1de08d5f80 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ConfigurationErrorEvent.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import com.intellij.ide.BrowserUtil; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationListener; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.externalSystem.ExternalSystemManager; +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil; +import com.intellij.openapi.options.Configurable; +import com.intellij.openapi.options.ShowSettingsUtil; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.GradleManager; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; +import org.jetbrains.plugins.gradle.service.project.GradleNotification; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import javax.swing.event.HyperlinkEvent; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public class ConfigurationErrorEvent extends AbstractTestEvent { + + public ConfigurationErrorEvent(GradleTestsExecutionConsoleManager consoleManager) { + super(consoleManager); + } + + @Override + public void process(XmlXpathHelper xml) throws XmlXpathHelper.XmlParserException { + final String errorTitle = xml.queryXml("/ijLog/event/title"); + assert errorTitle != null; + final String configurationErrorMsg = xml.queryXml("/ijLog/event/message"); + assert configurationErrorMsg != null; + final boolean openSettings = Boolean.valueOf(xml.queryXml("/ijLog/event/@openSettings")); + final Project project = getProject(); + assert project != null; + final String message = + openSettings ? String.format("
\n%s

\n\nOpen gradle settings", configurationErrorMsg) + : String.format("
\n%s", configurationErrorMsg); + GradleNotification.getInstance(project).showBalloon( + errorTitle, + message, NotificationType.WARNING, new NotificationListener() { + @Override + public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { + notification.expire(); + if ("Gradle settings".equals(event.getDescription())) { + ExternalSystemManager manager = ExternalSystemApiUtil.getManager(GradleConstants.SYSTEM_ID); + assert manager instanceof GradleManager; + GradleManager gradleManager = (GradleManager)manager; + Configurable configurable = gradleManager.getConfigurable(project); + ShowSettingsUtil.getInstance().editConfigurable(project, configurable); + } + else { + BrowserUtil.browse(event.getDescription()); + } + } + } + ); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/OnOutputEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/OnOutputEvent.java new file mode 100644 index 000000000000..e3a5954a4dfb --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/OnOutputEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import com.intellij.execution.process.ProcessOutputTypes; +import com.intellij.execution.testframework.sm.runner.SMTestProxy; +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public class OnOutputEvent extends AbstractTestEvent { + + public OnOutputEvent(GradleTestsExecutionConsoleManager consoleManager) { + super(consoleManager); + } + + @Override + public void process(XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + final String testId = getTestId(eventXml); + final String destination = eventXml.queryXml("/ijLog/event/test/event/@destination"); + final String output = eventXml.queryXml("/ijLog/event/test/event"); + + SMTestProxy testProxy = getConsoleManager().getTestsMap().get(testId); + if (testProxy == null) return; + + testProxy.addStdOutput(output, "StdOut".equals(destination) ? ProcessOutputTypes.STDOUT : ProcessOutputTypes.STDERR); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ReportLocationEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ReportLocationEvent.java new file mode 100644 index 000000000000..24606cd0ff32 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/ReportLocationEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; +import org.jetbrains.plugins.gradle.execution.test.runner.GradleTestsExecutionConsoleManager; + +import java.io.File; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public class ReportLocationEvent extends AbstractTestEvent { + + public ReportLocationEvent(GradleTestsExecutionConsoleManager consoleManager) { + super(consoleManager); + } + + @Override + public void process(XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException { + final String testReport = eventXml.queryXml("/ijLog/event/@testReport"); + assert testReport != null; + getProperties().setGradleTestReport(new File(testReport)); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEvent.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEvent.java new file mode 100644 index 000000000000..ce91c88ded29 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEvent.java @@ -0,0 +1,26 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +import org.jetbrains.plugins.gradle.util.XmlXpathHelper; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public interface TestEvent { + void process(XmlXpathHelper eventXml) throws XmlXpathHelper.XmlParserException; +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventResult.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventResult.java new file mode 100644 index 000000000000..8360c7ae886b --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventResult.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public enum TestEventResult { + SUCCESS("SUCCESS"), + FAILURE("FAILURE"), + SKIPPED("SKIPPED"), + UNKNOWN_RESULT("unknown"); + private final String value; + + TestEventResult(String v) { + value = v; + } + + public String value() { + return value; + } + + public static TestEventResult fromValue(String v) { + for (TestEventResult c : TestEventResult.values()) { + if (c.value.equals(v)) { + return c; + } + } + return UNKNOWN_RESULT; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventType.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventType.java new file mode 100644 index 000000000000..d9528336b691 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/execution/test/runner/events/TestEventType.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.execution.test.runner.events; + +/** + * @author Vladislav.Soroka + * @since 2/28/14 + */ +public enum TestEventType { + CONFIGURATION_ERROR("configurationError"), + REPORT_LOCATION("reportLocation"), + BEFORE_TEST("beforeTest"), + ON_OUTPUT("onOutput"), + AFTER_TEST("afterTest"), + BEFORE_SUITE("beforeSuite"), + AFTER_SUITE("afterSuite"), + UNKNOWN_EVENT("unknown"); + private final String value; + + TestEventType(String v) { + value = v; + } + + public String value() { + return value; + } + + public static TestEventType fromValue(String v) { + for (TestEventType c : TestEventType.values()) { + if (c.value.equals(v)) { + return c; + } + } + return UNKNOWN_EVENT; + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExternalTaskConfigurationType.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExternalTaskConfigurationType.java index c0322eafa624..ce7e4085739a 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExternalTaskConfigurationType.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExternalTaskConfigurationType.java @@ -16,6 +16,7 @@ package org.jetbrains.plugins.gradle.service.execution; import com.intellij.openapi.externalSystem.service.execution.AbstractExternalSystemTaskConfigurationType; +import com.intellij.openapi.externalSystem.util.ExternalSystemUtil; import org.jetbrains.plugins.gradle.util.GradleConstants; /** @@ -23,8 +24,12 @@ import org.jetbrains.plugins.gradle.util.GradleConstants; * @since 23.05.13 17:56 */ public class GradleExternalTaskConfigurationType extends AbstractExternalSystemTaskConfigurationType { - + public GradleExternalTaskConfigurationType() { super(GradleConstants.SYSTEM_ID); } + + public static GradleExternalTaskConfigurationType getInstance() { + return (GradleExternalTaskConfigurationType)ExternalSystemUtil.findConfigurationType(GradleConstants.SYSTEM_ID); + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java index af0a7e356a4b..f6fb50896b33 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleExecutionHelper.java @@ -48,6 +48,7 @@ import java.io.OutputStream; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; /** * @author Denis Zhdanov @@ -154,6 +155,9 @@ public class GradleExecutionHelper { return StringUtil.isEmpty(s) ? null : s; } }); + + // TODO remove this replacement when --tests option will become available for tooling API + replaceTestCommandOptionWithInitScript(filteredArgs); operation.withArguments(ArrayUtil.toStringArray(filteredArgs)); } @@ -384,7 +388,7 @@ public class GradleExecutionHelper { LOG.warn("Can't get init script template"); return null; } - String s = FileUtil.loadTextAndClose(stream).replace("${EXTENSIONS_JARS_PATH}", getToolingExtensionsJarPaths()); + String s = FileUtil.loadTextAndClose(stream).replaceFirst(Pattern.quote("${EXTENSIONS_JARS_PATH}"), getToolingExtensionsJarPaths()); if (isBuildSrcProject) { String buildSrcDefaultInitScript = getBuildSrcDefaultInitScript(); if (buildSrcDefaultInitScript == null) return null; @@ -432,6 +436,52 @@ public class GradleExecutionHelper { } } + private static void replaceTestCommandOptionWithInitScript(@NotNull List args) { + Set testIncludePatterns = ContainerUtil.newLinkedHashSet(); + Iterator it = args.iterator(); + while (it.hasNext()) { + final String next = it.next(); + if ("--tests".equals(next)) { + it.remove(); + if (it.hasNext()) { + testIncludePatterns.add(it.next()); + it.remove(); + } + } + } + if (!testIncludePatterns.isEmpty()) { + StringBuilder buf = new StringBuilder(); + buf.append('['); + for (Iterator iterator = testIncludePatterns.iterator(); iterator.hasNext(); ) { + String pattern = iterator.next(); + buf.append('\"').append(pattern).append('\"'); + if (iterator.hasNext()) { + buf.append(','); + } + } + buf.append(']'); + + InputStream stream = + ProjectImportAction.class.getResourceAsStream("/org/jetbrains/plugins/gradle/tooling/internal/testFilterInit.gradle"); + try { + if (stream == null) { + LOG.warn("Can't get test filter init script template"); + return; + } + String s = FileUtil.loadTextAndClose(stream).replaceFirst(Pattern.quote("${TEST_NAME_INCLUDES}"), buf.toString()); + final File tempFile = FileUtil.createTempFile("ijinit", '.' + GradleConstants.EXTENSION, true); + FileUtil.writeToFile(tempFile, s); + ContainerUtil.addAll(args, GradleConstants.INIT_SCRIPT_CMD_OPTION, tempFile.getAbsolutePath()); + } + catch (Exception e) { + LOG.warn("Can't generate IJ gradle test filter init script", e); + } + finally { + StreamUtil.closeStream(stream); + } + } + } + @NotNull private static String getToolingExtensionsJarPaths() throws ClassNotFoundException { final ArrayList> list = ContainerUtil.newArrayList( diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java index f0fc48bebe70..b59668e63c2e 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/task/GradleTaskManager.java @@ -73,10 +73,6 @@ public class GradleTaskManager extends AbstractExternalSystemTaskManager f = new Function() { @Override diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleIcons.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleIcons.java deleted file mode 100644 index 1bcf20e3be7c..000000000000 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/ui/GradleIcons.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.jetbrains.plugins.gradle.ui; - -import com.intellij.openapi.application.ex.ApplicationInfoEx; -import com.intellij.openapi.util.IconLoader; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; - -/** - * @author Denis Zhdanov - * @since 1/18/12 1:16 PM - */ -public class GradleIcons { - - public static final Icon PROJECT_ICON = getProjectIcon(); - - private GradleIcons() { - } - - @NotNull - private static Icon getProjectIcon() { - try { - return IconLoader.getIcon(ApplicationInfoEx.getInstanceEx().getSmallIconUrl()); - } - catch (Exception e) { - // Control flow may reach this place if we run tests and platform IoC has not been initialised. - return IconLoader.getIcon("/nodes/ideProject.png"); - } - } -} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/XmlXpathHelper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/XmlXpathHelper.java new file mode 100644 index 000000000000..d76858755617 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/XmlXpathHelper.java @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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.jetbrains.plugins.gradle.util; + +import com.sun.javafx.beans.annotations.NonNull; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.StringReader; + +/** + * {@link XmlXpathHelper} utility class for query of XML using XPath expressions. + * + * @author Vladislav.Soroka + * @since 2/20/14 + */ +public class XmlXpathHelper { + private final XPath xpath; + private Document xmlDocument; + + /** + * Parses the given XML string as a DOM document, using the JDK parser. The parser does not + * validate, and is namespace aware. + * + * @param xml the XML content to be parsed (must be well formed) + * @param namespaceAware whether the parser is namespace aware + * @throws XmlParserException + */ + public XmlXpathHelper(@NonNull String xml, boolean namespaceAware) throws XmlParserException { + xpath = XPathFactory.newInstance().newXPath(); + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + InputSource is = new InputSource(new StringReader(xml)); + factory.setNamespaceAware(namespaceAware); + factory.setValidating(false); + xmlDocument = factory.newDocumentBuilder().parse(is); + } + catch (Exception ex) { + throw new XmlParserException(ex); + } + } + + public XmlXpathHelper(@NonNull String xml) throws XmlParserException { + this(xml, false); + } + + public String queryXml(final String xpathExpr) throws XmlParserException { + try { + return xmlDocument == null ? "" : xpath.evaluate(xpathExpr, xmlDocument); + } + catch (XPathExpressionException ex) { + throw new XmlParserException(ex); + } + } + + /** + * Extracts the String value for the given expression. + * + * @param node + * @param expr + * @return + */ + public String getValue(Node node, String expr) throws XmlParserException { + try { + return xpath.compile(expr).evaluate(node); + } + catch (XPathExpressionException ex) { + throw new XmlParserException(ex); + } + } + + /** + * {@link XmlParserException} indicates errors during xml processing. + */ + public static class XmlParserException extends Exception { + public XmlParserException(final Throwable cause) { + super(cause); + } + + public XmlParserException(final String message, final Throwable cause) { + super(message, cause); + } + + public XmlParserException(final String message) { + super(message); + } + } +} diff --git a/plugins/gradle/tooling-extension/src/org/jetbrains/plugins/gradle/tooling/internal/testFilterInit.gradle b/plugins/gradle/tooling-extension/src/org/jetbrains/plugins/gradle/tooling/internal/testFilterInit.gradle new file mode 100644 index 000000000000..68d0389ed9e6 --- /dev/null +++ b/plugins/gradle/tooling-extension/src/org/jetbrains/plugins/gradle/tooling/internal/testFilterInit.gradle @@ -0,0 +1,163 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * 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. + */ + +String[] ijTestIncludes = ${TEST_NAME_INCLUDES} +boolean ijTestFilteringSupported = GradleVersion.current().compareTo(GradleVersion.version("1.10"))>=0 +boolean skipExecution + +gradle.addBuildListener(new BuildListener() { + void buildFinished(BuildResult result) { + if(result.failure?.message?.startsWith("Task 'cleanTest' not found in") || result.failure?.message?.startsWith("Task 'test' not found in")) { + logConfigurationError( + "Unable to run unit tests", + "Unable to run unit tests, since there is no 'test' task configured in the project." + + "\n
You can add the following into the project gradle script: apply plugin: 'java'" + + "\n

See Gradle Test task for details", + false + ) + } + } + void buildStarted(Gradle gradle){} + void projectsEvaluated(Gradle gradle){} + void projectsLoaded(Gradle gradle){} + void settingsEvaluated(Settings settings){} +}) + +for (incl in ijTestIncludes) { + if(incl != '*' && !ijTestFilteringSupported) { + skipExecution = true + logConfigurationError( + "Unable to configure test filtering", + "Unable to configure test filtering for " + GradleVersion.current() +".
Please, use Gradle 1.10 or above or run all tests of a module.", + true) + break; + } +} + +gradle.taskGraph.beforeTask { Task task -> + task.onlyIf { !skipExecution } + + if (task instanceof Test) { + try { + logTestReportLocation(task.reports?.html?.entryPoint?.path) + + task.beforeSuite { descriptor -> + logTestEvent("beforeSuite", descriptor, null, null) + } + task.afterSuite { descriptor, result -> + logTestEvent("afterSuite", descriptor, null, result) + } + + task.beforeTest { descriptor -> + logTestEvent("beforeTest", descriptor, null, null) + } + task.onOutput { descriptor, event -> + logTestEvent("onOutput", descriptor, event, null) + } + task.afterTest { descriptor, result -> + logTestEvent("afterTest", descriptor, null, result) + } + + if(ijTestFilteringSupported) { + task.filter { + ijTestIncludes.each(){ includeTestsMatching "${it}" } + } + } + } + catch (all) { + logger.error("", all) + } + } +} + +Object logTestEvent(def testEventType, def testDescriptor, def testEvent, def testResult) { + def writer = new StringWriter() + def xml = new groovy.xml.MarkupBuilder(writer) + xml.event(type: testEventType){ + test(id: testDescriptor.id, parentId: testDescriptor.parent?.id ?: '') { + if(testDescriptor){ + descriptor(name: testDescriptor.name ?: '', className: testDescriptor.className ?: '') + } + if(testEvent) { + def message = escapeCdata(testEvent.message) + event(destination: testEvent.destination){ + xml.mkp.yieldUnescaped("$message") + } + } + if(testResult) { + def errorMsg = escapeCdata(testResult.exception?.message?:'') + def stackTrace = escapeCdata(getStackTrace(testResult.exception)) + result(resultType: testResult.resultType ?: '', startTime: testResult.startTime, endTime: testResult.endTime){ + if(testResult.exception?.message?.trim()) xml.mkp.yieldUnescaped("$errorMsg") + if(testResult.exception) xml.mkp.yieldUnescaped("$stackTrace") + + if('junit.framework.ComparisonFailure'.equals(testResult.exception?.class?.name) || + 'org.junit.ComparisonFailure'.equals(testResult.exception?.class?.name)) { + def expected = escapeCdata(testResult.exception.fExpected) + def actual = escapeCdata(testResult.exception.fActual) + failureType('comparison') + xml.mkp.yieldUnescaped("$expected") + xml.mkp.yieldUnescaped("$actual") + } else if('junit.framework.AssertionFailedError'.equals(testResult.exception?.class?.name) || + testResult.exception instanceof AssertionError) { + failureType('assertionFailed') + } else { + failureType('error') + } + } + } + } + } + + writeLog(writer.toString()) +} + +def logTestReportLocation(def report) { + if(!report) return + def writer = new StringWriter() + def xml = new groovy.xml.MarkupBuilder(writer) + xml.event(type: 'reportLocation', testReport: report) + writeLog(writer.toString()); +} + +def logConfigurationError(aTitle, aMessage, boolean openSettings) { + def writer = new StringWriter() + def xml = new groovy.xml.MarkupBuilder(writer) + xml.event(type: 'configurationError', openSettings: openSettings) { + title(aTitle) + message(aMessage) + } + writeLog(writer.toString()); +} + +String escapeCdata(String s) { + return "", "]]]]>")+ "]]>"; +} +def getStackTrace(Throwable t) { + if(!t) return '' + StringWriter sw = new StringWriter() + t.printStackTrace(new PrintWriter(sw)) + sw.toString() +} + +def wrap(String s) { + if(!s) return s; + s.replaceAll("\r\n|\n\r|\n|\r","\n") +} + +def writeLog(s) { + println String.format("\n%s", wrap(s)) +}