mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
test discovery: rework local indices
This commit is contained in:
+2
-9
@@ -8,8 +8,6 @@ import gnu.trove.TIntObjectHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
class IdeaTestDiscoveryProtocolReader implements TestDiscoveryProtocolReader, TestDiscoveryProtocolReader.NameEnumeratorReader {
|
||||
private static final Logger LOG = Logger.getInstance(IdeaTestDiscoveryProtocolReader.class);
|
||||
|
||||
@@ -42,7 +40,7 @@ class IdeaTestDiscoveryProtocolReader implements TestDiscoveryProtocolReader, Te
|
||||
public MetadataReader createMetadataReader() {
|
||||
return new MetadataReader() {
|
||||
@Override
|
||||
public void processMetadataEntry(String s, String s1) {
|
||||
public void processMetadataEntry(String k, String v) {
|
||||
// do nothing
|
||||
}
|
||||
};
|
||||
@@ -85,12 +83,7 @@ class IdeaTestDiscoveryProtocolReader implements TestDiscoveryProtocolReader, Te
|
||||
|
||||
@Override
|
||||
public void testDataProcessed() {
|
||||
try {
|
||||
myIndex.updateFromData(myTestClassName, myTestMethodName, myUsedMethods, myModuleName, myFrameworkId);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
myIndex.updateTestData(myTestClassName, myTestMethodName, myUsedMethods, myModuleName, myFrameworkId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+1
-7
@@ -19,13 +19,7 @@ public class LocalTestDiscoveryProducer implements TestDiscoveryProducer {
|
||||
@NotNull String classFQName,
|
||||
@NotNull String methodName,
|
||||
byte frameworkId) {
|
||||
try {
|
||||
return TestDiscoveryIndex.getInstance(project).getTestsByMethodName(classFQName, methodName, frameworkId);
|
||||
}
|
||||
catch (IOException e) {
|
||||
TestDiscoveryProducer.LOG.error(e);
|
||||
return MultiMap.empty();
|
||||
}
|
||||
return TestDiscoveryIndex.getInstance(project).getTestsByMethodName(classFQName, methodName, frameworkId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+27
-106
@@ -1,6 +1,7 @@
|
||||
// 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;
|
||||
|
||||
import com.intellij.execution.testDiscovery.indices.DiscoveredTestDataHolder;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -9,8 +10,6 @@ import com.intellij.openapi.startup.StartupManager;
|
||||
import com.intellij.util.ThrowableConvertor;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.io.PathKt;
|
||||
import gnu.trove.TIntArrayList;
|
||||
import gnu.trove.TIntObjectHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -19,13 +18,10 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Maxim.Mossienko on 7/9/2015.
|
||||
*/
|
||||
public class TestDiscoveryIndex implements Disposable {
|
||||
static final Logger LOG = Logger.getInstance(TestDiscoveryIndex.class);
|
||||
|
||||
private volatile TestInfoHolder myHolder;
|
||||
private volatile DiscoveredTestDataHolder myHolder;
|
||||
private final Object myLock = new Object();
|
||||
private final Path myBasePath;
|
||||
|
||||
@@ -47,94 +43,34 @@ public class TestDiscoveryIndex implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTestTrace(@NotNull String testClassName, @NotNull String testMethodName, byte frameworkId) throws IOException {
|
||||
Boolean result = withTestDataHolder(localHolder -> {
|
||||
TestInfoHolder.TestId testId = localHolder.createTestId(testClassName, testMethodName, frameworkId);
|
||||
final int testNameId = localHolder.myTestEnumerator.tryEnumerate(testId);
|
||||
return testNameId != 0 && localHolder.myTestNameToUsedClassesAndMethodMap.get(testNameId) != null;
|
||||
});
|
||||
public boolean hasTestTrace(@NotNull String testClassName, @NotNull String testMethodName, byte frameworkId) {
|
||||
Boolean result = executeUnderLock(holder -> holder.hasTestTrace(testClassName, testMethodName, frameworkId));
|
||||
return result == Boolean.TRUE;
|
||||
}
|
||||
|
||||
public void removeTestTrace(@NotNull String testClassName, @NotNull String testMethodName, byte frameworkId) throws IOException {
|
||||
withTestDataHolder(localHolder -> {
|
||||
TestInfoHolder.TestId testId = localHolder.createTestId(testClassName, testMethodName, frameworkId);
|
||||
final int testNameId = localHolder.myTestEnumerator.tryEnumerate(testId);
|
||||
if (testNameId != 0) {
|
||||
localHolder.doUpdateFromDiff(testNameId, null,
|
||||
localHolder.myTestNameToUsedClassesAndMethodMap.get(testNameId),
|
||||
null);
|
||||
}
|
||||
public void removeTestTrace(@NotNull String testClassName, @NotNull String testMethodName, byte frameworkId) {
|
||||
executeUnderLock(holder -> {
|
||||
holder.removeTestTrace(testClassName, testMethodName, frameworkId);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MultiMap<String, String> getTestsByMethodName(@NotNull String classFQName, @NotNull String methodName, byte frameworkId) throws IOException {
|
||||
MultiMap<String, String> map = withTestDataHolder(new ThrowableConvertor<TestInfoHolder, MultiMap<String, String>, IOException>() {
|
||||
@Override
|
||||
public MultiMap<String, String> convert(TestInfoHolder localHolder) throws IOException {
|
||||
Collection<TestInfoHolder.TestId> ids = getTestIdsByMethod(localHolder);
|
||||
if (ids.isEmpty()) return MultiMap.empty();
|
||||
|
||||
MultiMap<String, String> result = new MultiMap<>();
|
||||
for (TestInfoHolder.TestId id : ids) {
|
||||
if (id.getFrameworkId() == frameworkId) {
|
||||
result.putValue(localHolder.myClassEnumerator.valueOf(id.getClassId()),
|
||||
localHolder.myMethodEnumerator.valueOf(id.getMethodId()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Collection<TestInfoHolder.TestId> getTestIdsByMethod(TestInfoHolder localHolder) throws IOException {
|
||||
final TIntArrayList localList = localHolder.myMethodQNameToTestNames.get(
|
||||
TestInfoHolder.createKey(
|
||||
localHolder.myClassEnumerator.enumerate(classFQName),
|
||||
localHolder.myMethodEnumerator.enumerate(methodName)
|
||||
)
|
||||
);
|
||||
if (localList == null) return Collections.emptyList();
|
||||
|
||||
final ArrayList<TestInfoHolder.TestId> result = new ArrayList<>(localList.size());
|
||||
for (int testNameId : localList.toNativeArray()) {
|
||||
if (testNameId < 0) {
|
||||
continue;
|
||||
}
|
||||
result.add(localHolder.myTestEnumerator.valueOf(testNameId));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
public MultiMap<String, String> getTestsByMethodName(@NotNull String classFQName, @NotNull String methodName, byte frameworkId) {
|
||||
MultiMap<String, String> map = executeUnderLock(holder -> holder.getTestsByMethodName(classFQName, methodName, frameworkId));
|
||||
return map == null ? MultiMap.empty() : map;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<String> getTestModulesByMethodName(@NotNull String classFQName, @NotNull String methodName, byte frameworkId) throws IOException {
|
||||
List<String> modules = withTestDataHolder(localHolder -> {
|
||||
final TIntArrayList list = localHolder.myTestNameToNearestModule.get(
|
||||
TestInfoHolder.createKey(
|
||||
localHolder.myClassEnumerator.enumerate(classFQName),
|
||||
localHolder.myMethodEnumerator.enumerate(methodName)
|
||||
)
|
||||
);
|
||||
if (list == null) return Collections.emptyList();
|
||||
final ArrayList<String> result = new ArrayList<>(list.size());
|
||||
for (int moduleNameId : list.toNativeArray()) {
|
||||
final TestInfoHolder.ModuleId moduleNameWithPrefix = localHolder.myModuleEnumerator.valueOf(moduleNameId);
|
||||
if (moduleNameWithPrefix != null && moduleNameWithPrefix.getFrameworkId() == frameworkId) {
|
||||
result.add(moduleNameWithPrefix.getModuleName());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
public Collection<String> getTestModulesByMethodName(@NotNull String classFQName, @NotNull String methodName, byte frameworkId) {
|
||||
Collection<String> modules = executeUnderLock(holder -> holder.getTestModulesByMethodName(classFQName, methodName, frameworkId));
|
||||
return modules == null ? Collections.emptySet() : modules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
synchronized (myLock) {
|
||||
TestInfoHolder holder = myHolder;
|
||||
DiscoveredTestDataHolder holder = myHolder;
|
||||
if (holder != null) {
|
||||
holder.dispose();
|
||||
myHolder = null;
|
||||
@@ -142,58 +78,43 @@ public class TestDiscoveryIndex implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateFromData(@NotNull String testClassName,
|
||||
public void updateTestData(@NotNull String testClassName,
|
||||
@NotNull String testMethodName,
|
||||
@NotNull MultiMap<String, String> usedMethods,
|
||||
@Nullable String moduleName,
|
||||
byte frameworkId) throws IOException {
|
||||
withTestDataHolder(localHolder -> {
|
||||
final int testNameId = localHolder.myTestEnumerator.enumerate(localHolder.createTestId(testClassName, testMethodName, frameworkId));
|
||||
TIntObjectHashMap<TIntArrayList> result = new TIntObjectHashMap<>();
|
||||
for (Map.Entry<String, Collection<String>> e : usedMethods.entrySet()) {
|
||||
int classId = localHolder.myClassEnumerator.enumerate(e.getKey());
|
||||
TIntArrayList methodIds = new TIntArrayList();
|
||||
result.put(classId, methodIds);
|
||||
for (String methodName : e.getValue()) {
|
||||
methodIds.add(localHolder.myMethodEnumerator.enumerate(methodName));
|
||||
}
|
||||
}
|
||||
TIntObjectHashMap<TIntArrayList> previousClassData = localHolder.myTestNameToUsedClassesAndMethodMap.get(testNameId);
|
||||
localHolder.doUpdateFromDiff(testNameId, result, previousClassData, moduleName != null ? localHolder.myModuleEnumerator.enumerate(new TestInfoHolder.ModuleId(moduleName, frameworkId)) : null);
|
||||
byte frameworkId) {
|
||||
executeUnderLock(holder -> {
|
||||
holder.updateTestData(testClassName, testMethodName, usedMethods, moduleName, frameworkId);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private TestInfoHolder getHolder() {
|
||||
TestInfoHolder holder = myHolder;
|
||||
private DiscoveredTestDataHolder getHolder() {
|
||||
DiscoveredTestDataHolder holder = myHolder;
|
||||
|
||||
if (holder == null) {
|
||||
synchronized (myLock) {
|
||||
holder = myHolder;
|
||||
if (holder == null && myBasePath != null) myHolder = holder = new TestInfoHolder(myBasePath, false, myLock);
|
||||
if (holder == null && myBasePath != null) {
|
||||
myHolder = holder = new DiscoveredTestDataHolder(myBasePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
|
||||
private void thingsWentWrongLetsReinitialize(@Nullable TestInfoHolder holder, Throwable throwable) throws IOException {
|
||||
LOG.error("Unexpected problem", throwable);
|
||||
if (holder != null) holder.dispose();
|
||||
PathKt.delete(TestInfoHolder.getVersionFile(myBasePath));
|
||||
|
||||
myHolder = null;
|
||||
if (throwable instanceof IOException) throw (IOException)throwable;
|
||||
}
|
||||
|
||||
private <R> R withTestDataHolder(ThrowableConvertor<TestInfoHolder, R, IOException> action) throws IOException {
|
||||
private <R> R executeUnderLock(ThrowableConvertor<DiscoveredTestDataHolder, R, IOException> action) {
|
||||
synchronized (myLock) {
|
||||
TestInfoHolder holder = getHolder();
|
||||
DiscoveredTestDataHolder holder = getHolder();
|
||||
if (holder == null || holder.isDisposed()) return null;
|
||||
try {
|
||||
return action.convert(holder);
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
thingsWentWrongLetsReinitialize(holder, throwable);
|
||||
LOG.error("Unexpected problem", throwable);
|
||||
holder.dispose();
|
||||
PathKt.delete(myBasePath);
|
||||
myHolder = null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+4
-7
@@ -62,14 +62,11 @@ public class TestDiscoverySearchHelper {
|
||||
if (containingClass != null && containingClass.getParent() == psiFile) {
|
||||
final String classQualifiedName = containingClass.getQualifiedName();
|
||||
final String changedMethodName = changedMethod.getName();
|
||||
try {
|
||||
if (classQualifiedName != null &&
|
||||
(position == null && TestFrameworks.detectFramework(containingClass) != null ||
|
||||
position != null && !discoveryIndex.hasTestTrace(classQualifiedName, changedMethodName, frameworkId))) {
|
||||
patterns.add(classQualifiedName + "," + changedMethodName);
|
||||
}
|
||||
if (classQualifiedName != null &&
|
||||
(position == null && TestFrameworks.detectFramework(containingClass) != null ||
|
||||
position != null && !discoveryIndex.hasTestTrace(classQualifiedName, changedMethodName, frameworkId))) {
|
||||
patterns.add(classQualifiedName + "," + changedMethodName);
|
||||
}
|
||||
catch (IOException ignore) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,557 +0,0 @@
|
||||
// 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;
|
||||
|
||||
import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
|
||||
import com.intellij.util.io.*;
|
||||
import com.intellij.util.io.DataOutputStream;
|
||||
import gnu.trove.TIntArrayList;
|
||||
import gnu.trove.TIntHashSet;
|
||||
import gnu.trove.TIntObjectHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
final class TestInfoHolder {
|
||||
final PersistentHashMap<Long, TIntArrayList> myMethodQNameToTestNames;
|
||||
final PersistentHashMap<Integer, TIntObjectHashMap<TIntArrayList>> myTestNameToUsedClassesAndMethodMap;
|
||||
final PersistentHashMap<Long, TIntArrayList> myTestNameToNearestModule;
|
||||
final PersistentStringEnumerator myClassEnumerator;
|
||||
final PersistentStringEnumerator myMethodEnumerator;
|
||||
final PersistentEnumeratorDelegate<TestId> myTestEnumerator;
|
||||
final PersistentEnumeratorDelegate<ModuleId> myModuleEnumerator;
|
||||
final List<PersistentEnumeratorDelegate> myConstructedDataFiles = new ArrayList<>(6);
|
||||
|
||||
private ScheduledFuture<?> myFlushingFuture;
|
||||
private boolean myDisposed;
|
||||
private final Object myLock;
|
||||
|
||||
private static final int VERSION = 7;
|
||||
|
||||
TestInfoHolder(@NotNull Path basePath, boolean readOnly, Object lock) {
|
||||
myLock = lock;
|
||||
final Path versionFile = getVersionFile(basePath);
|
||||
PathKt.createDirectories(basePath);
|
||||
final File methodQNameToTestNameFile = basePath.resolve("methodQNameToTestName.data").toFile();
|
||||
final File testNameToUsedClassesAndMethodMapFile = basePath.resolve("testToCalledMethodNames.data").toFile();
|
||||
final File classNameEnumeratorFile = basePath.resolve("classNameEnumerator.data").toFile();
|
||||
final File methodNameEnumeratorFile = basePath.resolve("methodNameEnumerator.data").toFile();
|
||||
final File testNameEnumeratorFile = basePath.resolve("testNameEnumerator.data").toFile();
|
||||
final File moduleNameEnumeratorFile = basePath.resolve("moduleNameEnumerator.data").toFile();
|
||||
final File testNameToNearestModuleFile = basePath.resolve("testNameToNearestModule.data").toFile();
|
||||
|
||||
try {
|
||||
int version = readVersion(versionFile);
|
||||
if (version != VERSION) {
|
||||
TestDiscoveryIndex.LOG.info(version != -1
|
||||
? "TestDiscoveryIndex was rewritten due to version change"
|
||||
: "TestDiscoveryIndex is not exist. Empty index is created");
|
||||
assert !readOnly;
|
||||
deleteAllIndexDataFiles(methodQNameToTestNameFile,
|
||||
testNameToUsedClassesAndMethodMapFile,
|
||||
classNameEnumeratorFile,
|
||||
methodNameEnumeratorFile,
|
||||
testNameEnumeratorFile, moduleNameEnumeratorFile,
|
||||
testNameToNearestModuleFile);
|
||||
|
||||
writeVersion(versionFile);
|
||||
}
|
||||
|
||||
PersistentHashMap<Long, TIntArrayList> methodQNameToTestNames;
|
||||
PersistentHashMap<Integer, TIntObjectHashMap<TIntArrayList>> testNameToUsedClassesAndMethodMap;
|
||||
PersistentHashMap<Long, TIntArrayList> testNameToNearestModule;
|
||||
PersistentStringEnumerator classNameEnumerator;
|
||||
PersistentStringEnumerator methodEnumerator;
|
||||
PersistentEnumeratorDelegate<TestId> testEnumerator;
|
||||
PersistentEnumeratorDelegate<ModuleId> moduleNameEnumerator;
|
||||
|
||||
int iterations = 0;
|
||||
|
||||
while (true) {
|
||||
++iterations;
|
||||
|
||||
try {
|
||||
methodQNameToTestNames = new PersistentHashMap<Long, TIntArrayList>(
|
||||
methodQNameToTestNameFile,
|
||||
MethodQNameSerializer.INSTANCE,
|
||||
new TestNamesExternalizer()
|
||||
) {
|
||||
@Override
|
||||
protected boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
};
|
||||
myConstructedDataFiles.add(methodQNameToTestNames);
|
||||
|
||||
testNameToUsedClassesAndMethodMap = new PersistentHashMap<Integer, TIntObjectHashMap<TIntArrayList>>(
|
||||
testNameToUsedClassesAndMethodMapFile,
|
||||
EnumeratorIntegerDescriptor.INSTANCE,
|
||||
new ClassesAndMethodsMapDataExternalizer()
|
||||
) {
|
||||
@Override
|
||||
protected boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
};
|
||||
myConstructedDataFiles.add(testNameToUsedClassesAndMethodMap);
|
||||
|
||||
testNameToNearestModule = new PersistentHashMap<>(testNameToNearestModuleFile,
|
||||
MethodQNameSerializer.INSTANCE,
|
||||
new TestNamesExternalizer());
|
||||
myConstructedDataFiles.add(testNameToNearestModule);
|
||||
|
||||
classNameEnumerator = new PersistentStringEnumerator(classNameEnumeratorFile, true);
|
||||
myConstructedDataFiles.add(classNameEnumerator);
|
||||
|
||||
methodEnumerator = new PersistentStringEnumerator(methodNameEnumeratorFile, true);
|
||||
myConstructedDataFiles.add(methodEnumerator);
|
||||
|
||||
moduleNameEnumerator = new PersistentEnumeratorDelegate<>(moduleNameEnumeratorFile, ModuleId.DESCRIPTOR, 64);
|
||||
myConstructedDataFiles.add(moduleNameEnumerator);
|
||||
|
||||
testEnumerator = new PersistentEnumeratorDelegate<>(testNameEnumeratorFile, TestId.DESCRIPTOR, 1024 * 4);
|
||||
myConstructedDataFiles.add(testEnumerator);
|
||||
|
||||
break;
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
TestDiscoveryIndex.LOG.info("TestDiscoveryIndex problem", throwable);
|
||||
closeAllConstructedFiles(true);
|
||||
myConstructedDataFiles.clear();
|
||||
|
||||
deleteAllIndexDataFiles(methodQNameToTestNameFile, testNameToUsedClassesAndMethodMapFile, classNameEnumeratorFile,
|
||||
methodNameEnumeratorFile,
|
||||
testNameEnumeratorFile, moduleNameEnumeratorFile, testNameToNearestModuleFile);
|
||||
// try another time
|
||||
}
|
||||
|
||||
if (iterations >= 3) {
|
||||
TestDiscoveryIndex.LOG.error("Unexpected circular initialization problem");
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
|
||||
myMethodQNameToTestNames = methodQNameToTestNames;
|
||||
myTestNameToUsedClassesAndMethodMap = testNameToUsedClassesAndMethodMap;
|
||||
myTestNameToNearestModule = testNameToNearestModule;
|
||||
myClassEnumerator = classNameEnumerator;
|
||||
myMethodEnumerator = methodEnumerator;
|
||||
myTestEnumerator = testEnumerator;
|
||||
myModuleEnumerator = moduleNameEnumerator;
|
||||
|
||||
myFlushingFuture = FlushingDaemon.everyFiveSeconds(() -> {
|
||||
synchronized (myLock) {
|
||||
if (myDisposed) {
|
||||
myFlushingFuture.cancel(false);
|
||||
return;
|
||||
}
|
||||
for (PersistentEnumeratorDelegate dataFile : myConstructedDataFiles) {
|
||||
if (dataFile.isDirty()) {
|
||||
dataFile.force();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeAllConstructedFiles(boolean ignoreCloseProblem) {
|
||||
for (Closeable closeable : myConstructedDataFiles) {
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
if (!ignoreCloseProblem) throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteAllIndexDataFiles(File... files) {
|
||||
for (File file : files) {
|
||||
IOUtil.deleteAllFilesStartingWith(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeVersion(@NotNull Path versionFile) throws IOException {
|
||||
try (final DataOutputStream versionOut = new DataOutputStream(PathKt.outputStream(versionFile))) {
|
||||
DataInputOutputUtil.writeINT(versionOut, VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
private static int readVersion(@NotNull Path versionFile) throws IOException {
|
||||
InputStream inputStream = PathKt.inputStreamIfExists(versionFile);
|
||||
if (inputStream == null) {
|
||||
return -1;
|
||||
}
|
||||
try (DataInputStream versionInput = new DataInputStream(inputStream)) {
|
||||
return DataInputOutputUtil.readINT(versionInput);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
assert Thread.holdsLock(myLock);
|
||||
try {
|
||||
closeAllConstructedFiles(false);
|
||||
}
|
||||
finally {
|
||||
myDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int REMOVED_MARKER = -1;
|
||||
|
||||
void doUpdateFromDiff(final int testNameId,
|
||||
@Nullable TIntObjectHashMap<TIntArrayList> classData,
|
||||
@Nullable TIntObjectHashMap<TIntArrayList> previousClassData,
|
||||
@Nullable Integer moduleId) throws IOException {
|
||||
ValueDiff valueDiff = new ValueDiff(classData, previousClassData);
|
||||
|
||||
if (valueDiff.hasRemovedDelta()) {
|
||||
for (int classQName : valueDiff.myRemovedClassData.keys()) {
|
||||
for (int methodName : valueDiff.myRemovedClassData.get(classQName).toNativeArray()) {
|
||||
myMethodQNameToTestNames.appendData(createKey(classQName, methodName),
|
||||
dataOutput -> {
|
||||
DataInputOutputUtil.writeINT(dataOutput, REMOVED_MARKER);
|
||||
DataInputOutputUtil.writeINT(dataOutput, testNameId);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (valueDiff.hasAddedDelta()) {
|
||||
for (int classQName : valueDiff.myAddedOrChangedClassData.keys()) {
|
||||
for (int methodName : valueDiff.myAddedOrChangedClassData.get(classQName).toNativeArray()) {
|
||||
myMethodQNameToTestNames.appendData(createKey(classQName, methodName),
|
||||
dataOutput -> DataInputOutputUtil.writeINT(dataOutput, testNameId));
|
||||
if (moduleId != null) {
|
||||
myTestNameToNearestModule.appendData(createKey(classQName, methodName),
|
||||
dataOutput -> DataInputOutputUtil.writeINT(dataOutput, moduleId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((valueDiff.hasAddedDelta() || valueDiff.hasRemovedDelta())) {
|
||||
if (classData != null) {
|
||||
myTestNameToUsedClassesAndMethodMap.put(testNameId, classData);
|
||||
}
|
||||
else {
|
||||
myTestNameToUsedClassesAndMethodMap.remove(testNameId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDisposed() {
|
||||
return myDisposed;
|
||||
}
|
||||
|
||||
public static boolean isValidPath(@NotNull Path path) {
|
||||
try {
|
||||
return readVersion(getVersionFile(path)) == VERSION;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestNamesExternalizer implements DataExternalizer<TIntArrayList> {
|
||||
public void save(@NotNull DataOutput dataOutput, TIntArrayList testNameIds) throws IOException {
|
||||
for (int testNameId : testNameIds.toNativeArray()) DataInputOutputUtil.writeINT(dataOutput, testNameId);
|
||||
}
|
||||
|
||||
public TIntArrayList read(@NotNull DataInput dataInput) throws IOException {
|
||||
TIntHashSet result = new TIntHashSet();
|
||||
|
||||
while (((InputStream)dataInput).available() > 0) {
|
||||
int id = DataInputOutputUtil.readINT(dataInput);
|
||||
if (REMOVED_MARKER == id) {
|
||||
id = DataInputOutputUtil.readINT(dataInput);
|
||||
if(!result.remove(id)) {
|
||||
result.add(-id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
return new TIntArrayList(result.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClassesAndMethodsMapDataExternalizer implements DataExternalizer<TIntObjectHashMap<TIntArrayList>> {
|
||||
public void save(@NotNull final DataOutput dataOutput, TIntObjectHashMap<TIntArrayList> classAndMethodsMap)
|
||||
throws IOException {
|
||||
DataInputOutputUtil.writeINT(dataOutput, classAndMethodsMap.size());
|
||||
final int[] classNameIds = classAndMethodsMap.keys();
|
||||
Arrays.sort(classNameIds);
|
||||
|
||||
int prevClassNameId = 0;
|
||||
for (int classNameId : classNameIds) {
|
||||
DataInputOutputUtil.writeINT(dataOutput, classNameId - prevClassNameId);
|
||||
TIntArrayList value = classAndMethodsMap.get(classNameId);
|
||||
DataInputOutputUtil.writeINT(dataOutput, value.size());
|
||||
|
||||
final int[] methodNameIds = value.toNativeArray();
|
||||
Arrays.sort(methodNameIds);
|
||||
int prevMethodNameId = 0;
|
||||
for (int methodNameId : methodNameIds) {
|
||||
DataInputOutputUtil.writeINT(dataOutput, methodNameId - prevMethodNameId);
|
||||
prevMethodNameId = methodNameId;
|
||||
}
|
||||
prevClassNameId = classNameId;
|
||||
}
|
||||
}
|
||||
|
||||
public TIntObjectHashMap<TIntArrayList> read(@NotNull DataInput dataInput) throws IOException {
|
||||
int numberOfClasses = DataInputOutputUtil.readINT(dataInput);
|
||||
TIntObjectHashMap<TIntArrayList> result = new TIntObjectHashMap<>();
|
||||
int prevClassNameId = 0;
|
||||
|
||||
while (numberOfClasses-- > 0) {
|
||||
int classNameId = DataInputOutputUtil.readINT(dataInput) + prevClassNameId;
|
||||
int numberOfMethods = DataInputOutputUtil.readINT(dataInput);
|
||||
TIntArrayList methodNameIds = new TIntArrayList(numberOfMethods);
|
||||
|
||||
int prevMethodNameId = 0;
|
||||
while (numberOfMethods-- > 0) {
|
||||
final int methodNameId = DataInputOutputUtil.readINT(dataInput) + prevMethodNameId;
|
||||
methodNameIds.add(methodNameId);
|
||||
prevMethodNameId = methodNameId;
|
||||
}
|
||||
|
||||
result.put(classNameId, methodNameIds);
|
||||
prevClassNameId = classNameId;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MethodQNameSerializer implements KeyDescriptor<Long> {
|
||||
public static final MethodQNameSerializer INSTANCE = new MethodQNameSerializer();
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, Long value) throws IOException {
|
||||
out.writeLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long read(@NotNull DataInput in) throws IOException {
|
||||
return in.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(Long value) {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(Long val1, Long val2) {
|
||||
return val1.equals(val2);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static Path getVersionFile(Path path) {
|
||||
return path.resolve("index.version");
|
||||
}
|
||||
|
||||
static long createKey(int classQName, int methodName) {
|
||||
return ((long)classQName << 32) | methodName;
|
||||
}
|
||||
|
||||
static class ValueDiff {
|
||||
final TIntObjectHashMap<TIntArrayList> myAddedOrChangedClassData;
|
||||
final TIntObjectHashMap<TIntArrayList> myRemovedClassData;
|
||||
|
||||
ValueDiff(@Nullable TIntObjectHashMap<TIntArrayList> classData, @Nullable TIntObjectHashMap<TIntArrayList> previousClassData) {
|
||||
TIntObjectHashMap<TIntArrayList> addedOrChangedClassData = classData;
|
||||
TIntObjectHashMap<TIntArrayList> removedClassData = previousClassData;
|
||||
|
||||
if (previousClassData != null && !previousClassData.isEmpty()) {
|
||||
removedClassData = new TIntObjectHashMap<>();
|
||||
addedOrChangedClassData = new TIntObjectHashMap<>();
|
||||
|
||||
if (classData != null) {
|
||||
for (int classQName : classData.keys()) {
|
||||
TIntArrayList currentMethods = classData.get(classQName);
|
||||
TIntArrayList previousMethods = previousClassData.get(classQName);
|
||||
|
||||
if (previousMethods == null) {
|
||||
addedOrChangedClassData.put(classQName, currentMethods);
|
||||
continue;
|
||||
}
|
||||
|
||||
final int[] previousMethodIds = previousMethods.toNativeArray();
|
||||
TIntHashSet previousMethodsSet = new TIntHashSet(previousMethodIds);
|
||||
final int[] currentMethodIds = currentMethods.toNativeArray();
|
||||
TIntHashSet currentMethodsSet = new TIntHashSet(currentMethodIds);
|
||||
currentMethodsSet.removeAll(previousMethodIds);
|
||||
previousMethodsSet.removeAll(currentMethodIds);
|
||||
|
||||
if (!currentMethodsSet.isEmpty()) {
|
||||
addedOrChangedClassData.put(classQName, new TIntArrayList(currentMethodsSet.toArray()));
|
||||
}
|
||||
if (!previousMethodsSet.isEmpty()) {
|
||||
removedClassData.put(classQName, new TIntArrayList(previousMethodsSet.toArray()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (classData != null) {
|
||||
for (int classQName : previousClassData.keys()) {
|
||||
if (classData.containsKey(classQName)) continue;
|
||||
|
||||
TIntArrayList previousMethods = previousClassData.get(classQName);
|
||||
removedClassData.put(classQName, previousMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
myAddedOrChangedClassData = addedOrChangedClassData;
|
||||
myRemovedClassData = removedClassData;
|
||||
}
|
||||
|
||||
public boolean hasRemovedDelta() {
|
||||
return myRemovedClassData != null && !myRemovedClassData.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasAddedDelta() {
|
||||
return myAddedOrChangedClassData != null && !myAddedOrChangedClassData.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
TestId createTestId(String className, String methodName, byte frameworkPrefix) throws IOException {
|
||||
return new TestId(myClassEnumerator.enumerate(className), myMethodEnumerator.enumerate(methodName), frameworkPrefix);
|
||||
}
|
||||
|
||||
static class ModuleId {
|
||||
@NotNull
|
||||
private final String myModuleName;
|
||||
private final byte myFrameworkId;
|
||||
|
||||
ModuleId(@NotNull String name, byte id) {
|
||||
myModuleName = name;
|
||||
myFrameworkId = id;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String getModuleName() {
|
||||
return myModuleName;
|
||||
}
|
||||
|
||||
byte getFrameworkId() {
|
||||
return myFrameworkId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ModuleId id = (ModuleId)o;
|
||||
return myFrameworkId == id.myFrameworkId &&
|
||||
Objects.equals(myModuleName, id.myModuleName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myModuleName, myFrameworkId);
|
||||
}
|
||||
|
||||
private static final KeyDescriptor<ModuleId> DESCRIPTOR = new KeyDescriptor<ModuleId>() {
|
||||
@Override
|
||||
public int getHashCode(ModuleId id) {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(ModuleId id1, ModuleId id2) {
|
||||
return id1.equals(id2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, ModuleId id) throws IOException {
|
||||
IOUtil.writeUTF(out, id.getModuleName());
|
||||
out.writeByte(id.getFrameworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleId read(@NotNull DataInput in) throws IOException {
|
||||
return new ModuleId(IOUtil.readUTF(in), in.readByte());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static class TestId {
|
||||
private final int myClassId;
|
||||
private final int myMethodId;
|
||||
private final byte myFrameworkId;
|
||||
|
||||
TestId(int classId, int methodId, byte id) {
|
||||
myClassId = classId;
|
||||
myMethodId = methodId;
|
||||
myFrameworkId = id;
|
||||
}
|
||||
|
||||
int getClassId() {
|
||||
return myClassId;
|
||||
}
|
||||
|
||||
int getMethodId() {
|
||||
return myMethodId;
|
||||
}
|
||||
|
||||
byte getFrameworkId() {
|
||||
return myFrameworkId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TestId id = (TestId)o;
|
||||
return myClassId == id.myClassId &&
|
||||
myMethodId == id.myMethodId &&
|
||||
myFrameworkId == id.myFrameworkId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myClassId, myMethodId, myFrameworkId);
|
||||
}
|
||||
|
||||
private static final KeyDescriptor<TestId> DESCRIPTOR = new KeyDescriptor<TestId>() {
|
||||
@Override
|
||||
public int getHashCode(TestId id) {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(TestId id1, TestId id2) {
|
||||
return id1.equals(id2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, TestId id) throws IOException {
|
||||
DataInputOutputUtil.writeINT(out, id.getClassId());
|
||||
DataInputOutputUtil.writeINT(out, id.getMethodId());
|
||||
out.writeByte(id.getFrameworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestId read(@NotNull DataInput in) throws IOException {
|
||||
return new TestId(DataInputOutputUtil.readINT(in), DataInputOutputUtil.readINT(in), in.readByte());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
// 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.Disposable;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.LowMemoryWatcher;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.indexing.StorageException;
|
||||
import com.intellij.util.indexing.ValueContainer;
|
||||
import com.intellij.util.io.*;
|
||||
import com.intellij.util.io.DataOutputStream;
|
||||
import gnu.trove.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
public final class DiscoveredTestDataHolder {
|
||||
private static final Logger LOG = Logger.getInstance(DiscoveredTestDataHolder.class);
|
||||
|
||||
private final DiscoveredTestsIndex myDiscoveredTestsIndex;
|
||||
private final TestModuleIndex myTestModuleIndex;
|
||||
|
||||
private final PersistentStringEnumerator myClassEnumerator;
|
||||
private final PersistentStringEnumerator myMethodEnumerator;
|
||||
private final PersistentEnumeratorDelegate<TestId> myTestEnumerator;
|
||||
private final PersistentObjectSeq myConstructedDataFiles = new PersistentObjectSeq();
|
||||
|
||||
private boolean myDisposed;
|
||||
private final Disposable myDisposable = Disposer.newDisposable();
|
||||
|
||||
static final int VERSION = 8;
|
||||
|
||||
public DiscoveredTestDataHolder(@NotNull Path basePath) {
|
||||
final Path versionFile = getVersionFile(basePath);
|
||||
PathKt.createDirectories(basePath);
|
||||
final File discoveredTestsIndexFile = basePath.resolve("discoveredTests.index").toFile();
|
||||
|
||||
final File classNameEnumeratorFile = basePath.resolve("className.enum").toFile();
|
||||
final File methodNameEnumeratorFile = basePath.resolve("methodName.enum").toFile();
|
||||
final File testNameEnumeratorFile = basePath.resolve("testName.enum").toFile();
|
||||
|
||||
try {
|
||||
int version = readVersion(versionFile);
|
||||
if (version != VERSION) {
|
||||
LOG.info(version != -1
|
||||
? "TestDiscoveryIndex was rewritten due to version change"
|
||||
: "TestDiscoveryIndex is not exist. Empty index is created");
|
||||
PathKt.delete(basePath);
|
||||
writeVersion(versionFile);
|
||||
}
|
||||
|
||||
DiscoveredTestsIndex discoveredTestsIndex;
|
||||
TestModuleIndex testModuleIndex;
|
||||
PersistentStringEnumerator classNameEnumerator;
|
||||
PersistentStringEnumerator methodEnumerator;
|
||||
PersistentEnumeratorDelegate<TestId> testEnumerator;
|
||||
|
||||
int iterations = 0;
|
||||
|
||||
while (true) {
|
||||
++iterations;
|
||||
|
||||
try {
|
||||
discoveredTestsIndex = new DiscoveredTestsIndex(discoveredTestsIndexFile);
|
||||
myConstructedDataFiles.add(discoveredTestsIndex);
|
||||
|
||||
testModuleIndex = new TestModuleIndex(basePath, myConstructedDataFiles);
|
||||
|
||||
classNameEnumerator = new PersistentStringEnumerator(classNameEnumeratorFile, true);
|
||||
myConstructedDataFiles.add(classNameEnumerator);
|
||||
|
||||
methodEnumerator = new PersistentStringEnumerator(methodNameEnumeratorFile, true);
|
||||
myConstructedDataFiles.add(methodEnumerator);
|
||||
|
||||
testEnumerator = new PersistentEnumeratorDelegate<>(testNameEnumeratorFile, TestId.DESCRIPTOR, 1024 * 4);
|
||||
myConstructedDataFiles.add(testEnumerator);
|
||||
|
||||
break;
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
LOG.info("TestDiscoveryIndex problem", throwable);
|
||||
myConstructedDataFiles.close(true);
|
||||
myConstructedDataFiles.clear();
|
||||
|
||||
PathKt.delete(basePath);
|
||||
// try another time
|
||||
}
|
||||
|
||||
if (iterations >= 3) {
|
||||
LOG.error("Unexpected circular initialization problem");
|
||||
assert false;
|
||||
}
|
||||
}
|
||||
|
||||
myDiscoveredTestsIndex = discoveredTestsIndex;
|
||||
myTestModuleIndex = testModuleIndex;
|
||||
myClassEnumerator = classNameEnumerator;
|
||||
myMethodEnumerator = methodEnumerator;
|
||||
myTestEnumerator = testEnumerator;
|
||||
|
||||
LowMemoryWatcher.register(() -> myConstructedDataFiles.flush(), myDisposable);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeVersion(@NotNull Path versionFile) throws IOException {
|
||||
try (final DataOutputStream versionOut = new DataOutputStream(PathKt.outputStream(versionFile))) {
|
||||
DataInputOutputUtil.writeINT(versionOut, VERSION);
|
||||
}
|
||||
}
|
||||
|
||||
private static int readVersion(@NotNull Path versionFile) throws IOException {
|
||||
InputStream inputStream = PathKt.inputStreamIfExists(versionFile);
|
||||
if (inputStream == null) {
|
||||
return -1;
|
||||
}
|
||||
try (DataInputStream versionInput = new DataInputStream(inputStream)) {
|
||||
return DataInputOutputUtil.readINT(versionInput);
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
myConstructedDataFiles.flush();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
try {
|
||||
Disposer.dispose(myDisposable);
|
||||
myConstructedDataFiles.close(false);
|
||||
}
|
||||
finally {
|
||||
myDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasTestTrace(@NotNull String testClassName, @NotNull String testMethodName, byte frameworkId) throws IOException {
|
||||
int testId = myTestEnumerator.tryEnumerate(createTestId(testClassName, testMethodName, frameworkId));
|
||||
return testId != 0 && myDiscoveredTestsIndex.containsDataFrom(testId);
|
||||
}
|
||||
|
||||
public void removeTestTrace(@NotNull String testClassName, @NotNull String testMethodName, byte frameworkId) throws IOException {
|
||||
int testId = myTestEnumerator.tryEnumerate(createTestId(testClassName, testMethodName, frameworkId));
|
||||
if (testId != 0) {
|
||||
myDiscoveredTestsIndex.update(testId, null).compute();
|
||||
myTestModuleIndex.removeTest(testId);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<String> getTestModulesByMethodName(@NotNull String testClassName, @NotNull String testMethodName, byte frameworkId) throws IOException {
|
||||
int testId = myTestEnumerator.tryEnumerate(createTestId(testClassName, testMethodName, frameworkId));
|
||||
if (testId != 0) {
|
||||
return myTestModuleIndex.getTestRunModules(testId);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public void updateTestData(@NotNull String testClassName,
|
||||
@NotNull String testMethodName,
|
||||
@NotNull MultiMap<String, String> usedMethods,
|
||||
@Nullable String moduleName,
|
||||
byte frameworkId) throws IOException {
|
||||
|
||||
final int testNameId = myTestEnumerator.enumerate(createTestId(testClassName, testMethodName, frameworkId));
|
||||
TLongHashSet result = new TLongHashSet();
|
||||
for (Map.Entry<String, Collection<String>> e : usedMethods.entrySet()) {
|
||||
int classId = myClassEnumerator.enumerate(e.getKey());
|
||||
for (String methodName : e.getValue()) {
|
||||
long key = createKey(classId, myMethodEnumerator.enumerate(methodName));
|
||||
result.add(key);
|
||||
}
|
||||
}
|
||||
myDiscoveredTestsIndex.update(testNameId, new DiscoveredTestsIndex.UsedMethods(result)).compute();
|
||||
myTestModuleIndex.appendModuleData(testNameId, moduleName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public MultiMap<String, String> getTestsByMethodName(@NotNull String classFQName, @NotNull String methodName, byte frameworkId) throws IOException {
|
||||
int methodId = myMethodEnumerator.tryEnumerate(methodName);
|
||||
if (methodId == 0) return MultiMap.empty();
|
||||
int classId = myClassEnumerator.tryEnumerate(classFQName);
|
||||
if (classId == 0) return MultiMap.empty();
|
||||
try {
|
||||
MultiMap<String, String> result = new MultiMap<>();
|
||||
IOException[] exception = {null};
|
||||
myDiscoveredTestsIndex.getData(createKey(classId, methodId)).forEach(new ValueContainer.ContainerAction<Void>() {
|
||||
@Override
|
||||
public boolean perform(int testId, Void value) {
|
||||
try {
|
||||
TestId test = myTestEnumerator.valueOf(testId);
|
||||
if (test.getFrameworkId() == frameworkId) {
|
||||
String testClassName = myClassEnumerator.valueOf(test.getClassId());
|
||||
String testMethodName = myMethodEnumerator.valueOf(test.getMethodId());
|
||||
result.putValue(testClassName, testMethodName);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
exception[0] = e;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (exception[0] != null) throw exception[0];
|
||||
return result;
|
||||
}
|
||||
catch (StorageException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDisposed() {
|
||||
return myDisposed;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static Path getVersionFile(Path path) {
|
||||
return path.resolve("index.version");
|
||||
}
|
||||
|
||||
public static long createKey(int classQName, int methodName) {
|
||||
return ((long)classQName << 32) | methodName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TestId createTestId(String className, String methodName, byte frameworkPrefix) throws IOException {
|
||||
return new TestId(myClassEnumerator.enumerate(className), myMethodEnumerator.enumerate(methodName), frameworkPrefix);
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
// 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.StorageException;
|
||||
import com.intellij.util.indexing.impl.*;
|
||||
import com.intellij.util.io.*;
|
||||
import gnu.trove.TLongHashSet;
|
||||
import gnu.trove.TLongIterator;
|
||||
import gnu.trove.TLongProcedure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class DiscoveredTestsIndex extends MapReduceIndex<Long, Void, DiscoveredTestsIndex.UsedMethods> {
|
||||
protected DiscoveredTestsIndex(@NotNull File file) throws IOException {
|
||||
super(INDEX_EXTENSION, new MyIndexStorage(file), new MyForwardIndex() {
|
||||
@NotNull
|
||||
@Override
|
||||
public PersistentHashMap<Integer, UsedMethods> createMap() throws IOException {
|
||||
return new PersistentHashMap<>(new File(file, "forward.idx"), EnumeratorIntegerDescriptor.INSTANCE, UsedMethods.USED_METHODS_DATA_EXTERNALIZER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCanceled() {
|
||||
ProgressManager.checkCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void requestRebuild(Throwable e) {
|
||||
//TODO index corrupted
|
||||
}
|
||||
|
||||
public boolean containsDataFrom(int testId) throws IOException {
|
||||
return ((MyForwardIndex)myForwardIndex).containsDataFrom(testId);
|
||||
}
|
||||
|
||||
private static class MyIndexStorage extends MapIndexStorage<Long, Void> {
|
||||
protected MyIndexStorage(@NotNull File storageFile) throws IOException {
|
||||
super(storageFile, MethodQNameSerializer.INSTANCE, VoidDataExternalizer.INSTANCE, 4 * 1024, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkCanceled() {
|
||||
ProgressManager.checkCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
private static final IndexExtension<Long, Void, UsedMethods> INDEX_EXTENSION = new IndexExtension<Long, Void, UsedMethods>() {
|
||||
@NotNull
|
||||
@Override
|
||||
public IndexId<Long, Void> getName() {
|
||||
return IndexId.create("jvm.discovered.tests");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataIndexer<Long, Void, UsedMethods> getIndexer() {return inputData -> inputData;}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public KeyDescriptor<Long> getKeyDescriptor() {
|
||||
return MethodQNameSerializer.INSTANCE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataExternalizer<Void> getValueExternalizer() {
|
||||
return VoidDataExternalizer.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return DiscoveredTestDataHolder.VERSION;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private abstract static class MyForwardIndex extends MapBasedForwardIndex<Long, Void, UsedMethods> {
|
||||
protected MyForwardIndex() throws IOException {
|
||||
super(INDEX_EXTENSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputDataDiffBuilder<Long, Void> getDiffBuilder(int inputId, UsedMethods oldData) {
|
||||
return new InputDataDiffBuilder<Long, Void>(inputId) {
|
||||
@Override
|
||||
public boolean differentiate(@NotNull Map<Long, Void> newData,
|
||||
@NotNull KeyValueUpdateProcessor<Long, Void> addProcessor,
|
||||
@NotNull KeyValueUpdateProcessor<Long, Void> updateProcessor,
|
||||
@NotNull RemovedKeyProcessor<Long> removeProcessor) throws StorageException {
|
||||
boolean[] updated = {false};
|
||||
StorageException[] exception = {null};
|
||||
if (oldData != null) {
|
||||
oldData.forEach(m -> {
|
||||
if (!newData.containsKey(m)) {
|
||||
try {
|
||||
removeProcessor.process(m, myInputId);
|
||||
if (!updated[0]) {
|
||||
updated[0] = true;
|
||||
}
|
||||
}
|
||||
catch (StorageException e) {
|
||||
exception[0] = e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
if (exception[0] != null) {
|
||||
throw exception[0];
|
||||
}
|
||||
|
||||
((UsedMethods)newData).forEach(m -> {
|
||||
if (oldData == null || !oldData.containsKey(m)) {
|
||||
try {
|
||||
addProcessor.process(m, null, myInputId);
|
||||
if (!updated[0]) {
|
||||
updated[0] = true;
|
||||
}
|
||||
}
|
||||
catch (StorageException e) {
|
||||
exception[0] = e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (exception[0] != null) {
|
||||
throw exception[0];
|
||||
}
|
||||
return updated[0];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UsedMethods convertToMapValueType(int inputId, Map<Long, Void> map) {
|
||||
return (UsedMethods) map;
|
||||
}
|
||||
|
||||
public boolean containsDataFrom(int testId) throws IOException {
|
||||
return getInput(testId) != null;
|
||||
}
|
||||
}
|
||||
|
||||
static class UsedMethods extends AbstractMap<Long, Void> {
|
||||
private final TLongHashSet myTestUsedMethods;
|
||||
|
||||
UsedMethods(TLongHashSet methods) {myTestUsedMethods = methods;}
|
||||
|
||||
public boolean contains(long method) {
|
||||
return myTestUsedMethods.contains(method);
|
||||
}
|
||||
|
||||
public boolean forEach(@NotNull TLongProcedure methodProcedure) {
|
||||
return myTestUsedMethods.forEach(methodProcedure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Long, Void>> entrySet() {
|
||||
return new AbstractSet<Entry<Long, Void>>() {
|
||||
@Override
|
||||
public Iterator<Entry<Long, Void>> iterator() {
|
||||
TLongIterator methodIterator = myTestUsedMethods.iterator();
|
||||
return new Iterator<Entry<Long, Void>>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return methodIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<Long, Void> next() {
|
||||
return new SimpleEntry<>(methodIterator.next(), null);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return myTestUsedMethods.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static final DataExternalizer<UsedMethods> USED_METHODS_DATA_EXTERNALIZER = new DataExternalizer<UsedMethods>() {
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, UsedMethods value) throws IOException {
|
||||
DataInputOutputUtil.writeINT(out, value.size());
|
||||
IOException[] exception = {null};
|
||||
value.forEach(m -> {
|
||||
try {
|
||||
MethodQNameSerializer.INSTANCE.save(out, m);
|
||||
}
|
||||
catch (IOException e) {
|
||||
exception[0] = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (exception[0] != null) {
|
||||
throw exception[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsedMethods read(@NotNull DataInput in) throws IOException {
|
||||
int size = DataInputOutputUtil.readINT(in);
|
||||
TLongHashSet result = new TLongHashSet();
|
||||
for (int i = 0; i < size; i++) {
|
||||
result.add(MethodQNameSerializer.INSTANCE.read(in));
|
||||
}
|
||||
return new UsedMethods(result);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// 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.util.io.KeyDescriptor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
class MethodQNameSerializer implements KeyDescriptor<Long> {
|
||||
public static final MethodQNameSerializer INSTANCE = new MethodQNameSerializer();
|
||||
|
||||
private MethodQNameSerializer() {}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, Long value) throws IOException {
|
||||
out.writeLong(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long read(@NotNull DataInput in) throws IOException {
|
||||
return in.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(Long value) {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(Long val1, Long val2) {
|
||||
return val1.equals(val2);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// 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.diagnostic.Logger;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.indexing.InvertedIndex;
|
||||
import com.intellij.util.io.PersistentEnumeratorDelegate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
class PersistentObjectSeq {
|
||||
private static final Logger LOG = Logger.getInstance(PersistentObjectSeq.class);
|
||||
|
||||
interface PersistentObject {
|
||||
void flush() throws Exception;
|
||||
|
||||
void close() throws IOException;
|
||||
}
|
||||
|
||||
private final Collection<PersistentObject> myObjects = ContainerUtil.newConcurrentSet();
|
||||
|
||||
public void add(InvertedIndex<?, ?, ?> index) {
|
||||
myObjects.add(new PersistentObject() {
|
||||
@Override
|
||||
public void flush() throws Exception {
|
||||
index.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
index.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void add(PersistentEnumeratorDelegate<?> enumerator) {
|
||||
myObjects.add(new PersistentObject() {
|
||||
@Override
|
||||
public void flush() {
|
||||
if (enumerator.isDirty()) {
|
||||
enumerator.force();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
enumerator.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void close(boolean ignoreCloseProblem) {
|
||||
for (PersistentObject object : myObjects) {
|
||||
try {
|
||||
object.close();
|
||||
}
|
||||
catch (Throwable throwable) {
|
||||
if (!ignoreCloseProblem) throw new RuntimeException(throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
for (PersistentObject object : myObjects) {
|
||||
try {
|
||||
object.flush();
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
myObjects.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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.util.io.DataInputOutputUtil;
|
||||
import com.intellij.util.io.KeyDescriptor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class TestId {
|
||||
private final int myClassId;
|
||||
private final int myMethodId;
|
||||
private final byte myFrameworkId;
|
||||
|
||||
TestId(int classId, int methodId, byte id) {
|
||||
myClassId = classId;
|
||||
myMethodId = methodId;
|
||||
myFrameworkId = id;
|
||||
}
|
||||
|
||||
public int getClassId() {
|
||||
return myClassId;
|
||||
}
|
||||
|
||||
public int getMethodId() {
|
||||
return myMethodId;
|
||||
}
|
||||
|
||||
public byte getFrameworkId() {
|
||||
return myFrameworkId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
TestId id = (TestId)o;
|
||||
return myClassId == id.myClassId &&
|
||||
myMethodId == id.myMethodId &&
|
||||
myFrameworkId == id.myFrameworkId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(myClassId, myMethodId, myFrameworkId);
|
||||
}
|
||||
|
||||
static final KeyDescriptor<TestId> DESCRIPTOR = new KeyDescriptor<TestId>() {
|
||||
@Override
|
||||
public int getHashCode(TestId id) {
|
||||
return id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(TestId id1, TestId id2) {
|
||||
return id1.equals(id2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, TestId id) throws IOException {
|
||||
DataInputOutputUtil.writeINT(out, id.getClassId());
|
||||
DataInputOutputUtil.writeINT(out, id.getMethodId());
|
||||
out.writeByte(id.getFrameworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestId read(@NotNull DataInput in) throws IOException {
|
||||
return new TestId(DataInputOutputUtil.readINT(in), DataInputOutputUtil.readINT(in), in.readByte());
|
||||
}
|
||||
};
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// 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.diagnostic.Logger;
|
||||
import com.intellij.util.io.*;
|
||||
import gnu.trove.TIntArrayList;
|
||||
import gnu.trove.TIntHashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class TestModuleIndex {
|
||||
private static final Logger LOG = Logger.getInstance(TestModuleIndex.class);
|
||||
|
||||
private final PersistentHashMap<Integer, TIntArrayList> myTestNameToRunModule;
|
||||
private final PersistentEnumeratorDelegate<String> myModuleNameEnumerator;
|
||||
|
||||
public TestModuleIndex(@NotNull Path basePath, @NotNull PersistentObjectSeq persistentObjectSeq) throws IOException {
|
||||
File moduleNameEnumeratorFile = basePath.resolve("moduleName.enum").toFile();
|
||||
myModuleNameEnumerator = new PersistentEnumeratorDelegate<>(moduleNameEnumeratorFile, EnumeratorStringDescriptor.INSTANCE, 64);
|
||||
persistentObjectSeq.add(myModuleNameEnumerator);
|
||||
|
||||
File testModuleIndexFile = basePath.resolve("testModule.index").toFile();
|
||||
myTestNameToRunModule = new PersistentHashMap<>(testModuleIndexFile, EnumeratorIntegerDescriptor.INSTANCE,
|
||||
new IntSeqExternalizer());
|
||||
}
|
||||
|
||||
void appendModuleData(int testId, @Nullable String moduleName) throws IOException {
|
||||
if (moduleName != null) {
|
||||
int moduleId = myModuleNameEnumerator.enumerate(moduleName);
|
||||
TIntArrayList previousRunModules = myTestNameToRunModule.get(moduleId);
|
||||
if (previousRunModules != null && previousRunModules.contains(moduleId)) {
|
||||
return;
|
||||
}
|
||||
myTestNameToRunModule.appendData(testId, out -> DataInputOutputUtil.writeINT(out, moduleId));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Collection<String> getTestRunModules(int testId) throws IOException {
|
||||
TIntArrayList moduleIds = myTestNameToRunModule.get(testId);
|
||||
if (moduleIds == null) return Collections.emptySet();
|
||||
List<String> result = new ArrayList<>(moduleIds.size());
|
||||
for (int i = 0; i < moduleIds.size(); i++) {
|
||||
int moduleId = moduleIds.get(i);
|
||||
String moduleName = myModuleNameEnumerator.valueOf(moduleId);
|
||||
if (LOG.assertTrue(moduleName != null)) {
|
||||
result.add(moduleName);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void removeTest(int testId) throws IOException {
|
||||
myTestNameToRunModule.remove(testId);
|
||||
}
|
||||
|
||||
private static class IntSeqExternalizer implements DataExternalizer<TIntArrayList> {
|
||||
public void save(@NotNull DataOutput dataOutput, TIntArrayList testNameIds) throws IOException {
|
||||
for (int testNameId : testNameIds.toNativeArray()) DataInputOutputUtil.writeINT(dataOutput, testNameId);
|
||||
}
|
||||
|
||||
public TIntArrayList read(@NotNull DataInput dataInput) throws IOException {
|
||||
TIntHashSet result = new TIntHashSet();
|
||||
|
||||
while (((InputStream)dataInput).available() > 0) {
|
||||
int id = DataInputOutputUtil.readINT(dataInput);
|
||||
result.add(id);
|
||||
}
|
||||
|
||||
return new TIntArrayList(result.toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -96,9 +96,18 @@ public class TestDiscoveryJUnitIntegrationTest extends JUnitAbstractIntegrationT
|
||||
}
|
||||
|
||||
private void assertTestDiscoveryIndex(String className, String methodName, Pair<String, String>... expectedTests) throws IOException {
|
||||
MultiMap<String, String> rawActualTests = TestDiscoveryIndex.getInstance(myProject).getTestsByMethodName(className, methodName, JUnitConfiguration.FRAMEWORK_ID);
|
||||
TestDiscoveryIndex testDiscoveryIndex = TestDiscoveryIndex.getInstance(myProject);
|
||||
MultiMap<String, String> rawActualTests = testDiscoveryIndex.getTestsByMethodName(className, methodName, JUnitConfiguration.FRAMEWORK_ID);
|
||||
Set<Pair<String, String>> actualTests = rawActualTests.entrySet().stream().flatMap(e -> e.getValue().stream().map(m -> Pair.create(e.getKey(), m))).collect(Collectors.toSet());
|
||||
assertEquals(ContainerUtil.newHashSet(expectedTests), actualTests);
|
||||
|
||||
Set<String> modules = actualTests
|
||||
.stream()
|
||||
.flatMap(
|
||||
test -> testDiscoveryIndex.getTestModulesByMethodName(test.getFirst(), test.getSecond(), JUnitConfiguration.FRAMEWORK_ID).stream())
|
||||
.collect(Collectors.toSet());
|
||||
String module = assertOneElement(modules);
|
||||
assertEquals(myModule.getName(), module);
|
||||
}
|
||||
|
||||
private static Pair<String, String> t(String testClassName, String testMethodName) {
|
||||
|
||||
Reference in New Issue
Block a user