test discovery: support affected test data discovery for local test discovery

This commit is contained in:
Dmitry Batkovich
2018-09-26 16:59:45 +03:00
parent 9144ece3be
commit 477eaf9b2f
10 changed files with 204 additions and 40 deletions
@@ -3,11 +3,14 @@ package com.intellij.execution.testDiscovery;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.rt.coverage.data.api.TestDiscoveryProtocolReader;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.MultiMap;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
class IdeaTestDiscoveryProtocolReader implements TestDiscoveryProtocolReader, TestDiscoveryProtocolReader.NameEnumeratorReader {
private static final Logger LOG = Logger.getInstance(IdeaTestDiscoveryProtocolReader.class);
@@ -87,6 +90,7 @@ class IdeaTestDiscoveryProtocolReader implements TestDiscoveryProtocolReader, Te
private final String myTestClassName = myTestExecutionNameEnumerator.get(testClassId);
private final String myTestMethodName = myTestExecutionNameEnumerator.get(testMethodId);
private final MultiMap<String, String> myUsedMethods = new MultiMap<>();
private String[] myUsedFiles = ArrayUtil.EMPTY_STRING_ARRAY;
private int myCurrentClassId;
@Override
@@ -112,12 +116,12 @@ class IdeaTestDiscoveryProtocolReader implements TestDiscoveryProtocolReader, Te
@Override
public void testDataProcessed() {
myIndex.updateTestData(myTestClassName, myTestMethodName, myUsedMethods, myModuleName, myFrameworkId);
myIndex.updateTestData(myTestClassName, myTestMethodName, myUsedMethods, myUsedFiles, myModuleName, myFrameworkId);
}
@Override
public void processAffectedFile(int[] ints) {
// todo[batkovich] update index
myUsedFiles = Arrays.stream(ints).mapToObj(id -> myTestExecutionNameEnumerator.get(id)).toArray(ArrayUtil.STRING_ARRAY_FACTORY::create);
}
};
}
@@ -27,8 +27,7 @@ public class IntellijTestDiscoveryProducer implements TestDiscoveryProducer {
@Override
public MultiMap<String, String> getDiscoveredTests(@NotNull Project project,
@NotNull List<Couple<String>> classesAndMethods,
byte frameworkId,
@NotNull List<String> filePaths) {
byte frameworkId) {
if (!ApplicationManager.getApplication().isInternal()) {
return MultiMap.empty();
}
@@ -76,7 +75,7 @@ public class IntellijTestDiscoveryProducer implements TestDiscoveryProducer {
@NotNull
@Override
public MultiMap<String, String> getDiscoveredTests(@NotNull Project project, @NotNull List<String> filePaths) {
public MultiMap<String, String> getDiscoveredTestsForFiles(@NotNull Project project, @NotNull List<String> filePaths, byte frameworkId) {
try {
return request(filePaths, s -> "\"" + s + "\"", "files");
}
@@ -6,7 +6,7 @@ import com.intellij.openapi.util.Couple;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.io.IOException;
import java.util.List;
public class LocalTestDiscoveryProducer implements TestDiscoveryProducer {
@@ -14,8 +14,7 @@ public class LocalTestDiscoveryProducer implements TestDiscoveryProducer {
@NotNull
public MultiMap<String, String> getDiscoveredTests(@NotNull Project project,
@NotNull List<Couple<String>> classesAndMethods,
byte frameworkId,
@NotNull List<String> filePaths) {
byte frameworkId) {
MultiMap<String, String> result = new MultiMap<>();
TestDiscoveryIndex instance = TestDiscoveryIndex.getInstance(project);
classesAndMethods.forEach(couple -> result.putAllValues(couple.second == null ?
@@ -24,20 +23,25 @@ public class LocalTestDiscoveryProducer implements TestDiscoveryProducer {
return result;
}
@NotNull
@Override
public MultiMap<String, String> getDiscoveredTestsForFiles(@NotNull Project project, @NotNull List<String> paths, byte frameworkId) {
MultiMap<String, String> result = new MultiMap<>();
TestDiscoveryIndex instance = TestDiscoveryIndex.getInstance(project);
for (String path : paths) {
result.putAllValues(instance.getTestsByFile(path, frameworkId));
}
return result;
}
@NotNull
@Override
public List<String> getAffectedFilePaths(@NotNull Project project, @NotNull List<String> testFqns) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public boolean isRemote() {
return false;
}
@NotNull
@Override
public MultiMap<String, String> getDiscoveredTests(@NotNull Project project, @NotNull List<String> filePaths) {
return MultiMap.empty();
}
@NotNull
@Override
public List<String> getAffectedFilePaths(@NotNull Project project, @NotNull List<String> testFqns) {
return Collections.emptyList();
}
}
@@ -85,6 +85,7 @@ public class TestDiscoveryExtension extends RunConfigurationExtension {
if (agentPath == null) return;
params.getVMParametersList().add("-javaagent:" + agentPath);
TestDiscoveryDataSocketListener listener = tryInstallSocketListener(configuration);
params.getVMParametersList().addProperty(SocketTestDiscoveryProtocolDataListener.DATA_VERSION, String.valueOf(3));
if (listener != null) {
params.getVMParametersList().addProperty(SocketTestDiscoveryProtocolDataListener.PORT_PROP, Integer.toString(listener.getPort()));
params.getVMParametersList().addProperty(SocketTestDiscoveryProtocolDataListener.HOST_PROP, "127.0.0.1");
@@ -56,6 +56,13 @@ public class TestDiscoveryIndex implements Disposable {
});
}
@NotNull
public MultiMap<String, String> getTestsByFile(String relativePath, byte frameworkId) {
MultiMap<String, String> map = executeUnderLock(holder -> holder.getTestsByFile(relativePath, frameworkId));
return map == null ? MultiMap.empty() : map;
}
@NotNull
public MultiMap<String, String> getTestsByClassName(@NotNull String classFQName, byte frameworkId) {
MultiMap<String, String> map = executeUnderLock(holder -> holder.getTestsByClassName(classFQName, frameworkId));
@@ -88,10 +95,11 @@ public class TestDiscoveryIndex implements Disposable {
public void updateTestData(@NotNull String testClassName,
@NotNull String testMethodName,
@NotNull MultiMap<String, String> usedMethods,
@NotNull String[] usedFiles,
@Nullable String moduleName,
byte frameworkId) {
executeUnderLock(holder -> {
holder.updateTestData(testClassName, testMethodName, usedMethods, moduleName, frameworkId);
holder.updateTestData(testClassName, testMethodName, usedMethods, usedFiles, moduleName, frameworkId);
return null;
});
}
@@ -29,13 +29,14 @@ public interface TestDiscoveryProducer {
@NotNull
MultiMap<String, String> getDiscoveredTests(@NotNull Project project,
@NotNull List<Couple<String>> classesAndMethods,
byte frameworkId,
@NotNull List<String> filePaths);
boolean isRemote();
byte frameworkId);
@NotNull
MultiMap<String, String> getDiscoveredTests(@NotNull Project project, @NotNull List<String> filePaths);
MultiMap<String, String> getDiscoveredTestsForFiles(@NotNull Project project,
@NotNull List<String> paths,
byte frameworkId);
boolean isRemote();
static void consumeDiscoveredTests(@NotNull Project project,
@NotNull List<Couple<String>> classesAndMethods,
@@ -51,8 +52,8 @@ public interface TestDiscoveryProducer {
};
for (TestDiscoveryProducer producer : EP.getExtensions()) {
for (Map.Entry<String, Collection<String>> entry : ContainerUtil.concat(
producer.getDiscoveredTests(project, classesAndMethods, frameworkId, filePaths).entrySet(),
producer.getDiscoveredTests(project, filePaths).entrySet())) {
producer.getDiscoveredTests(project, classesAndMethods, frameworkId).entrySet(),
producer.getDiscoveredTestsForFiles(project, filePaths, frameworkId).entrySet())) {
String className = entry.getKey();
for (String methodRawName : entry.getValue()) {
if (!visitedTests.get(className).contains(methodRawName)) {
@@ -68,6 +69,7 @@ public interface TestDiscoveryProducer {
@NotNull
List<String> getAffectedFilePaths(@NotNull Project project, @NotNull List<String> testFqns) throws IOException;
//TODO use it [Dmitry Batkovich]
static void consumeAffectedPaths(@NotNull Project project, @NotNull List<String> testFqns, @NotNull Consumer<? super String> pathsConsumer) throws IOException {
for (TestDiscoveryProducer extension : EP.getExtensions()) {
for (String path : extension.getAffectedFilePaths(project, testFqns)) {
@@ -27,25 +27,29 @@ public final class DiscoveredTestDataHolder {
private static final Logger LOG = Logger.getInstance(DiscoveredTestDataHolder.class);
private final DiscoveredTestsIndex myDiscoveredTestsIndex;
private final TestFilesIndex myTestFilesIndex;
private final TestModuleIndex myTestModuleIndex;
private final PersistentStringEnumerator myClassEnumerator;
private final PersistentStringEnumerator myMethodEnumerator;
private final PersistentStringEnumerator myPathEnumerator;
private final PersistentEnumeratorDelegate<TestId> myTestEnumerator;
private final PersistentObjectSeq myConstructedDataFiles = new PersistentObjectSeq();
private boolean myDisposed;
private final Disposable myDisposable = Disposer.newDisposable();
static final int VERSION = 9;
static final int VERSION = 10;
public DiscoveredTestDataHolder(@NotNull Path basePath) {
final Path versionFile = getVersionFile(basePath);
PathKt.createDirectories(basePath);
final File discoveredTestsIndexFile = basePath.resolve("discoveredTests.index").toFile();
final File testFilesIndexFile = basePath.resolve("testFiles.index").toFile();
final File classNameEnumeratorFile = basePath.resolve("className.enum").toFile();
final File methodNameEnumeratorFile = basePath.resolve("methodName.enum").toFile();
final File pathEnumeratorFile = basePath.resolve("path.enum").toFile();
final File testNameEnumeratorFile = basePath.resolve("testName.enum").toFile();
try {
@@ -59,9 +63,11 @@ public final class DiscoveredTestDataHolder {
}
DiscoveredTestsIndex discoveredTestsIndex;
TestFilesIndex testFilesIndex;
TestModuleIndex testModuleIndex;
PersistentStringEnumerator classNameEnumerator;
PersistentStringEnumerator methodEnumerator;
PersistentStringEnumerator pathEnumerator;
PersistentEnumeratorDelegate<TestId> testEnumerator;
int iterations = 0;
@@ -73,6 +79,9 @@ public final class DiscoveredTestDataHolder {
discoveredTestsIndex = new DiscoveredTestsIndex(discoveredTestsIndexFile);
myConstructedDataFiles.add(discoveredTestsIndex);
testFilesIndex = new TestFilesIndex(testFilesIndexFile);
myConstructedDataFiles.add(testFilesIndex);
testModuleIndex = new TestModuleIndex(basePath, myConstructedDataFiles);
classNameEnumerator = new PersistentStringEnumerator(classNameEnumeratorFile, true);
@@ -81,6 +90,9 @@ public final class DiscoveredTestDataHolder {
methodEnumerator = new PersistentStringEnumerator(methodNameEnumeratorFile, true);
myConstructedDataFiles.add(methodEnumerator);
pathEnumerator = new PersistentStringEnumerator(pathEnumeratorFile, true);
myConstructedDataFiles.add(pathEnumerator);
testEnumerator = new PersistentEnumeratorDelegate<>(testNameEnumeratorFile, TestId.DESCRIPTOR, 1024 * 4);
myConstructedDataFiles.add(testEnumerator);
@@ -102,9 +114,11 @@ public final class DiscoveredTestDataHolder {
}
myDiscoveredTestsIndex = discoveredTestsIndex;
myTestFilesIndex = testFilesIndex;
myTestModuleIndex = testModuleIndex;
myClassEnumerator = classNameEnumerator;
myMethodEnumerator = methodEnumerator;
myPathEnumerator = pathEnumerator;
myTestEnumerator = testEnumerator;
LowMemoryWatcher.register(() -> myConstructedDataFiles.flush(), myDisposable);
@@ -114,7 +128,6 @@ public final class DiscoveredTestDataHolder {
}
}
private static void writeVersion(@NotNull Path versionFile) throws IOException {
try (final DataOutputStream versionOut = new DataOutputStream(PathKt.outputStream(versionFile))) {
DataInputOutputUtil.writeINT(versionOut, VERSION);
@@ -170,6 +183,7 @@ public final class DiscoveredTestDataHolder {
public void updateTestData(@NotNull String testClassName,
@NotNull String testMethodName,
@NotNull MultiMap<String, String> usedMethods,
@NotNull String[] usedFiles,
@Nullable String moduleName,
byte frameworkId) throws IOException {
@@ -182,10 +196,35 @@ public final class DiscoveredTestDataHolder {
methodIds.add(myMethodEnumerator.enumerate(methodName));
}
}
myDiscoveredTestsIndex.update(testNameId, new DiscoveredTestsIndex.UsedMethods(result)).compute();
Map<Integer, Void> usedVirtualFileIds = new HashMap<>();
for (String file : usedFiles) {
int fileId = myPathEnumerator.enumerate(file);
usedVirtualFileIds.put(fileId, null);
}
UsedSources usedSources = new UsedSources(result, usedVirtualFileIds);
myDiscoveredTestsIndex.update(testNameId, usedSources).compute();
myTestFilesIndex.update(testNameId, usedSources).compute();
myTestModuleIndex.appendModuleData(testNameId, moduleName);
}
@NotNull
public MultiMap<String, String> getTestsByFile(@NotNull String relativePath, byte frameworkId) throws IOException {
int fileId = myPathEnumerator.tryEnumerate(relativePath);
if (fileId == 0) return MultiMap.empty();
try {
MultiMap<String, String> result = new MultiMap<>();
IOException[] exception = {null};
myTestFilesIndex.getData(fileId).forEach((testId, v) -> consumeDiscoveredTest(testId, frameworkId, result, exception));
if (exception[0] != null) throw exception[0];
return result;
}
catch (StorageException e) {
throw new IOException(e);
}
}
@NotNull
public MultiMap<String, String> getTestsByClassName(@NotNull String classFQName, byte frameworkId) throws IOException {
int classId = myClassEnumerator.tryEnumerate(classFQName);
@@ -15,9 +15,8 @@ import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
public class DiscoveredTestsIndex extends MapReduceIndex<Integer, TIntArrayList, DiscoveredTestsIndex.UsedMethods> {
public class DiscoveredTestsIndex extends MapReduceIndex<Integer, TIntArrayList, UsedSources> {
protected DiscoveredTestsIndex(@NotNull File file) throws IOException {
super(INDEX_EXTENSION, new MyIndexStorage(file), new MyForwardIndex() {
@NotNull
@@ -54,7 +53,7 @@ public class DiscoveredTestsIndex extends MapReduceIndex<Integer, TIntArrayList,
}
}
private static final IndexExtension<Integer, TIntArrayList, UsedMethods> INDEX_EXTENSION = new IndexExtension<Integer, TIntArrayList, UsedMethods>() {
private static final IndexExtension<Integer, TIntArrayList, UsedSources> INDEX_EXTENSION = new IndexExtension<Integer, TIntArrayList, UsedSources>() {
@NotNull
@Override
public IndexId<Integer, TIntArrayList> getName() {
@@ -63,7 +62,7 @@ public class DiscoveredTestsIndex extends MapReduceIndex<Integer, TIntArrayList,
@NotNull
@Override
public DataIndexer<Integer, TIntArrayList, UsedMethods> getIndexer() {return inputData -> inputData.myTestUsedMethods;}
public DataIndexer<Integer, TIntArrayList, UsedSources> getIndexer() {return inputData -> inputData.myUsedMethods;}
@NotNull
@Override
@@ -93,9 +92,4 @@ public class DiscoveredTestsIndex extends MapReduceIndex<Integer, TIntArrayList,
return getInput(testId) != null;
}
}
static class UsedMethods {
private final Map<Integer, TIntArrayList> myTestUsedMethods;
UsedMethods(Map<Integer, TIntArrayList> methods) {myTestUsedMethods = methods;}
}
}
@@ -0,0 +1,95 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.testDiscovery.indices;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.util.indexing.DataIndexer;
import com.intellij.util.indexing.IndexExtension;
import com.intellij.util.indexing.IndexId;
import com.intellij.util.indexing.impl.*;
import com.intellij.util.io.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
public class TestFilesIndex extends MapReduceIndex<Integer, Void, UsedSources> {
protected TestFilesIndex(@NotNull File file) throws IOException {
super(INDEX_EXTENSION, new MyIndexStorage(file), new MyForwardIndex() {
@NotNull
@Override
public PersistentHashMap<Integer, Collection<Integer>> createMap() throws IOException {
return new PersistentHashMap<>(new File(file, "forward.idx"), EnumeratorIntegerDescriptor.INSTANCE,
new IntCollectionDataExternalizer());
}
});
}
@Override
public void checkCanceled() {
ProgressManager.checkCanceled();
}
@Override
protected void requestRebuild(@NotNull Throwable e) {
//TODO index corrupted
}
@Nullable
Collection<Integer> getTestDataFor(int testId) throws IOException {
return ((MyForwardIndex)myForwardIndex).containsDataFrom(testId);
}
private static class MyIndexStorage extends MapIndexStorage<Integer, Void> {
protected MyIndexStorage(@NotNull File storageFile) throws IOException {
super(storageFile, EnumeratorIntegerDescriptor.INSTANCE, VoidDataExternalizer.INSTANCE, 4 * 1024, false);
}
@Override
protected void checkCanceled() {
ProgressManager.checkCanceled();
}
}
private static final IndexExtension<Integer, Void, UsedSources> INDEX_EXTENSION = new IndexExtension<Integer, Void, UsedSources>() {
@NotNull
@Override
public IndexId<Integer, Void> getName() {
return IndexId.create("jvm.discovered.test.files");
}
@NotNull
@Override
public DataIndexer<Integer, Void, UsedSources> getIndexer() {return inputData -> inputData.myUsedFiles;}
@NotNull
@Override
public KeyDescriptor<Integer> getKeyDescriptor() {
return EnumeratorIntegerDescriptor.INSTANCE;
}
@NotNull
@Override
public DataExternalizer<Void> getValueExternalizer() {
return VoidDataExternalizer.INSTANCE;
}
@Override
public int getVersion() {
return DiscoveredTestDataHolder.VERSION;
}
};
private abstract static class MyForwardIndex extends KeyCollectionBasedForwardIndex<Integer, Void> {
protected MyForwardIndex() throws IOException {
super(INDEX_EXTENSION);
}
@Nullable
public Collection<Integer> containsDataFrom(int testId) throws IOException {
return getInput(testId);
}
}
}
@@ -0,0 +1,18 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.testDiscovery.indices;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
class UsedSources {
@NotNull
final Map<Integer, TIntArrayList> myUsedMethods;
@NotNull
final Map<Integer, Void> myUsedFiles;
UsedSources(@NotNull Map<Integer, TIntArrayList> methods, @NotNull Map<Integer, Void> files) {
myUsedMethods = methods;
myUsedFiles = files;
}
}