support new coverage and test-discovery agents

This commit is contained in:
Dmitry Batkovich
2018-02-15 12:03:17 +03:00
parent c65464cf22
commit 6b2b24d862
20 changed files with 424 additions and 252 deletions
-15
View File
@@ -1,15 +0,0 @@
<component name="libraryTable">
<library name="Coverage">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/coverage-agent.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/coverage-instrumenter.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/coverage-util.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/test-discovery-agent.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/test-discovery-instrumenter.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/src/coverage-src.zip!/" />
</SOURCES>
</library>
</component>
+10
View File
@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="intellij-coverage" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.intellij.deps:intellij-coverage:1.0.369" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/intellij-coverage/1.0.369/intellij-coverage-1.0.369.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
+10
View File
@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="intellij-test-discovery" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.intellij.deps:intellij-test-discovery:1.0.369" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/intellij-test-discovery/1.0.369/intellij-test-discovery-1.0.369.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>
@@ -18,7 +18,8 @@
<orderEntry type="module" module-name="intellij.java.indexing" />
<orderEntry type="module" module-name="intellij.platform.smRunner" />
<orderEntry type="module" module-name="intellij.java.jshell.protocol" />
<orderEntry type="library" name="Coverage" level="project" />
<orderEntry type="library" name="intellij-coverage" level="project" />
<orderEntry type="library" name="intellij-test-discovery" level="project" />
<orderEntry type="library" scope="RUNTIME" name="precompiled_jshell-frontend" level="project" />
<orderEntry type="module" module-name="intellij.platform.externalSystem" />
<orderEntry type="library" name="gson" level="project" />
@@ -0,0 +1,56 @@
// 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.diagnostic.Logger;
import com.intellij.rt.coverage.data.SocketTestDataReader;
import com.intellij.util.containers.MultiMap;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
class IdeaSocketTestDiscoveryDataReader extends SocketTestDataReader {
private static final Logger LOG = Logger.getInstance(IdeaSocketTestDiscoveryDataReader.class);
@NotNull
private final TIntObjectHashMap<String> myTestExecutionNameEnumerator;
//test data
private String myTestName;
private final MultiMap<String, String> myUsedMethods = new MultiMap<>();
IdeaSocketTestDiscoveryDataReader(@NotNull TIntObjectHashMap<String> testExecutionNameEnumerator) {
myTestExecutionNameEnumerator = testExecutionNameEnumerator;
}
@NotNull
String getTestName() {
return myTestName;
}
@NotNull
MultiMap<String, String> getUsedMethods() {
return myUsedMethods;
}
@Override
protected void processTestName(int testClassId, int testMethodId) {
myTestName = myTestExecutionNameEnumerator.get(testClassId) + "-" + myTestExecutionNameEnumerator.get(testMethodId);
}
@Override
protected void processEnumeratedName(int id, String name) {
String previousName = myTestExecutionNameEnumerator.put(id, name);
LOG.assertTrue(previousName == null || previousName.equals(name));
}
@Override
protected void processUsedMethod(int classInternalId, int methodInternalId) {
String className = myTestExecutionNameEnumerator.get(classInternalId);
String methodName = myTestExecutionNameEnumerator.get(methodInternalId);
if (className == null || methodName == null) {
LOG.error("Inconsistent state");
return;
}
myUsedMethods.putValue(className, methodName);
}
}
@@ -0,0 +1,57 @@
// 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.rt.coverage.data.SingleTrFileReader;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
class IdeaTrFileTestDataReader extends SingleTrFileReader {
private String myCurrentClassName;
private MultiMap<String, String> myUsedMethods = new MultiMap<>();
private final TestDiscoveryIndex myIndex;
private final String myModuleName;
private final String myFrameworkPrefix;
public IdeaTrFileTestDataReader(@NotNull File file,
@NotNull TestDiscoveryIndex index,
@NotNull String moduleName,
@NotNull String frameworkPrefix) {
super(file);
myIndex = index;
myModuleName = moduleName;
myFrameworkPrefix = frameworkPrefix;
}
@Override
protected void testProcessingFinished(String testName) {
// flush
try {
int separatorIndex = testName.lastIndexOf('.');
testName = testName.substring(0, separatorIndex) + "-" + testName.substring(separatorIndex + 1);
myIndex.updateFromData(testName, myUsedMethods, myModuleName, myFrameworkPrefix);
}
catch (IOException e) {
throw new RuntimeException(e);
}
myUsedMethods.clear();
}
@Override
protected void classProcessingFinished(String className) {
myCurrentClassName = null;
}
@Override
protected void processMethodName(String methodName) {
myUsedMethods.putValue(myCurrentClassName, methodName);
}
@Override
protected void classProcessingStarted(String className) {
myCurrentClassName = className;
}
}
@@ -65,9 +65,8 @@ public abstract class TestDiscoveryConfigurationProducer extends JavaRunConfigur
try {
final Project project = configuration.getProject();
final TestDiscoveryIndex testDiscoveryIndex = TestDiscoveryIndex.getInstance(project);
final Collection<String> testsByMethodName = testDiscoveryIndex.getTestsByMethodName(position.first, position.second);
if (testsByMethodName == null ||
ContainerUtil.filter(testsByMethodName, s -> s.startsWith(configuration.getFrameworkPrefix())).isEmpty()) {
final Collection<String> testsByMethodName = testDiscoveryIndex.getTestsByMethodName(position.first, position.second, configuration.getFrameworkPrefix());
if (ContainerUtil.isEmpty(testsByMethodName)) {
return false;
}
setPosition(configuration, new PsiLocation<>(sourceMethod));
@@ -0,0 +1,129 @@
// 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.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.rt.coverage.data.SocketTestDataReader;
import com.intellij.rt.coverage.data.SocketTestDiscoveryProtocolDataListener;
import com.intellij.rt.coverage.data.TestDiscoveryProtocolDataListener;
import com.intellij.util.TimeoutUtil;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TestDiscoveryDataSocketListener {
private static final Logger LOG = Logger.getInstance(TestDiscoveryDataSocketListener.class);
private final @Nullable String myModuleName;
private final @NotNull String myFrameworkPrefix;
private final ServerSocket myServer;
private final int myPort;
private final TestDiscoveryIndex myTestDiscoveryIndex;
private final TIntObjectHashMap<String> myTestExecutionNameEnumerator = new TIntObjectHashMap<>();
private volatile boolean myCloseForcibly;
private volatile boolean myStarted;
public TestDiscoveryDataSocketListener(@NotNull Project project,
@Nullable String moduleName,
@NotNull String frameworkPrefix) throws IOException {
myTestDiscoveryIndex = TestDiscoveryIndex.getInstance(project);
myModuleName = moduleName;
myFrameworkPrefix = frameworkPrefix;
myServer = new ServerSocket(0);
myPort = myServer.getLocalPort();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
Socket socket;
myStarted = true;
while (true) {
if (myCloseForcibly) {
return;
}
try {
socket = myServer.accept();
if (socket != null) {
break;
}
}
catch (IOException e) {
LOG.error(e);
return;
}
TimeoutUtil.sleep(10);
}
try {
listenForFinishedTests(socket);
}
catch (IOException e) {
LOG.error(e);
}
});
while (!myStarted) {
TimeoutUtil.sleep(10);
}
}
private void listenForFinishedTests(@NotNull Socket socket) throws IOException {
InputStream testDataStream = socket.getInputStream();
while (true) {
byte msgType = (byte)testDataStream.read();
switch (msgType) {
case TestDiscoveryProtocolDataListener.START_MARKER:
int version = testDataStream.read();
LOG.assertTrue(version == SocketTestDiscoveryProtocolDataListener.VERSION);
LOG.debug("test discovery started");
break;
case TestDiscoveryProtocolDataListener.FINISH_MARKER:
LOG.debug("test discovery finished");
socket.close();
myServer.close();
return;
case TestDiscoveryProtocolDataListener.NAMES_DICTIONARY_PART_MARKER:
LOG.info("name enumerator part received");
SocketTestDataReader.readDictionary(new DataInputStream(testDataStream), new SocketTestDataReader() {
@Override
protected void processTestName(int testClassId, int testMethodId) {
throw new UnsupportedOperationException();
}
@Override
protected void processUsedMethod(int classId, int methodId) {
throw new UnsupportedOperationException();
}
@Override
protected void processEnumeratedName(int id, String name) {
String previousName = myTestExecutionNameEnumerator.put(id, name);
LOG.assertTrue(previousName == null || name.equals(previousName));
}
});
break;
case TestDiscoveryProtocolDataListener.TEST_FINISHED_MARKER:
LOG.info("test data received");
IdeaSocketTestDiscoveryDataReader reader = new IdeaSocketTestDiscoveryDataReader(myTestExecutionNameEnumerator);
SocketTestDataReader.readTestData(new DataInputStream(testDataStream), reader);
myTestDiscoveryIndex.updateFromData(reader.getTestName(), reader.getUsedMethods(), myModuleName, myFrameworkPrefix);
break;
}
}
}
public int getPort() {
return myPort;
}
public void closeForcibly() {
//myCloseForcibly = true;
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 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.JavaTestConfigurationBase;
@@ -22,7 +8,6 @@ import com.intellij.execution.configurations.JavaParameters;
import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.execution.configurations.RunnerSettings;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.testframework.JavaTestLocator;
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter;
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener;
import com.intellij.execution.testframework.sm.runner.SMTestProxy;
@@ -32,14 +17,17 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.rt.coverage.data.SingleTrFileDiscoveryProtocolDataListener;
import com.intellij.rt.coverage.data.SocketTestDiscoveryProtocolDataListener;
import com.intellij.rt.coverage.data.TestDiscoveryProjectData;
import com.intellij.rt.coverage.main.CoveragePremain;
import com.intellij.util.Alarm;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.SystemProperties;
import com.intellij.util.messages.MessageBusConnection;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
@@ -48,10 +36,11 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public class TestDiscoveryExtension extends RunConfigurationExtension {
private static final boolean USE_SOCKET = SystemProperties.getBooleanProperty("test.discovery.use.socket", false);
private static final Key<TestDiscoveryDataSocketListener> SOCKET_LISTENER_KEY = Key.create("test.discovery.socket.data.listener");
private static final Logger LOG = Logger.getInstance(TestDiscoveryExtension.class);
@NotNull
@@ -65,42 +54,24 @@ public class TestDiscoveryExtension extends RunConfigurationExtension {
@NotNull final ProcessHandler handler,
@Nullable RunnerSettings runnerSettings) {
if (runnerSettings == null && isApplicableFor(configuration)) {
final String frameworkPrefix = ((JavaTestConfigurationBase)configuration).getFrameworkPrefix();
final String moduleName = ((JavaTestConfigurationBase)configuration).getConfigurationModule().getModuleName();
Disposable disposable = Disposer.newDisposable();
final Alarm processTracesAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, disposable);
final MessageBusConnection connection = configuration.getProject().getMessageBus().connect();
connection.subscribe(SMTRunnerEventsListener.TEST_STATUS, new SMTRunnerEventsAdapter() {
private final List<String> myCompletedMethodNames = new ArrayList<>();
@Override
public void onTestFinished(@NotNull SMTestProxy test) {
final SMTestProxy.SMRootTestProxy root = test.getRoot();
if ((root == null || root.getHandler() == handler)) {
final String fullTestName = test.getLocationUrl();
if (fullTestName != null && fullTestName.startsWith(JavaTestLocator.TEST_PROTOCOL)) {
myCompletedMethodNames.add(frameworkPrefix + fullTestName.substring(JavaTestLocator.TEST_PROTOCOL.length() + 3));
if (myCompletedMethodNames.size() > 50) {
final String[] fullTestNames = ArrayUtil.toStringArray(myCompletedMethodNames);
myCompletedMethodNames.clear();
processTracesAlarm.addRequest(() -> processAvailableTraces(fullTestNames,
getTracesDirectory(configuration), moduleName, frameworkPrefix,
TestDiscoveryIndex.getInstance(configuration.getProject())
), 100);
}
}
}
}
TestDiscoveryDataSocketListener listener = SOCKET_LISTENER_KEY.get(configuration);
connection.subscribe(SMTRunnerEventsListener.TEST_STATUS, new SMTRunnerEventsAdapter() {
@Override
public void onTestingFinished(@NotNull SMTestProxy.SMRootTestProxy testsRoot) {
if (testsRoot.getHandler() == handler) {
if (testsRoot.getHandler() != handler) return;
if (listener == null) {
processTracesAlarm.cancelAllRequests();
processTracesAlarm.addRequest(() -> {
processAvailableTraces(configuration);
processTracesFile((JavaTestConfigurationBase)configuration);
Disposer.dispose(disposable);
}, 0);
connection.disconnect();
} else {
listener.closeForcibly();
}
}
});
@@ -117,12 +88,21 @@ public class TestDiscoveryExtension extends RunConfigurationExtension {
argument.append(agentPath);
params.getVMParametersList().add(argument.toString());
params.getClassPath().add(agentPath);
params.getVMParametersList().addProperty(TestDiscoveryProjectData.TRACE_DIR, getTracesDirectory(configuration));
params.getClassPath().add(PathUtil.getJarPathForClass(CoveragePremain.class));
TestDiscoveryDataSocketListener listener = tryInstallSocketListener(configuration);
if (listener != null) {
params.getVMParametersList().addProperty(SocketTestDiscoveryProtocolDataListener.PORT_PROP, Integer.toString(listener.getPort()));
params.getVMParametersList().addProperty(SocketTestDiscoveryProtocolDataListener.HOST_PROP, "127.0.0.1");
params.getVMParametersList().addProperty(TestDiscoveryProjectData.TEST_DISCOVERY_DATA_LISTENER_PROP, SocketTestDiscoveryProtocolDataListener.class.getName());
} else {
params.getVMParametersList().addProperty(SingleTrFileDiscoveryProtocolDataListener.TRACE_FILE, getTraceFilePath(configuration));
params.getVMParametersList().addProperty(TestDiscoveryProjectData.TEST_DISCOVERY_DATA_LISTENER_PROP, SingleTrFileDiscoveryProtocolDataListener.class.getName());
}
}
@NotNull
private static String getTracesDirectory(RunConfigurationBase configuration) {
return baseTestDiscoveryPathForProject(configuration.getProject()) + File.separator + configuration.getUniqueID();
private static String getTraceFilePath(RunConfigurationBase configuration) {
return baseTestDiscoveryPathForProject(configuration.getProject()) + File.separator + configuration.getUniqueID() + ".tr";
}
@Override
@@ -148,58 +128,60 @@ public class TestDiscoveryExtension extends RunConfigurationExtension {
return ProjectUtil.getProjectCachePath(project, "testDiscovery", true);
}
@Override
public void cleanUserData(RunConfigurationBase runConfigurationBase) {
TestDiscoveryDataSocketListener listener = runConfigurationBase.getUserData(SOCKET_LISTENER_KEY);
if (listener != null) {
listener.closeForcibly();
runConfigurationBase.putUserData(SOCKET_LISTENER_KEY, null);
}
}
private static final Object ourTracesLock = new Object();
private static void processAvailableTraces(RunConfigurationBase configuration) {
final String tracesDirectory = getTracesDirectory(configuration);
final TestDiscoveryIndex coverageIndex = TestDiscoveryIndex.getInstance(configuration.getProject());
synchronized (ourTracesLock) {
final File tracesDirectoryFile = new File(tracesDirectory);
final File[] testMethodTraces = tracesDirectoryFile.listFiles((dir, name) -> name.endsWith(".tr"));
if (testMethodTraces != null) {
for (File testMethodTrace : testMethodTraces) {
try {
coverageIndex.updateFromTestTrace(testMethodTrace, ((JavaTestConfigurationBase)configuration).getConfigurationModule().getModuleName(),
((JavaTestConfigurationBase)configuration).getFrameworkPrefix());
FileUtil.delete(testMethodTrace);
}
catch (IOException e) {
LOG.error("Can not load " + testMethodTrace, e);
}
}
private static void processTracesFile(JavaTestConfigurationBase configuration) {
final String tracesFilePath = getTraceFilePath(configuration);
final TestDiscoveryIndex testDiscoveryIndex = TestDiscoveryIndex.getInstance(configuration.getProject());
String moduleName = getConfigurationModuleName(configuration);
String frameworkPrefix = configuration.getFrameworkPrefix();
processTracesFile(tracesFilePath, moduleName, frameworkPrefix, testDiscoveryIndex);
}
final String[] filesInTracedDirectories = tracesDirectoryFile.list();
if (filesInTracedDirectories == null || filesInTracedDirectories.length == 0) {
FileUtil.delete(tracesDirectoryFile);
}
@SuppressWarnings("WeakerAccess") // called via reflection from com.intellij.InternalTestDiscoveryListener.flushCurrentTraces()
public static void processTracesFile(String tracesFilePath,
String moduleName,
String frameworkPrefix,
TestDiscoveryIndex discoveryIndex) {
final File tracesFile = new File(tracesFilePath);
synchronized (ourTracesLock) {
try {
new IdeaTrFileTestDataReader(tracesFile, discoveryIndex, moduleName, frameworkPrefix).read();
}
catch (IOException e) {
LOG.error("Can not load " + tracesFilePath, e);
} finally {
FileUtil.delete(tracesFile);
}
}
}
@SuppressWarnings("WeakerAccess") // called via reflection from com.intellij.InternalTestDiscoveryListener.flushCurrentTraces()
public static void processAvailableTraces(final String[] fullTestNames,
final String tracesDirectory,
final String moduleName,
final String frameworkPrefix,
final TestDiscoveryIndex discoveryIndex) {
synchronized (ourTracesLock) {
for (String fullTestName : fullTestNames) {
final String className = StringUtil.getPackageName(fullTestName);
final String methodName = StringUtil.getShortName(fullTestName);
if (!StringUtil.isEmptyOrSpaces(className) && !StringUtil.isEmptyOrSpaces(methodName)) {
final File testMethodTrace = new File(tracesDirectory, className + "-" + methodName + ".tr");
if (testMethodTrace.exists()) {
try {
discoveryIndex.updateFromTestTrace(testMethodTrace, moduleName, frameworkPrefix);
FileUtil.delete(testMethodTrace);
}
catch (Throwable e) {
LOG.error("Can not load " + testMethodTrace, e);
}
}
}
}
@NotNull
private static String getConfigurationModuleName(JavaTestConfigurationBase configuration) {
return configuration.getConfigurationModule().getModuleName();
}
@Nullable
private static TestDiscoveryDataSocketListener tryInstallSocketListener(@NotNull RunConfigurationBase configuration) {
TestDiscoveryDataSocketListener listener = null;
if (USE_SOCKET) {
try {
JavaTestConfigurationBase javaTestConfigurationBase = (JavaTestConfigurationBase)configuration;
listener = new TestDiscoveryDataSocketListener(configuration.getProject(), getConfigurationModuleName(javaTestConfigurationBase), javaTestConfigurationBase.getFrameworkPrefix());
configuration.putUserData(SOCKET_LISTENER_KEY, listener);
} catch (IOException e) {
LOG.error(e);
}
}
return listener;
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 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.Disposable;
@@ -21,9 +7,9 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.ThrowableNotNullFunction;
import com.intellij.util.ThrowableConvertor;
import com.intellij.util.io.DataInputOutputUtil;
import com.intellij.util.io.IOUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.io.PathKt;
import gnu.trove.THashSet;
import gnu.trove.TIntArrayList;
@@ -31,13 +17,10 @@ import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
/**
* @author Maxim.Mossienko on 7/9/2015.
@@ -97,7 +80,8 @@ public class TestDiscoveryIndex implements Disposable {
// todo: should we remove our local run data ?
}
public Collection<String> getTestsByMethodName(@NotNull String classFQName, @NotNull String methodName) throws IOException {
public Collection<String> getTestsByMethodName(@NotNull String classFQName, @NotNull String methodName, String frameworkPrefix) throws IOException {
//TODO support framework prefix again
return myLocalTestRunDataController.withTestDataHolder(new ThrowableConvertor<TestInfoHolder, Collection<String>, IOException>() {
@Override
public Collection<String> convert(TestInfoHolder localHolder) throws IOException {
@@ -263,18 +247,31 @@ public class TestDiscoveryIndex implements Disposable {
myRemoteTestRunDataController.dispose();
}
public void updateFromTestTrace(@NotNull File file,
@Nullable final String moduleName,
@NotNull final String frameworkPrefix) throws IOException {
int fileNameDotIndex = file.getName().lastIndexOf('.');
final String testName = fileNameDotIndex != -1 ? file.getName().substring(0, fileNameDotIndex) : file.getName();
doUpdateFromTestTrace(file, testName, moduleName != null ? frameworkPrefix + moduleName : null);
public void updateFromData(@NotNull String testName,
@NotNull MultiMap<String, String> usedMethods,
@Nullable String moduleName,
@NotNull String frameworkPrefix) throws IOException {
doUpdateFromTestTrace(testName, holder -> {
TIntObjectHashMap<TIntArrayList> result = new TIntObjectHashMap<>();
for (Map.Entry<String, Collection<String>> e : usedMethods.entrySet()) {
int classId = holder.myClassEnumeratorCache.enumerate(e.getKey());
TIntArrayList methodIds = new TIntArrayList();
result.put(classId, methodIds);
for (String methodName : e.getValue()) {
methodIds.add(holder.myMethodEnumeratorCache.enumerate(methodName));
}
}
return result;
}, moduleName, frameworkPrefix);
}
private void doUpdateFromTestTrace(File file, final String testName, @Nullable final String moduleName) throws IOException {
myLocalTestRunDataController.withTestDataHolder((ThrowableConvertor<TestInfoHolder, Void, IOException>)localHolder -> {
private void doUpdateFromTestTrace(@NotNull String testName,
@NotNull ThrowableNotNullFunction<TestInfoHolder, TIntObjectHashMap<TIntArrayList>, IOException> classDataExtractor,
@Nullable String moduleName,
@NotNull String frameworkPrefix) throws IOException {
myLocalTestRunDataController.withTestDataHolder(localHolder -> {
final int testNameId = localHolder.myTestNameEnumerator.enumerate(testName);
TIntObjectHashMap<TIntArrayList> classData = loadClassAndMethodsMap(file, localHolder);
TIntObjectHashMap<TIntArrayList> classData = classDataExtractor.fun(localHolder);
TIntObjectHashMap<TIntArrayList> previousClassData = localHolder.myTestNameToUsedClassesAndMethodMap.get(testNameId);
if (previousClassData == null) {
previousClassData = myRemoteTestRunDataController.withTestDataHolder(
@@ -304,36 +301,9 @@ public class TestDiscoveryIndex implements Disposable {
});
}
localHolder.doUpdateFromDiff(testNameId, classData, previousClassData, moduleName != null ? localHolder.myModuleNameEnumerator.enumerate(moduleName) : null);
String moduleId = moduleName == null ? null : moduleName + frameworkPrefix;
localHolder.doUpdateFromDiff(testNameId, classData, previousClassData, moduleId != null ? localHolder.myModuleNameEnumerator.enumerate(moduleId) : null);
return null;
});
}
@NotNull
private static TIntObjectHashMap<TIntArrayList> loadClassAndMethodsMap(File file, TestInfoHolder holder) throws IOException {
DataInputStream inputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(file), 64 * 1024));
byte[] buffer = IOUtil.allocReadWriteUTFBuffer();
try {
int numberOfClasses = DataInputOutputUtil.readINT(inputStream);
TIntObjectHashMap<TIntArrayList> classData = new TIntObjectHashMap<>(numberOfClasses);
while (numberOfClasses-- > 0) {
String classQName = IOUtil.readUTFFast(buffer, inputStream);
int classId = holder.myClassEnumeratorCache.enumerate(classQName);
int numberOfMethods = DataInputOutputUtil.readINT(inputStream);
TIntArrayList methodsList = new TIntArrayList(numberOfMethods);
while (numberOfMethods-- > 0) {
String methodName = IOUtil.readUTFFast(buffer, inputStream);
methodsList.add(holder.myMethodEnumeratorCache.enumerate(methodName));
}
classData.put(classId, methodsList);
}
return classData;
}
finally {
inputStream.close();
}
}
}
@@ -86,7 +86,7 @@ public class TestDiscoverySearchHelper {
try {
if (classQualifiedName != null &&
(position == null && TestFrameworks.detectFramework(containingClass) != null ||
position != null && !discoveryIndex.hasTestTrace(frameworkPrefix + classQualifiedName + "-" + changedMethodName))) {
position != null && !discoveryIndex.hasTestTrace(classQualifiedName + "-" + changedMethodName))) {
patterns.add(classQualifiedName + "," + changedMethodName);
}
}
@@ -112,10 +112,10 @@ public class TestDiscoverySearchHelper {
final String methodName,
final String frameworkId) throws IOException {
final TestDiscoveryIndex discoveryIndex = TestDiscoveryIndex.getInstance(project);
final Collection<String> testsByMethodName = discoveryIndex.getTestsByMethodName(classFQName, methodName);
final Collection<String> testsByMethodName = discoveryIndex.getTestsByMethodName(classFQName, methodName, frameworkId);
if (testsByMethodName != null) {
for (String pattern : ContainerUtil.filter(testsByMethodName, s -> s.startsWith(frameworkId))) {
patterns.add(pattern.substring(frameworkId.length()).replace('-', ','));
for (String pattern : testsByMethodName) {
patterns.add(pattern.replace('-', ','));
}
}
}
@@ -47,7 +47,7 @@ final class TestInfoHolder {
private boolean myDisposed;
private final Object myLock;
private static final int VERSION = 4;
private static final int VERSION = 5;
TestInfoHolder(@NotNull Path basePath, boolean readOnly, Object lock) {
myLock = lock;
@@ -22,8 +22,8 @@ public abstract class TestDiscoveryListener {
public void testStarted(String className, String methodName) {
try {
final Object data = getData();
Method testStarted = data.getClass().getMethod("testDiscoveryStarted", new Class[] {String.class});
testStarted.invoke(data, new Object[] {className + "-" + methodName});
Method testStarted = data.getClass().getMethod("testDiscoveryStarted", new Class[] {String.class, String.class});
testStarted.invoke(data, new Object[] {className, methodName});
} catch (Throwable t) {
t.printStackTrace();
}
@@ -33,8 +33,8 @@ public abstract class TestDiscoveryListener {
if (succeed) {
try {
final Object data = getData();
Method testEnded = data.getClass().getMethod("testDiscoveryEnded", new Class[] {String.class});
testEnded.invoke(data, new Object[] {getFrameworkId() + className + "-" + methodName});
Method testEnded = data.getClass().getMethod("testDiscoveryEnded", new Class[] {String.class, String.class});
testEnded.invoke(data, new Object[] {className, methodName});
} catch (Throwable t) {
t.printStackTrace();
}
Executable → Regular
BIN
View File
Binary file not shown.
Binary file not shown.
@@ -19,7 +19,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.io.ZipUtil;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
@@ -27,12 +26,8 @@ import junit.framework.TestListener;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipOutputStream;
/**
* Used in TestAll to collect data in command line
@@ -40,17 +35,16 @@ import java.util.zip.ZipOutputStream;
@SuppressWarnings({"unused", "UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
public class InternalTestDiscoveryListener implements TestListener, Closeable {
private final String myModuleName;
private final String myTracesDirectory;
private final List<String> myCompletedMethodNames = new ArrayList<>();
private final boolean myCompactResults;
private final String myTracesFile;
private Object myDiscoveryIndex;
private Class<?> myDiscoveryIndexClass;
public InternalTestDiscoveryListener() {
myTracesDirectory = System.getProperty("org.jetbrains.instrumentation.trace.dir");
myTracesFile = System.getProperty("org.jetbrains.instrumentation.trace.file");
if (myTracesFile == null) throw new IllegalArgumentException();
myModuleName = System.getProperty("org.jetbrains.instrumentation.main.module");
myCompactResults = Boolean.parseBoolean(System.getProperty("org.jetbrains.instrumentation.compact.traces", "true"));
System.out.println(getClass().getSimpleName() + " instantiated with module='" + myModuleName + "' , directory='" + myTracesDirectory + "'");
if (myModuleName == null) throw new IllegalArgumentException();
System.out.println(getClass().getSimpleName() + " instantiated with module='" + myModuleName + "' , directory='" + myTracesFile + "'");
}
private Object getIndex() {
@@ -60,7 +54,7 @@ public class InternalTestDiscoveryListener implements TestListener, Closeable {
myDiscoveryIndexClass = Class.forName("com.intellij.execution.testDiscovery.TestDiscoveryIndex");
myDiscoveryIndex = myDiscoveryIndexClass
.getConstructor(Project.class, String.class)
.newInstance(project, myTracesDirectory);
.newInstance(project, myTracesFile);
}
catch (Throwable e) {
e.printStackTrace();
@@ -82,37 +76,12 @@ public class InternalTestDiscoveryListener implements TestListener, Closeable {
try {
Object data = getData();
Method testEnded = data.getClass().getMethod("testDiscoveryEnded", String.class);
testEnded.invoke(data, "j" + className + "-" + methodName);
Method testEnded = data.getClass().getMethod("testDiscoveryEnded", String.class, String.class);
testEnded.invoke(data, className, methodName);
}
catch (Throwable t) {
t.printStackTrace();
}
myCompletedMethodNames.add("j" + className + "." + methodName);
if (myCompletedMethodNames.size() > 50) {
final String[] fullTestNames = ArrayUtil.toStringArray(myCompletedMethodNames);
myCompletedMethodNames.clear();
AppExecutorUtil.getAppExecutorService().execute(() -> flushCurrentTraces(fullTestNames));
}
}
protected void flushCurrentTraces(final String[] fullTestNames) {
if (!myCompactResults) return;
System.out.println("Start compacting to index");
try {
Object index = getIndex();
Method method = Class.forName("com.intellij.execution.testDiscovery.TestDiscoveryExtension")
.getMethod("processAvailableTraces", fullTestNames.getClass(), myTracesDirectory.getClass(), String.class,
String.class,
myDiscoveryIndexClass);
method.invoke(null, fullTestNames, myTracesDirectory, myModuleName, "j", index);
System.out.println("Compacting done.");
}
catch (Throwable e) {
e.printStackTrace();
}
}
private static String getMethodName(Test test) {
@@ -131,8 +100,8 @@ public class InternalTestDiscoveryListener implements TestListener, Closeable {
public void startTest(Test test) {
try {
Object data = getData();
Method testStarted = data.getClass().getMethod("testDiscoveryStarted", String.class);
testStarted.invoke(data, getClassName(test) + "-" + getMethodName(test));
Method testStarted = data.getClass().getMethod("testDiscoveryStarted", String.class, String.class);
testStarted.invoke(data, getClassName(test), getMethodName(test));
}
catch (Throwable t) {
t.printStackTrace();
@@ -140,39 +109,40 @@ public class InternalTestDiscoveryListener implements TestListener, Closeable {
}
protected Object getData() throws Exception {
return Class.forName("com.intellij.rt.coverage.data.ProjectData")
return Class.forName("com.intellij.rt.coverage.data.TestDiscoveryProjectData")
.getMethod("getProjectData", ArrayUtil.EMPTY_CLASS_ARRAY)
.invoke(null, ArrayUtil.EMPTY_OBJECT_ARRAY);
}
@Override
public void close() throws IOException {
final String[] fullTestNames = ArrayUtil.toStringArray(myCompletedMethodNames);
myCompletedMethodNames.clear();
flushCurrentTraces(fullTestNames);
zipOutput(myTracesDirectory);
System.out.println("Start compacting to index");
try {
Object index = getIndex();
Method method = Class.forName("com.intellij.execution.testDiscovery.TestDiscoveryExtension")
.getMethod("processTracesFile", String.class, String.class, String.class, myDiscoveryIndexClass);
method.invoke(null, myTracesFile, myModuleName, "j", index);
System.out.println("Compacting done.");
}
catch (Throwable e) {
e.printStackTrace();
}
zipOutput(myTracesFile);
}
private static void zipOutput(String tracesDirectory) {
final String zipName = "out.zip";
final File[] files = new File(tracesDirectory).listFiles((dir, name) -> name != null && !name.equalsIgnoreCase(zipName));
if (files == null) {
System.out.println("No traces found.");
return;
}
private static void zipOutput(String traceFilePath) {
File traceFile = new File(traceFilePath);
File parent = traceFile.getParentFile();
String zipName = traceFile.getName() + ".zip";
System.out.println("Preparing zip.");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(new File(tracesDirectory, zipName)))) {
for (File file : files) {
ZipUtil.addFileToZip(zipOutputStream, file, "/" + file.getName(), null, null);
}
System.out.println("Zip prepared.");
for (File file : files) {
FileUtil.delete(file);
}
try {
File zipFile = new File(parent, zipName);
ZipUtil.compressFile(traceFile, zipFile);
FileUtil.delete(traceFile);
System.out.println("archive " + zipFile.getPath() + " prepared");
}
catch (Throwable ex) {
ex.printStackTrace();
catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -275,6 +275,7 @@ public class TestAll implements Test {
}
private static TestListener loadDiscoveryListener() {
// com.intellij.InternalTestDiscoveryListener
final String discoveryListener = System.getProperty("test.discovery.listener");
if (discoveryListener != null) {
try {
@@ -13,7 +13,8 @@
<orderEntry type="module" module-name="intellij.platform.extensions" />
<orderEntry type="module" module-name="intellij.platform.annotations.java5" />
<orderEntry type="library" exported="" name="ASM" level="project" />
<orderEntry type="library" exported="" name="Coverage" level="project" />
<orderEntry type="library" exported="" name="intellij-coverage" level="project" />
<orderEntry type="library" exported="" name="intellij-test-discovery" level="project" />
<orderEntry type="library" exported="" name="Trove4j" level="project" />
<orderEntry type="module" module-name="intellij.platform.testRunner" />
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
@@ -10,6 +10,7 @@
<orderEntry type="module" module-name="intellij.testng.rt" />
<orderEntry type="module" module-name="intellij.junit.rt" />
<orderEntry type="library" scope="PROVIDED" name="TestNG" level="project" />
<orderEntry type="library" name="Coverage" level="project" />
<orderEntry type="library" name="intellij-coverage" level="project" />
<orderEntry type="library" name="intellij-test-discovery" level="project" />
</component>
</module>
@@ -18,8 +18,8 @@ package com.intellij.coverage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.rt.coverage.instrumentation.SourceLineCounter;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.coverage.gnu.trove.TIntObjectHashMap;
import org.jetbrains.coverage.org.objectweb.asm.ClassReader;
import java.util.HashSet;
import java.util.List;
@@ -36,7 +36,7 @@ public class SourceLineCounterUtil {
SourceLineCounter counter = new SourceLineCounter(null, excludeLines, null);
reader.accept(counter, 0);
Set<Object> descriptions = new HashSet<>();
TIntObjectHashMap<?> lines = counter.getSourceLines();
TIntObjectHashMap lines = counter.getSourceLines();
lines.forEachEntry((line, description) -> {
if (includeDescriptionCondition.value((String)description)) {
classCoverageInfo.totalLineCount++;
@@ -65,7 +65,7 @@ public class SourceLineCounterUtil {
String qualifiedName = reader.getClassName();
Condition<String> includeDescriptionCondition = description -> !JavaCoverageOptionsProvider.getInstance(project).isGeneratedConstructor(qualifiedName, description);
TIntObjectHashMap<?> lines = collector.getSourceLines();
TIntObjectHashMap lines = collector.getSourceLines();
lines.forEachEntry((line, description) -> {
if (includeDescriptionCondition.value((String)description)) {
line--;