diff --git a/.idea/libraries/Coverage.xml b/.idea/libraries/Coverage.xml deleted file mode 100644 index 3585b430326d..000000000000 --- a/.idea/libraries/Coverage.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/intellij_coverage.xml b/.idea/libraries/intellij_coverage.xml new file mode 100644 index 000000000000..0821d76efd40 --- /dev/null +++ b/.idea/libraries/intellij_coverage.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/intellij_test_discovery.xml b/.idea/libraries/intellij_test_discovery.xml new file mode 100644 index 000000000000..e6082b6fe695 --- /dev/null +++ b/.idea/libraries/intellij_test_discovery.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java/execution/impl/intellij.java.execution.impl.iml b/java/execution/impl/intellij.java.execution.impl.iml index 029f0ba24a68..5bafa0222a4a 100644 --- a/java/execution/impl/intellij.java.execution.impl.iml +++ b/java/execution/impl/intellij.java.execution.impl.iml @@ -18,7 +18,8 @@ - + + diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/IdeaSocketTestDiscoveryDataReader.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/IdeaSocketTestDiscoveryDataReader.java new file mode 100644 index 000000000000..a05b01429fd1 --- /dev/null +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/IdeaSocketTestDiscoveryDataReader.java @@ -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 myTestExecutionNameEnumerator; + + //test data + private String myTestName; + private final MultiMap myUsedMethods = new MultiMap<>(); + + IdeaSocketTestDiscoveryDataReader(@NotNull TIntObjectHashMap testExecutionNameEnumerator) { + myTestExecutionNameEnumerator = testExecutionNameEnumerator; + } + + @NotNull + String getTestName() { + return myTestName; + } + + @NotNull + MultiMap 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); + } +} diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/IdeaTrFileTestDataReader.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/IdeaTrFileTestDataReader.java new file mode 100644 index 000000000000..6d6d79736784 --- /dev/null +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/IdeaTrFileTestDataReader.java @@ -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 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; + } +} diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java index a7845c73b615..5c1d8aaa04f0 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryConfigurationProducer.java @@ -65,9 +65,8 @@ public abstract class TestDiscoveryConfigurationProducer extends JavaRunConfigur try { final Project project = configuration.getProject(); final TestDiscoveryIndex testDiscoveryIndex = TestDiscoveryIndex.getInstance(project); - final Collection testsByMethodName = testDiscoveryIndex.getTestsByMethodName(position.first, position.second); - if (testsByMethodName == null || - ContainerUtil.filter(testsByMethodName, s -> s.startsWith(configuration.getFrameworkPrefix())).isEmpty()) { + final Collection testsByMethodName = testDiscoveryIndex.getTestsByMethodName(position.first, position.second, configuration.getFrameworkPrefix()); + if (ContainerUtil.isEmpty(testsByMethodName)) { return false; } setPosition(configuration, new PsiLocation<>(sourceMethod)); diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryDataSocketListener.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryDataSocketListener.java new file mode 100644 index 000000000000..9f963081c627 --- /dev/null +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryDataSocketListener.java @@ -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 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; + } +} diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java index 5f26df25af32..f22f821d62c0 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java @@ -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 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 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; } } \ No newline at end of file diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java index 8531808a6046..6e761baf1c51 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java @@ -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 getTestsByMethodName(@NotNull String classFQName, @NotNull String methodName) throws IOException { + public Collection getTestsByMethodName(@NotNull String classFQName, @NotNull String methodName, String frameworkPrefix) throws IOException { + //TODO support framework prefix again return myLocalTestRunDataController.withTestDataHolder(new ThrowableConvertor, IOException>() { @Override public Collection 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 usedMethods, + @Nullable String moduleName, + @NotNull String frameworkPrefix) throws IOException { + doUpdateFromTestTrace(testName, holder -> { + TIntObjectHashMap result = new TIntObjectHashMap<>(); + for (Map.Entry> 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)localHolder -> { + private void doUpdateFromTestTrace(@NotNull String testName, + @NotNull ThrowableNotNullFunction, IOException> classDataExtractor, + @Nullable String moduleName, + @NotNull String frameworkPrefix) throws IOException { + myLocalTestRunDataController.withTestDataHolder(localHolder -> { final int testNameId = localHolder.myTestNameEnumerator.enumerate(testName); - TIntObjectHashMap classData = loadClassAndMethodsMap(file, localHolder); + TIntObjectHashMap classData = classDataExtractor.fun(localHolder); TIntObjectHashMap 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 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 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(); - } - } } diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoverySearchHelper.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoverySearchHelper.java index 1b8a94121cc3..1f374a60b369 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoverySearchHelper.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoverySearchHelper.java @@ -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 testsByMethodName = discoveryIndex.getTestsByMethodName(classFQName, methodName); + final Collection 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('-', ',')); } } } diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestInfoHolder.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestInfoHolder.java index d54aebb4dd71..f6e99a50c518 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestInfoHolder.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestInfoHolder.java @@ -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; diff --git a/java/java-runtime/src/com/intellij/execution/TestDiscoveryListener.java b/java/java-runtime/src/com/intellij/execution/TestDiscoveryListener.java index 3411748d7076..cbeea8979ce4 100644 --- a/java/java-runtime/src/com/intellij/execution/TestDiscoveryListener.java +++ b/java/java-runtime/src/com/intellij/execution/TestDiscoveryListener.java @@ -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(); } diff --git a/lib/test-discovery-agent.jar b/lib/test-discovery-agent.jar old mode 100755 new mode 100644 index 68da6db00609..904fd7739123 Binary files a/lib/test-discovery-agent.jar and b/lib/test-discovery-agent.jar differ diff --git a/lib/test-discovery-instrumenter.jar b/lib/test-discovery-instrumenter.jar deleted file mode 100755 index 4adff197c39f..000000000000 Binary files a/lib/test-discovery-instrumenter.jar and /dev/null differ diff --git a/platform/testFramework/src/com/intellij/InternalTestDiscoveryListener.java b/platform/testFramework/src/com/intellij/InternalTestDiscoveryListener.java index ca6029a05d39..80d168f08777 100644 --- a/platform/testFramework/src/com/intellij/InternalTestDiscoveryListener.java +++ b/platform/testFramework/src/com/intellij/InternalTestDiscoveryListener.java @@ -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 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(); } } } diff --git a/platform/testFramework/src/com/intellij/TestAll.java b/platform/testFramework/src/com/intellij/TestAll.java index d8623303569e..14b47634accd 100644 --- a/platform/testFramework/src/com/intellij/TestAll.java +++ b/platform/testFramework/src/com/intellij/TestAll.java @@ -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 { diff --git a/plugins/coverage-common/intellij.platform.coverage.iml b/plugins/coverage-common/intellij.platform.coverage.iml index 9aabc3aebfe7..1c09f8f4c313 100644 --- a/plugins/coverage-common/intellij.platform.coverage.iml +++ b/plugins/coverage-common/intellij.platform.coverage.iml @@ -13,7 +13,8 @@ - + + diff --git a/plugins/coverage/coverage_rt/intellij.java.coverage.rt.iml b/plugins/coverage/coverage_rt/intellij.java.coverage.rt.iml index 657a814f9d2e..3be4291b0267 100644 --- a/plugins/coverage/coverage_rt/intellij.java.coverage.rt.iml +++ b/plugins/coverage/coverage_rt/intellij.java.coverage.rt.iml @@ -10,6 +10,7 @@ - + + \ No newline at end of file diff --git a/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java b/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java index 252e948f15b3..58aeac2bd310 100644 --- a/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java +++ b/plugins/coverage/src/com/intellij/coverage/SourceLineCounterUtil.java @@ -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 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 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--;