diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/InternalTestDiscoveryListener.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/InternalTestDiscoveryListener.java new file mode 100644 index 000000000000..5c89e1bd9031 --- /dev/null +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/InternalTestDiscoveryListener.java @@ -0,0 +1,134 @@ +/* + * Copyright 2000-2016 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. + */ +package com.intellij.execution.testDiscovery; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; +import com.intellij.openapi.util.Disposer; +import com.intellij.util.Alarm; +import com.intellij.util.ArrayUtil; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * Used in TestAll to collect data in command line + */ +@SuppressWarnings("unused") +public class InternalTestDiscoveryListener implements TestListener, Closeable { + private List myCompletedMethodNames = new ArrayList(); + private String myTracesDirectory; + private final Alarm myProcessTracesAlarm; + private TestDiscoveryIndex myDiscoveryIndex; + + public InternalTestDiscoveryListener() { + myProcessTracesAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, null); + myTracesDirectory = System.getProperty("org.jetbrains.instrumentation.trace.dir"); + } + + private TestDiscoveryIndex getIndex() { + if (myDiscoveryIndex == null) { + final Project project = ProjectManager.getInstance().getDefaultProject(); + try { + myDiscoveryIndex = (TestDiscoveryIndex)Class.forName(TestDiscoveryIndex.class.getName()) + .getConstructor(Project.class, String.class) + .newInstance(project, myTracesDirectory); + } + catch (Throwable e) { + e.printStackTrace(); + } + } + return myDiscoveryIndex; + } + + @Override + public void addError(Test test, Throwable t) {} + + @Override + public void addFailure(Test test, AssertionFailedError t) {} + + @Override + public void endTest(Test test) { + final String className = getClassName(test); + final String methodName = getMethodName(test); + + try { + final Object data = getData(); + Method testEnded = data.getClass().getMethod("testDiscoveryEnded", new Class[] {String.class}); + testEnded.invoke(data, new Object[] {"j" + className + "-" + methodName}); + } catch (Throwable t) { + t.printStackTrace(); + } + + myCompletedMethodNames.add("j" + className + "." + methodName); + + if (myCompletedMethodNames.size() > 50) { + flushCurrentTraces(); + } + } + + protected void flushCurrentTraces() { + final String[] fullTestNames = ArrayUtil.toStringArray(myCompletedMethodNames); + myCompletedMethodNames.clear(); + myProcessTracesAlarm.addRequest(() -> { + TestDiscoveryExtension.processAvailableTraces(fullTestNames, myTracesDirectory, null, getIndex()); + }, 100); + } + + private static String getMethodName(Test test) { + final String toString = test.toString(); + final int braceIdx = toString.indexOf("("); + return braceIdx > 0 ? toString.substring(0, braceIdx) : toString; + } + + private static String getClassName(Test test) { + final String toString = test.toString(); + final int braceIdx = toString.indexOf("("); + return braceIdx > 0 && toString.endsWith(")") ? toString.substring(braceIdx + 1, toString.length() - 1) : null; + } + + @Override + public void startTest(Test test) { + try { + final Object data = getData(); + Method testStarted = data.getClass().getMethod("testDiscoveryStarted", new Class[] {String.class}); + testStarted.invoke(data, new Object[] {getClassName(test) + "-" + getMethodName(test)}); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + protected Object getData() throws Exception { + return Class.forName("com.intellij.rt.coverage.data.ProjectData") + .getMethod("getProjectData", new Class[0]) + .invoke(null, new Object[0]); + } + + @Override + public void close() throws IOException { + myProcessTracesAlarm.cancelAllRequests(); + myProcessTracesAlarm.addRequest(() -> { + flushCurrentTraces(); + Disposer.dispose(myProcessTracesAlarm); + }, 0); + } +} 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 30b4392368ee..377583935031 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryExtension.java @@ -88,7 +88,10 @@ public class TestDiscoveryExtension extends RunConfigurationExtension { if (myCompletedMethodNames.size() > 50) { final String[] fullTestNames = ArrayUtil.toStringArray(myCompletedMethodNames); myCompletedMethodNames.clear(); - processTracesAlarm.addRequest(() -> processAvailableTraces(configuration, fullTestNames), 100); + processTracesAlarm.addRequest(() -> processAvailableTraces(fullTestNames, + getTracesDirectory(configuration), configuration, + TestDiscoveryIndex.getInstance(configuration.getProject()) + ), 100); } } } @@ -175,9 +178,10 @@ public class TestDiscoveryExtension extends RunConfigurationExtension { } } - private static void processAvailableTraces(RunConfigurationBase configuration, String[] fullTestNames) { - final String tracesDirectory = getTracesDirectory(configuration); - final TestDiscoveryIndex coverageIndex = TestDiscoveryIndex.getInstance(configuration.getProject()); + public static void processAvailableTraces(final String[] fullTestNames, + final String tracesDirectory, + RunConfigurationBase configuration, + final TestDiscoveryIndex discoveryIndex) { synchronized (ourTracesLock) { for (String fullTestName : fullTestNames) { final String className = StringUtil.getPackageName(fullTestName); @@ -186,7 +190,7 @@ public class TestDiscoveryExtension extends RunConfigurationExtension { final File testMethodTrace = new File(tracesDirectory, className + "-" + methodName + ".tr"); if (testMethodTrace.exists()) { try { - coverageIndex.updateFromTestTrace(testMethodTrace, (JavaTestConfigurationBase)configuration); + discoveryIndex.updateFromTestTrace(testMethodTrace, (JavaTestConfigurationBase)configuration); FileUtil.delete(testMethodTrace); } catch (IOException e) { diff --git a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java index 69eda5c41e67..68a6f4e25cf2 100644 --- a/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java +++ b/java/execution/impl/src/com/intellij/execution/testDiscovery/TestDiscoveryIndex.java @@ -45,14 +45,18 @@ public class TestDiscoveryIndex implements ProjectComponent { private static final int REMOVED_MARKER = -1; private final Object ourLock = new Object(); private final Project myProject; + private final String myBasePath; private volatile Holder myHolder; public TestDiscoveryIndex(Project project) { + this(project, TestDiscoveryExtension.baseTestDiscoveryPathForProject(project)); + } + + public TestDiscoveryIndex(final Project project, final String basePath) { myProject = project; - String path = TestDiscoveryExtension.baseTestDiscoveryPathForProject(myProject); - - if (new File(path).exists()) { + myBasePath = basePath; + if (new File(basePath).exists()) { StartupManager.getInstance(project).registerPostStartupActivity(() -> ApplicationManager.getApplication().executeOnPooledThread(() -> { getHolder(); // proactively init with maybe io costly compact })); @@ -204,22 +208,26 @@ public class TestDiscoveryIndex implements ProjectComponent { private boolean myDisposed; Holder() { - String path = TestDiscoveryExtension.baseTestDiscoveryPathForProject(myProject); - final File versionFile = getVersionFile(path); + final File versionFile = getVersionFile(myBasePath); versionFile.getParentFile().mkdirs(); - final File methodQNameToTestNameFile = new File(path + File.separator + "methodQNameToTestName.data"); - final File testNameToUsedClassesAndMethodMapFile = new File(path + File.separator + "testToCalledMethodNames.data"); - final File classNameEnumeratorFile = new File(path + File.separator + "classNameEnumerator.data"); - final File methodNameEnumeratorFile = new File(path + File.separator + "methodNameEnumerator.data"); - final File testNameEnumeratorFile = new File(path + File.separator + "testNameEnumerator.data"); - final File moduleNameEnumeratorFile = new File(path + File.separator + "moduleNameEnumerator.data"); - final File testNameToNearestModuleFile = new File(path + File.separator + "testNameToNearestModule.data"); + final File methodQNameToTestNameFile = new File(myBasePath + File.separator + "methodQNameToTestName.data"); + final File testNameToUsedClassesAndMethodMapFile = new File(myBasePath + File.separator + "testToCalledMethodNames.data"); + final File classNameEnumeratorFile = new File(myBasePath + File.separator + "classNameEnumerator.data"); + final File methodNameEnumeratorFile = new File(myBasePath + File.separator + "methodNameEnumerator.data"); + final File testNameEnumeratorFile = new File(myBasePath + File.separator + "testNameEnumerator.data"); + final File moduleNameEnumeratorFile = new File(myBasePath + File.separator + "moduleNameEnumerator.data"); + final File testNameToNearestModuleFile = new File(myBasePath + File.separator + "testNameToNearestModule.data"); try { int version = readVersion(versionFile); if (version != VERSION) { LOG.info("TestDiscoveryIndex was rewritten due to version change"); - deleteAllIndexDataFiles(methodQNameToTestNameFile, testNameToUsedClassesAndMethodMapFile, classNameEnumeratorFile, methodNameEnumeratorFile, testNameEnumeratorFile); + deleteAllIndexDataFiles(methodQNameToTestNameFile, + testNameToUsedClassesAndMethodMapFile, + classNameEnumeratorFile, + methodNameEnumeratorFile, + testNameEnumeratorFile, moduleNameEnumeratorFile, + testNameToNearestModuleFile); writeVersion(versionFile); } @@ -276,7 +284,7 @@ public class TestDiscoveryIndex implements ProjectComponent { myConstructedDataFiles.clear(); deleteAllIndexDataFiles(methodQNameToTestNameFile, testNameToUsedClassesAndMethodMapFile, classNameEnumeratorFile, methodNameEnumeratorFile, - testNameEnumeratorFile); + testNameEnumeratorFile, moduleNameEnumeratorFile, testNameToNearestModuleFile); // try another time } @@ -327,14 +335,10 @@ public class TestDiscoveryIndex implements ProjectComponent { } } - private void deleteAllIndexDataFiles(File methodQNameToTestNameFile, - File testNameToUsedClassesAndMethodMapFile, - File classNameEnumeratorFile, File methodNameEnumeratorFile, File testNameEnumeratorFile) { - IOUtil.deleteAllFilesStartingWith(methodQNameToTestNameFile); - IOUtil.deleteAllFilesStartingWith(testNameToUsedClassesAndMethodMapFile); - IOUtil.deleteAllFilesStartingWith(classNameEnumeratorFile); - IOUtil.deleteAllFilesStartingWith(methodNameEnumeratorFile); - IOUtil.deleteAllFilesStartingWith(testNameEnumeratorFile); + private void deleteAllIndexDataFiles(File... files) { + for (File file : files) { + IOUtil.deleteAllFilesStartingWith(file); + } } private void writeVersion(File versionFile) throws IOException { @@ -465,10 +469,10 @@ public class TestDiscoveryIndex implements ProjectComponent { } } - public void updateFromTestTrace(@NotNull File file, JavaTestConfigurationBase configurationBase) throws IOException { + public void updateFromTestTrace(@NotNull File file, @Nullable JavaTestConfigurationBase configurationBase) throws IOException { int fileNameDotIndex = file.getName().lastIndexOf('.'); final String testName = fileNameDotIndex != -1 ? file.getName().substring(0, fileNameDotIndex) : file.getName(); - final Module module = configurationBase.getConfigurationModule().getModule(); + final Module module = configurationBase != null ? configurationBase.getConfigurationModule().getModule() : null; doUpdateFromTestTrace(file, testName, module != null ? configurationBase.getFrameworkPrefix() + module.getName() : null); } diff --git a/platform/testFramework/src/com/intellij/TestAll.java b/platform/testFramework/src/com/intellij/TestAll.java index 8a3aee855dc2..18a93116e578 100644 --- a/platform/testFramework/src/com/intellij/TestAll.java +++ b/platform/testFramework/src/com/intellij/TestAll.java @@ -40,6 +40,7 @@ import org.junit.runner.Description; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.NoTestsRemainException; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; @@ -289,6 +290,11 @@ public class TestAll implements Test { public void run(final TestResult testResult) { loadTestRecorder(); + final TestListener testListener = loadDiscoveryListener(); + if (testListener != null) { + testResult.addListener(testListener); + } + List classes = myTestCaseLoader.getClasses(); int totalTests = classes.size(); for (Class aClass : classes) { @@ -308,9 +314,31 @@ public class TestAll implements Test { if (testResult.shouldStop()) break; } + if (testListener instanceof Closeable) { + try { + ((Closeable)testListener).close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + tryGc(10); } + private TestListener loadDiscoveryListener() { + final String discoveryListener = System.getProperty("test.discovery.listener"); + if (discoveryListener != null) { + try { + return (TestListener)Class.forName(discoveryListener).newInstance(); + } + catch (Throwable e) { + return null; + } + } + return null; + } + private static boolean shouldRecord(@NotNull Class aClass) { return aClass.getAnnotation(RecordExecution.class) != null; }