Testing RunConfigurationProvider and ModuleBasedConfiguration subclass

Answered

I want to write tests for https://github.com/KronicDeth/intellij-elixir/pull/482, which introduces a new Run Configuration format to run tests using the `mix test` command line tool.  I found `ExecutionUtil.runConfiguration` and by stepping through I found the MessageBus and ExecutionManager.EXECUTION_TOPIC, but when running `ExecutuonUtil.runConfiguration` in a ProjectWizardTestCase subclass, the ExecutionListener passed to connection.subscribe is only run after the testDirectory() method returns.  How do I setup the test, so I can check the output of `mix test` as run by the runConfiguration.  I need to ensure it exits cleanly and parses the output correctly.  If there's a different API to do that I'm fine with switch to that.  Full test code:

package org.elixir_lang.mix.runner.exunit;

import com.intellij.compiler.CompilerWorkspaceConfiguration;
import com.intellij.execution.*;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.ConfigurationFromContext;
import com.intellij.execution.actions.RunConfigurationProducer;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.impl.ExecutionManagerImpl;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.ide.projectWizard.ProjectWizardTestCase;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.PathMacros;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.impl.ModuleRootManagerImpl;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.testFramework.MapDataContext;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusConnection;
import org.elixir_lang.configuration.ElixirCompilerSettings;
import org.elixir_lang.mix.importWizard.MixProjectImportBuilder;
import org.elixir_lang.mix.importWizard.MixProjectImportProvider;
import org.elixir_lang.sdk.ElixirSdkRelease;
import org.elixir_lang.sdk.ElixirSdkType;
import org.jdom.Document;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

public class MixExUnitRunConfigurationProducerTest extends ProjectWizardTestCase {
private static final String MODULE_DIR = "MODULE_DIR";
private static final String TEST_DATA_IMPORT = "testData/org/elixir_lang/" + "mix/runner/exunit/mix_ex_unit_run_configuration_producer";

private static void validateProject(@NotNull Project project) {
ElixirCompilerSettings compilerSettings = ElixirCompilerSettings.getInstance(project);
assertNotNull("Elixir compiler settings are not created.", compilerSettings);
assertTrue("Mix compiler is not set as default compiler.", compilerSettings.isUseMixCompilerEnabled());
assertFalse("Clear output directory flag was not unset", CompilerWorkspaceConfiguration.getInstance(project).CLEAR_OUTPUT_DIRECTORY);
}

/*
* Tests
*/

public void testDirectory() throws InterruptedException {
Project project = getProject();
VirtualFile baseVirtualFile = project.getBaseDir();
VirtualFile testVirtualFile = baseVirtualFile.findChild("test");

assertNotNull("Project does not have a test directory", testVirtualFile);
PsiDirectory testPsiDirectory = PsiManager.getInstance(project).findDirectory(testVirtualFile);

assertNotNull("Test directory does not have PsiDirectory", testPsiDirectory);

MixExUnitRunConfigurationProducer mixExUnitRunConfigurationProducer = mixExUnitRunConfigurationProducer();

MapDataContext dataContext = new MapDataContext();
dataContext.put(CommonDataKeys.PROJECT, project);
dataContext.put(Location.DATA_KEY, PsiLocation.fromPsiElement(testPsiDirectory));

ConfigurationContext configurationContext = ConfigurationContext.getFromContext(dataContext);
ConfigurationFromContext configurationFromContext =
mixExUnitRunConfigurationProducer.createConfigurationFromContext(configurationContext);

assertNotNull(configurationFromContext);

RunnerAndConfigurationSettings runnerAndConfigurationSettings =
configurationFromContext.getConfigurationSettings();

assertNotNull(runnerAndConfigurationSettings);

Executor executor = DefaultRunExecutor.getRunExecutorInstance();

MessageBusConnection connection = project.getMessageBus().connect();
final SynchronousQueue<String> synchronousQueue = new SynchronousQueue<String>();

connection.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionListener() {
@Override
public void processStartScheduled(String executorId, ExecutionEnvironment env) {
assert executorId != null;
}

@Override
public void processStarting(String executorId, @NotNull ExecutionEnvironment env) {
assert executorId != null;
}

@Override
public void processNotStarted(String executorId, @NotNull ExecutionEnvironment env) {
assert executorId != null;
}

@Override
public void processStarted(String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler) {
assert executorId != null;
}

@Override
public void processTerminating(@NotNull RunProfile runProfile, @NotNull ProcessHandler handler) {
assert handler != null;
}

@Override
public void processTerminated(@NotNull RunProfile runProfile, @NotNull ProcessHandler handler) {
assert handler != null;
}
});

ExecutionUtil.runConfiguration(runnerAndConfigurationSettings, executor);

//assertEquals("processStartScheduled", synchronousQueue.take());

assertNotNull(executor);
}

public void testFile() {

}

public void testLine() {

}

/*
* Protected Instance Methods
*/

@Override
protected void setUp() throws Exception {
super.setUp();
createSdk();
File currentTestRoot = new File(TEST_DATA_IMPORT);
FileUtil.copyDir(currentTestRoot, new File(getProject().getBaseDir().getPath()));
String projectPath = getProject().getBaseDir().getPath();
Module firstModule = importProjectFrom(projectPath, null, new MixProjectImportProvider(new MixProjectImportBuilder()));
Project createProject = firstModule.getProject();
validateProject(createProject);

for (Module importedModule : ModuleManager.getInstance(createProject).getModules()) {
validateModule(importedModule);
}
}

/*
* Private Instance Methods
*/

private void createSdk() {
String elixirSdkPath = elixirSdkPath();
File elixirSdkFile = new File(elixirSdkPath);
String elixirSdkName = elixirSdkFile.getName();
ElixirSdkRelease elixirSdkRelease = ElixirSdkRelease.fromString(elixirSdkName);

assertNotNull("ElixirSdkRelease could not be extracted from name (" + elixirSdkName + ") at end of path (" + elixirSdkPath + ")", elixirSdkRelease);

final Sdk sdk = ElixirSdkType.createMockSdk(elixirSdkPath, elixirSdkRelease);

ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
ProjectJdkTable.getInstance().addJdk(sdk);
}
});
}

@NotNull
private String elixirSdkPath() {
String elixirSdkPath = System.getenv("ELIXIR_SDK_PATH");

assertNotNull("ELIXIR_SDK_PATH is not set", elixirSdkPath);

return elixirSdkPath;
}

private MixExUnitRunConfigurationProducer mixExUnitRunConfigurationProducer() {
List<RunConfigurationProducer<?>> producers = runConfigurationProducerList();

MixExUnitRunConfigurationProducer mixExUnitRunConfigurationProducer = null;

for (RunConfigurationProducer<?> producer : producers) {
if (producer instanceof MixExUnitRunConfigurationProducer) {
mixExUnitRunConfigurationProducer = (MixExUnitRunConfigurationProducer) producer;
}
}

assertNotNull(
"No MixExUnitRunConfigurationProducer in RunConfigurationProducer.getProducers(project)",
mixExUnitRunConfigurationProducer
);

return mixExUnitRunConfigurationProducer;
}

private List<RunConfigurationProducer<?>> runConfigurationProducerList() {
List<RunConfigurationProducer<?>> producers = RunConfigurationProducer.getProducers(getProject());

assertTrue(producers.size() > 0);

return producers;
}

private void validateModule(@NotNull Module module) throws Exception {
String importedModulePath = getProject().getBaseDir().getPath();

Element actualImlElement = new Element("root");
((ModuleRootManagerImpl) ModuleRootManager.getInstance(module)).getState().writeExternal(actualImlElement);
PathMacros.getInstance().setMacro(MODULE_DIR, importedModulePath);
PathMacroManager.getInstance(module).collapsePaths(actualImlElement);
PathMacroManager.getInstance(getProject()).collapsePaths(actualImlElement);
PathMacros.getInstance().removeMacro(MODULE_DIR);
}
}
2 comments
Avatar
Vassiliy Kudryashov
Comment actions Permalink

I suppose you should wait in a loop after calling

 ExecutionUtil.runConfiguration(...)

As for break condition you may try

(ExecutionManager.getInstance(...).getRunningProcesses() == 0) && startedFlag

where startedFlag becomes true when processStarting() is triggered in your listener.

0
Comment actions Permalink

Looping on `getRunningProcesses().length == 0` did not work.  It got stuck; however, I know in the debugger that a process does eventually get spawned, but only once the body of the test method returns, so something is weird about the thread switching.  Any other ideas how to test it besides a loop?

0

Please sign in to leave a comment.