/* * Copyright 2016-present Facebook, Inc. * * 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.facebook.buck.intellij.ideabuck.configurations; import com.facebook.buck.intellij.ideabuck.build.BuckBuildCommandHandler; import com.facebook.buck.intellij.ideabuck.build.BuckCommand; import com.facebook.buck.intellij.ideabuck.build.BuckCommandHandler; import com.facebook.buck.intellij.ideabuck.config.BuckModule; import com.intellij.debugger.DebugEnvironment; import com.intellij.debugger.DebuggerManagerEx; import com.intellij.debugger.DefaultDebugEnvironment; import com.intellij.debugger.engine.JavaDebugProcess; import com.intellij.debugger.engine.RemoteStateState; import com.intellij.debugger.impl.DebuggerSession; import com.intellij.execution.DefaultExecutionResult; import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionResult; import com.intellij.execution.Executor; import com.intellij.execution.RunManager; import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationTypeUtil; import com.intellij.execution.configurations.RemoteConnection; import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.process.OSProcessHandler; import com.intellij.execution.process.ProcessHandler; import com.intellij.execution.process.ProcessOutputTypes; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironmentBuilder; import com.intellij.execution.runners.ProgramRunner; import com.intellij.execution.testframework.TestConsoleProperties; import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; import com.intellij.execution.ui.ConsoleView; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.xdebugger.XDebugProcess; import com.intellij.xdebugger.XDebugProcessStarter; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerManager; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; class TestExecutionState implements RunProfileState { protected static final Logger LOG = Logger.getInstance(BuckCommandHandler.class); public static final Pattern DEBUG_SUSPEND_PATTERN = Pattern.compile( "Debugging. Suspending JVM. Connect a JDWP debugger to port (\\d+) to proceed."); final TestConfiguration mConfiguration; final Project mProject; public TestExecutionState(TestConfiguration mConfiguration, Project project) { this.mConfiguration = mConfiguration; this.mProject = project; } @Nullable @Override public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { final ProcessHandler processHandler = runBuildCommand(executor); final TestConsoleProperties properties = new BuckTestConsoleProperties( processHandler, mProject, mConfiguration, "Buck test", executor); final ConsoleView console = SMTestRunnerConnectionUtil.createAndAttachConsole("buck test", processHandler, properties); return new DefaultExecutionResult(console, processHandler, AnAction.EMPTY_ARRAY); } private ProcessHandler runBuildCommand(Executor executor) { final BuckModule buckModule = mProject.getComponent(BuckModule.class); final String target = mConfiguration.data.target; final String additionalParams = mConfiguration.data.additionalParams; final String testSelectors = mConfiguration.data.testSelectors; final String title = "Buck Test " + target; buckModule.attach(target); final BuckBuildCommandHandler handler = new BuckBuildCommandHandler( mProject, mProject.getBaseDir(), BuckCommand.TEST, /* doStartNotify */ false) { @Override protected void notifyLines(Key outputType, Iterable<String> lines) { super.notifyLines(outputType, lines); if (outputType != ProcessOutputTypes.STDERR) { return; } for (String line : lines) { final Matcher matcher = DEBUG_SUSPEND_PATTERN.matcher(line); if (matcher.find()) { final String port = matcher.group(1); attachDebugger(title, port); } } } }; if (!target.isEmpty()) { handler.command().addParameter(target); } if (!testSelectors.isEmpty()) { handler.command().addParameter("--test-selectors"); handler.command().addParameter(testSelectors); } if (!additionalParams.isEmpty()) { for (String param : additionalParams.split("\\s")) { handler.command().addParameter(param); } } if (executor.getId().equals(DefaultDebugExecutor.EXECUTOR_ID)) { handler.command().addParameter("--debug"); } handler.start(); final OSProcessHandler result = handler.getHandler(); showProgress(result, title); return result; } private void showProgress(final OSProcessHandler result, final String title) { final ProgressManager manager = ProgressManager.getInstance(); ApplicationManager.getApplication() .invokeLater( () -> { manager.run( new Task.Backgroundable(mProject, title, true) { public void run(@NotNull final ProgressIndicator indicator) { try { result.waitFor(); } finally { indicator.cancel(); } } }); }); } private void attachDebugger(String title, String port) { final RemoteConnection remoteConnection = new RemoteConnection(/* useSockets */ true, "localhost", port, /* serverMode */ false); final RemoteStateState state = new RemoteStateState(mProject, remoteConnection); final String name = title + " debugger (" + port + ")"; final ConfigurationFactory cfgFactory = ConfigurationTypeUtil.findConfigurationType("Remote").getConfigurationFactories()[0]; RunnerAndConfigurationSettings runSettings = RunManager.getInstance(mProject).createRunConfiguration(name, cfgFactory); final Executor debugExecutor = DefaultDebugExecutor.getDebugExecutorInstance(); final ExecutionEnvironment env = new ExecutionEnvironmentBuilder(mProject, debugExecutor) .runProfile(runSettings.getConfiguration()) .build(); final int pollTimeout = 3000; final DebugEnvironment environment = new DefaultDebugEnvironment(env, state, remoteConnection, pollTimeout); ApplicationManager.getApplication() .invokeLater( () -> { try { final DebuggerSession debuggerSession = DebuggerManagerEx.getInstanceEx(mProject).attachVirtualMachine(environment); if (debuggerSession == null) { return; } XDebuggerManager.getInstance(mProject) .startSessionAndShowTab( name, null, new XDebugProcessStarter() { @Override @NotNull public XDebugProcess start(@NotNull XDebugSession session) { return JavaDebugProcess.create(session, debuggerSession); } }); } catch (ExecutionException e) { LOG.error( "failed to attach to debugger on port " + port + " with polling timeout " + pollTimeout); } }); } }