diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfiguration.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfiguration.java index 4b485484e5ba..ab3e8f7aa3e6 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfiguration.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfiguration.java @@ -135,6 +135,12 @@ public abstract class TestDiscoveryConfiguration extends JavaTestConfigurationBa } } + @Override + public void setModule(Module module) { + super.setModule(module); + myDelegate.setModule(module); + } + @Override public void writeExternal(Element element) throws WriteExternalException { myDelegate.writeExternal(element); diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java index 15899ae90efd..a9ebeb0e8391 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java @@ -18,12 +18,15 @@ package com.intellij.execution.testDiscovery; import com.intellij.codeInsight.TestFrameworks; import com.intellij.execution.JavaExecutionUtil; import com.intellij.execution.Location; +import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.actions.ConfigurationContext; import com.intellij.execution.configurations.ConfigurationType; +import com.intellij.execution.configurations.ModuleBasedConfiguration; import com.intellij.execution.junit.JavaRunConfigurationProducerBase; -import com.intellij.execution.testframework.TestSearchScope; import com.intellij.openapi.module.Module; -import com.intellij.openapi.util.Condition; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.registry.Registry; @@ -34,9 +37,13 @@ import com.intellij.psi.PsiMethod; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testIntegration.TestFramework; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashSet; import java.io.IOException; +import java.util.Arrays; import java.util.Collection; +import java.util.List; +import java.util.Set; public abstract class TestDiscoveryConfigurationProducer extends JavaRunConfigurationProducerBase { protected TestDiscoveryConfigurationProducer(ConfigurationType type) { @@ -57,18 +64,46 @@ public abstract class TestDiscoveryConfigurationProducer extends JavaRunConfigur final Pair position = getPosition(location); if (position != null) { try { - final Collection testsByMethodName = TestDiscoveryIndex - .getInstance(configuration.getProject()).getTestsByMethodName(position.first, position.second); - if (testsByMethodName == null || ContainerUtil.filter(testsByMethodName, s -> s.startsWith(configuration.getFrameworkPrefix())).isEmpty()) return false; - + final Project project = configuration.getProject(); + final TestDiscoveryIndex testDiscoveryIndex = TestDiscoveryIndex.getInstance(project); + final Collection testsByMethodName = testDiscoveryIndex.getTestsByMethodName(position.first, position.second); + if (testsByMethodName == null || + ContainerUtil.filter(testsByMethodName, s -> s.startsWith(configuration.getFrameworkPrefix())).isEmpty()) { + return false; + } + configuration.setPosition(position); + configuration.setName("Tests for " + StringUtil.getShortName(position.first) + "." + position.second); + + final RunnerAndConfigurationSettings template = + configurationContext.getRunManager().getConfigurationTemplate(getConfigurationFactory()); + final Module predefinedModule = ((ModuleBasedConfiguration)template.getConfiguration()).getConfigurationModule().getModule(); + if (predefinedModule != null) { + configuration.setModule(predefinedModule); + } + + //potentially this set won't be big, it reflects modules from where user starts his tests + final Collection modules = testDiscoveryIndex.getTestModulesByMethodName(position.first, position.second); + if (modules.isEmpty()) return true; + + final ModuleManager moduleManager = ModuleManager.getInstance(project); + final Set allModules = new HashSet<>(Arrays.asList(moduleManager.getModules())); + modules.stream() + .map(moduleManager::findModuleByName) + .filter(module -> module != null) + .forEach(module -> { + final List dependentModules = ModuleUtilCore.getAllDependentModules(module); + dependentModules.add(module); + allModules.retainAll(dependentModules); + }); + if (!allModules.isEmpty()) { + configuration.setModule(allModules.iterator().next()); + } + + return true; } catch (IOException e) { return false; } - configuration.setPosition(position); - configuration.setName("Tests for " + StringUtil.getShortName(position.first) + "." + position.second); - setupPackageConfiguration(configurationContext, configuration, TestSearchScope.MODULE_WITH_DEPENDENCIES); - return true; } return false; } diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java index eb4dff2eddf2..77c0f312114f 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java @@ -46,7 +46,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -160,7 +159,7 @@ public class TestDiscoveryExtension extends RunConfigurationExtension { if (testMethodTraces != null) { for (File testMethodTrace : testMethodTraces) { try { - coverageIndex.updateFromTestTrace(testMethodTrace); + coverageIndex.updateFromTestTrace(testMethodTrace, ((JavaTestConfigurationBase)configuration).getConfigurationModule().getModule()); FileUtil.delete(testMethodTrace); } catch (IOException e) { @@ -187,7 +186,8 @@ public class TestDiscoveryExtension extends RunConfigurationExtension { final File testMethodTrace = new File(tracesDirectory, className + "-" + methodName + ".tr"); if (testMethodTrace.exists()) { try { - coverageIndex.updateFromTestTrace(testMethodTrace); + coverageIndex.updateFromTestTrace(testMethodTrace, + ((JavaTestConfigurationBase)configuration).getConfigurationModule().getModule()); FileUtil.delete(testMethodTrace); } catch (IOException e) { diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java index 73e19855dfed..851d4fc3265a 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java @@ -18,10 +18,12 @@ package com.intellij.execution.testDiscovery; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.io.*; import gnu.trove.TIntArrayList; import gnu.trove.TIntHashSet; @@ -80,7 +82,9 @@ public class TestDiscoveryIndex implements ProjectComponent { final int testNameId = holder.myTestNameEnumerator.tryEnumerate(testName); if (testNameId == 0) return; - doUpdateFromDiff(holder, testNameId, null, holder.myTestNameToUsedClassesAndMethodMap.get(testNameId)); + doUpdateFromDiff(holder, testNameId, null, + holder.myTestNameToUsedClassesAndMethodMap.get(testNameId), + null); } catch (Throwable throwable) { thingsWentWrongLetsReinitialize(holder, throwable); } @@ -109,6 +113,31 @@ public class TestDiscoveryIndex implements ProjectComponent { } } + + public Collection getTestModulesByMethodName(@NotNull String classFQName, @NotNull String methodName) throws IOException { + synchronized (ourLock) { + Holder holder = null; + try { + holder = getHolder(); + final TIntArrayList list = holder.myTestNameToNearestModule.get( + createKey( + holder.myClassEnumerator.enumerate(classFQName), + holder.myMethodEnumerator.enumerate(methodName) + ) + ); + if (list == null) return Collections.emptyList(); + final ArrayList result = new ArrayList(list.size()); + for (int moduleNameId : list.toNativeArray()) { + result.add(holder.myModuleNameEnumerator.valueOf(moduleNameId)); + } + return result; + } catch (Throwable throwable) { + thingsWentWrongLetsReinitialize(holder, throwable); + return Collections.emptyList(); + } + } + } + private Holder getHolder() { Holder holder = myHolder; @@ -154,17 +183,19 @@ public class TestDiscoveryIndex implements ProjectComponent { public void projectClosed() { } - private static final int VERSION = 2; + private static final int VERSION = 3; private final class Holder { final PersistentHashMap myMethodQNameToTestNames; final PersistentHashMap> myTestNameToUsedClassesAndMethodMap; + final PersistentHashMap myTestNameToNearestModule; final PersistentStringEnumerator myClassEnumerator; final CachingEnumerator myClassEnumeratorCache; final PersistentStringEnumerator myMethodEnumerator; final CachingEnumerator myMethodEnumeratorCache; final PersistentStringEnumerator myTestNameEnumerator; - final List myConstructedDataFiles = new ArrayList(4); + final PersistentStringEnumerator myModuleNameEnumerator; + final List myConstructedDataFiles = new ArrayList(6); private ScheduledFuture myFlushingFuture; private boolean myDisposed; @@ -178,6 +209,8 @@ public class TestDiscoveryIndex implements ProjectComponent { final File classNameEnumeratorFile = new File(path + File.separator + "classNameEnumerator.data"); final File methodNameEnumeratorFile = new File(path + File.separator + "methodNameEnumerator.data"); final File testNameEnumeratorFile = new File(path + File.separator + "testNameEnumerator.data"); + final File moduleNameEnumeratorFile = new File(path + File.separator + "moduleNameEnumerator.data"); + final File testNameToNearestModuleFile = new File(path + File.separator + "testNameToNearestModule.data"); try { int version = readVersion(versionFile); @@ -190,9 +223,11 @@ public class TestDiscoveryIndex implements ProjectComponent { PersistentHashMap methodQNameToTestNames; PersistentHashMap> testNameToUsedClassesAndMethodMap; + PersistentHashMap testNameToNearestModule; PersistentStringEnumerator classNameEnumerator; PersistentStringEnumerator methodNameEnumerator; PersistentStringEnumerator testNameEnumerator; + PersistentStringEnumerator moduleNameEnumerator; int iterations = 0; @@ -214,12 +249,20 @@ public class TestDiscoveryIndex implements ProjectComponent { ); myConstructedDataFiles.add(testNameToUsedClassesAndMethodMap); + testNameToNearestModule = new PersistentHashMap(testNameToNearestModuleFile, + new MethodQNameSerializer(), + new TestNamesExternalizer()); + myConstructedDataFiles.add(testNameToNearestModule); + classNameEnumerator = new PersistentStringEnumerator(classNameEnumeratorFile); myConstructedDataFiles.add(classNameEnumerator); methodNameEnumerator = new PersistentStringEnumerator(methodNameEnumeratorFile); myConstructedDataFiles.add(methodNameEnumerator); + moduleNameEnumerator = new PersistentStringEnumerator(moduleNameEnumeratorFile); + myConstructedDataFiles.add(methodNameEnumerator); + testNameEnumerator = new PersistentStringEnumerator(testNameEnumeratorFile); myConstructedDataFiles.add(testNameEnumerator); @@ -242,9 +285,11 @@ public class TestDiscoveryIndex implements ProjectComponent { myMethodQNameToTestNames = methodQNameToTestNames; myTestNameToUsedClassesAndMethodMap = testNameToUsedClassesAndMethodMap; + myTestNameToNearestModule = testNameToNearestModule; myClassEnumerator = classNameEnumerator; myMethodEnumerator = methodNameEnumerator; myTestNameEnumerator = testNameEnumerator; + myModuleNameEnumerator = moduleNameEnumerator; myMethodEnumeratorCache = new CachingEnumerator(methodNameEnumerator, EnumeratorStringDescriptor.INSTANCE); myClassEnumeratorCache = new CachingEnumerator(classNameEnumerator, EnumeratorStringDescriptor.INSTANCE); @@ -416,14 +461,14 @@ public class TestDiscoveryIndex implements ProjectComponent { return val1.equals(val2); } } - - public void updateFromTestTrace(@NotNull File file) throws IOException { + + public void updateFromTestTrace(@NotNull File file, @Nullable Module module) throws IOException { int fileNameDotIndex = file.getName().lastIndexOf('.'); final String testName = fileNameDotIndex != -1 ? file.getName().substring(0, fileNameDotIndex) : file.getName(); - doUpdateFromTestTrace(file, testName); + doUpdateFromTestTrace(file, testName, module); } - private void doUpdateFromTestTrace(File file, final String testName) throws IOException { + private void doUpdateFromTestTrace(File file, final String testName, @Nullable Module module) throws IOException { synchronized (ourLock) { Holder holder = getHolder(); if (holder.myDisposed) return; @@ -432,7 +477,7 @@ public class TestDiscoveryIndex implements ProjectComponent { TIntObjectHashMap classData = loadClassAndMethodsMap(file, holder); TIntObjectHashMap previousClassData = holder.myTestNameToUsedClassesAndMethodMap.get(testNameId); - doUpdateFromDiff(holder, testNameId, classData, previousClassData); + doUpdateFromDiff(holder, testNameId, classData, previousClassData, module != null ? holder.myModuleNameEnumerator.enumerate(module.getName()) : null); } catch (Throwable throwable) { thingsWentWrongLetsReinitialize(holder, throwable); } @@ -442,7 +487,8 @@ public class TestDiscoveryIndex implements ProjectComponent { private void doUpdateFromDiff(Holder holder, final int testNameId, @Nullable TIntObjectHashMap classData, - @Nullable TIntObjectHashMap previousClassData) throws IOException { + @Nullable TIntObjectHashMap previousClassData, + @Nullable Integer moduleId) throws IOException { ValueDiff valueDiff = new ValueDiff(classData, previousClassData); if (valueDiff.hasRemovedDelta()) { @@ -471,6 +517,15 @@ public class TestDiscoveryIndex implements ProjectComponent { DataInputOutputUtil.writeINT(dataOutput, testNameId); } }); + if (moduleId != null) { + holder.myTestNameToNearestModule.appendData(createKey(classQName, methodName), + new PersistentHashMap.ValueDataAppender() { + @Override + public void append(DataOutput dataOutput) throws IOException { + DataInputOutputUtil.writeINT(dataOutput, moduleId); + } + }); + } } } }