/* * Copyright 2000-2015 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.testframework.sm.runner.ui; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.concurrency.JobScheduler; import com.intellij.execution.TestStateStorage; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.testframework.*; import com.intellij.execution.testframework.actions.ScrollToTestSourceAction; import com.intellij.execution.testframework.export.TestResultsXmlFormatter; import com.intellij.execution.testframework.sm.SMRunnerUtil; import com.intellij.execution.testframework.sm.TestHistoryConfiguration; import com.intellij.execution.testframework.sm.runner.*; import com.intellij.execution.testframework.sm.runner.history.ImportedTestConsoleProperties; import com.intellij.execution.testframework.sm.runner.history.actions.AbstractImportTestsAction; import com.intellij.execution.testframework.ui.TestResultsPanel; import com.intellij.execution.testframework.ui.TestsProgressAnimator; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator; import com.intellij.openapi.progress.util.ColorProgressBar; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pass; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.pom.Navigatable; import com.intellij.ui.JBColor; import com.intellij.util.OpenSourceUtil; import com.intellij.util.PathUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.update.Update; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.xml.transform.OutputKeys; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import java.awt.*; import java.io.File; import java.io.FileWriter; import java.text.SimpleDateFormat; import java.util.*; import java.util.List; import java.util.concurrent.TimeUnit; /** * @author: Roman Chernyatchik */ public class SMTestRunnerResultsForm extends TestResultsPanel implements TestFrameworkRunningModel, TestResultsViewer, SMTRunnerEventsListener { @NonNls public static final String HISTORY_DATE_FORMAT = "yyyy.MM.dd 'at' HH'h' mm'm' ss's'"; @NonNls private static final String DEFAULT_SM_RUNNER_SPLITTER_PROPERTY = "SMTestRunner.Splitter.Proportion"; public static final Color DARK_YELLOW = JBColor.YELLOW.darker(); private static final Logger LOG = Logger.getInstance(SMTestRunnerResultsForm.class); private SMTRunnerTestTreeView myTreeView; private TestsProgressAnimator myAnimator; /** * Fake parent suite for all tests and suites */ private final SMTestProxy.SMRootTestProxy myTestsRootNode; private SMTRunnerTreeBuilder myTreeBuilder; private final List<EventsListener> myEventListeners = ContainerUtil.createLockFreeCopyOnWriteList(); private PropagateSelectionHandler myShowStatisticForProxyHandler; private final Project myProject; private int myTotalTestCount = 0; private int myStartedTestCount = 0; private int myFinishedTestCount = 0; private int myFailedTestCount = 0; private int myIgnoredTestCount = 0; private long myStartTime; private long myEndTime; // custom progress private String myCurrentCustomProgressCategory; private final Set<String> myMentionedCategories = new LinkedHashSet<>(); private boolean myTestsRunning = true; private AbstractTestProxy myLastSelected; private final Set<Update> myRequests = Collections.synchronizedSet(new HashSet<Update>()); private boolean myDisposed = false; private SMTestProxy myLastFailed; public SMTestRunnerResultsForm(@NotNull final JComponent console, final TestConsoleProperties consoleProperties) { this(console, AnAction.EMPTY_ARRAY, consoleProperties, null); } public SMTestRunnerResultsForm(@NotNull final JComponent console, AnAction[] consoleActions, final TestConsoleProperties consoleProperties, @Nullable String splitterPropertyName) { super(console, consoleActions, consoleProperties, StringUtil.notNullize(splitterPropertyName, DEFAULT_SM_RUNNER_SPLITTER_PROPERTY), 0.2f); myProject = consoleProperties.getProject(); //Create tests common suite root //noinspection HardCodedStringLiteral myTestsRootNode = new SMTestProxy.SMRootTestProxy(consoleProperties.isPreservePresentableName()); //todo myTestsRootNode.setOutputFilePath(runConfiguration.getOutputFilePath()); // Fire selection changed and move focus on SHIFT+ENTER //TODO[romeo] improve /* final ArrayList<Component> components = new ArrayList<Component>(); components.add(myTreeView); components.add(myTabs.getComponent()); myContentPane.setFocusTraversalPolicy(new MyFocusTraversalPolicy(components)); myContentPane.setFocusCycleRoot(true); */ } protected ToolbarPanel createToolbarPanel() { return new SMTRunnerToolbarPanel(myProperties, this, this); } protected JComponent createTestTreeView() { myTreeView = new SMTRunnerTestTreeView(); myTreeView.setLargeModel(true); myTreeView.attachToModel(this); myTreeView.setTestResultsViewer(this); final SMTRunnerTreeStructure structure = new SMTRunnerTreeStructure(myProject, myTestsRootNode); myTreeBuilder = new SMTRunnerTreeBuilder(myTreeView, structure); myTreeBuilder.setTestsComparator(myProperties); Disposer.register(this, myTreeBuilder); myAnimator = new TestsProgressAnimator(myTreeBuilder); TrackRunningTestUtil.installStopListeners(myTreeView, myProperties, new Pass<AbstractTestProxy>() { @Override public void pass(AbstractTestProxy testProxy) { if (testProxy == null) return; final AbstractTestProxy selectedProxy = testProxy; //drill to the first leaf while (!testProxy.isLeaf()) { final List<? extends AbstractTestProxy> children = testProxy.getChildren(); if (!children.isEmpty()) { final AbstractTestProxy firstChild = children.get(0); if (firstChild != null) { testProxy = firstChild; continue; } } break; } //pretend the selection on the first leaf //so if test would be run, tracking would be restarted myLastSelected = testProxy; //ensure scroll to source on explicit selection only if (ScrollToTestSourceAction.isScrollEnabled(SMTestRunnerResultsForm.this)) { final Navigatable descriptor = TestsUIUtil.getOpenFileDescriptor(selectedProxy, SMTestRunnerResultsForm.this); if (descriptor != null) { OpenSourceUtil.navigate(false, descriptor); } } } }); //TODO always hide root node //myTreeView.setRootVisible(false); return myTreeView; } public void addTestsTreeSelectionListener(final TreeSelectionListener listener) { myTreeView.getSelectionModel().addTreeSelectionListener(listener); } /** * Is used for navigation from tree view to other UI components * * @param handler */ public void setShowStatisticForProxyHandler(final PropagateSelectionHandler handler) { myShowStatisticForProxyHandler = handler; } /** * Returns root node, fake parent suite for all tests and suites * * @param testsRoot * @return */ public void onTestingStarted(@NotNull SMTestProxy.SMRootTestProxy testsRoot) { myAnimator.setCurrentTestCase(myTestsRootNode); myTreeBuilder.updateFromRoot(); // Status line myStatusLine.setStatusColor(ColorProgressBar.GREEN); // Tests tree selectAndNotify(myTestsRootNode); myStartTime = System.currentTimeMillis(); boolean printTestingStartedTime = true; if (myProperties instanceof SMTRunnerConsoleProperties) { printTestingStartedTime = ((SMTRunnerConsoleProperties)myProperties).isPrintTestingStartedTime(); } if (printTestingStartedTime) { myTestsRootNode.addSystemOutput("Testing started at " + DateFormatUtil.formatTime(myStartTime) + " ...\n"); } updateStatusLabel(false); // TODO : show info - "Loading..." msg fireOnTestingStarted(); } public void onTestingFinished(@NotNull SMTestProxy.SMRootTestProxy testsRoot) { myEndTime = System.currentTimeMillis(); if (myTotalTestCount == 0) { myTotalTestCount = myStartedTestCount; myStatusLine.setFraction(1); } updateStatusLabel(true); updateIconProgress(true); myAnimator.stopMovie(); myTreeBuilder.updateFromRoot(); LvcsHelper.addLabel(this); final Runnable onDone = () -> { myTestsRunning = false; final boolean sortByDuration = TestConsoleProperties.SORT_BY_DURATION.value(myProperties); if (sortByDuration) { myTreeBuilder.setTestsComparator(myProperties); } }; if (myLastSelected == null) { selectAndNotify(myTestsRootNode, onDone); } else { onDone.run(); } fireOnTestingFinished(); if (testsRoot.wasTerminated() && myStatusLine.getStatusColor() == ColorProgressBar.GREEN) { myStatusLine.setStatusColor(JBColor.LIGHT_GRAY); } if (testsRoot.isEmptySuite() && testsRoot.isTestsReporterAttached() && myProperties instanceof SMTRunnerConsoleProperties && ((SMTRunnerConsoleProperties)myProperties).fixEmptySuite()) { return; } final TestsUIUtil.TestResultPresentation presentation = new TestsUIUtil.TestResultPresentation(testsRoot, myStartTime > 0, null) .getPresentation(myFailedTestCount, Math.max(0, myFinishedTestCount - myFailedTestCount - myIgnoredTestCount), myTotalTestCount - (myFinishedTestCount - myIgnoredTestCount), myIgnoredTestCount); TestsUIUtil.notifyByBalloon(myProperties.getProject(), testsRoot, myProperties, presentation); addToHistory(testsRoot, myProperties, this); } private void addToHistory(final SMTestProxy.SMRootTestProxy root, TestConsoleProperties consoleProperties, Disposable parentDisposable) { final RunProfile configuration = consoleProperties.getConfiguration(); if (configuration instanceof RunConfiguration && !(consoleProperties instanceof ImportedTestConsoleProperties) && !ApplicationManager.getApplication().isUnitTestMode() && !myDisposed) { final MySaveHistoryTask backgroundable = new MySaveHistoryTask(consoleProperties, root, (RunConfiguration)configuration); final BackgroundableProcessIndicator processIndicator = new BackgroundableProcessIndicator(backgroundable); Disposer.register(parentDisposable, new Disposable() { @Override public void dispose() { processIndicator.cancel(); backgroundable.dispose(); } }); Disposer.register(parentDisposable, processIndicator); ProgressManager.getInstance().runProcessWithProgressAsynchronously(backgroundable, processIndicator); } } public void onTestsCountInSuite(final int count) { updateCountersAndProgressOnTestCount(count, false); } /** * Adds test to tree and updates status line. * Test proxy should be initialized, proxy parent must be some suite (already added to tree) * * @param testProxy Proxy */ public void onTestStarted(@NotNull final SMTestProxy testProxy) { if (!testProxy.isConfig()) { updateOnTestStarted(false); } _addTestOrSuite(testProxy); fireOnTestNodeAdded(testProxy); } @Override public void onSuiteTreeNodeAdded(SMTestProxy testProxy) { myTotalTestCount++; } @Override public void onSuiteTreeStarted(SMTestProxy suite) { } public void onTestFailed(@NotNull final SMTestProxy test) { if (Comparing.equal(test, myLastFailed)) return; myLastFailed = test; updateOnTestFailed(false); if (test.isConfig()) { myStartedTestCount++; myFinishedTestCount++; } updateIconProgress(false); //still expand failure when user selected another test if (myLastSelected != null && TestConsoleProperties.TRACK_RUNNING_TEST.value(myProperties) && TestConsoleProperties.HIDE_PASSED_TESTS.value(myProperties)) { myTreeBuilder.expand(test, null); } } public void onTestIgnored(@NotNull final SMTestProxy test) { updateOnTestIgnored(); } /** * Adds suite to tree * Suite proxy should be initialized, proxy parent must be some suite (already added to tree) * If parent is null, then suite will be added to tests root. * * @param newSuite Tests suite */ public void onSuiteStarted(@NotNull final SMTestProxy newSuite) { _addTestOrSuite(newSuite); } public void onCustomProgressTestsCategory(@Nullable String categoryName, int testCount) { myCurrentCustomProgressCategory = categoryName; updateCountersAndProgressOnTestCount(testCount, true); } public void onCustomProgressTestStarted() { updateOnTestStarted(true); } public void onCustomProgressTestFailed() { updateOnTestFailed(true); } @Override public void onCustomProgressTestFinished() { updateOnTestFinished(true); } public void onTestFinished(@NotNull final SMTestProxy test) { if (!test.isConfig()) { updateOnTestFinished(false); } updateIconProgress(false); } public void onSuiteFinished(@NotNull final SMTestProxy suite) { //Do nothing } public SMTestProxy.SMRootTestProxy getTestsRootNode() { return myTestsRootNode; } public TestConsoleProperties getProperties() { return myProperties; } public void setFilter(final Filter filter) { // is used by Test Runner actions, e.g. hide passed, etc final SMTRunnerTreeStructure treeStructure = myTreeBuilder.getSMRunnerTreeStructure(); treeStructure.setFilter(filter); // TODO - show correct info if no children are available // (e.g no tests found or all tests passed, etc.) // treeStructure.getChildElements(treeStructure.getRootElement()).length == 0 myTreeBuilder.queueUpdate(); } public boolean isRunning() { return myTestsRunning; } public TestTreeView getTreeView() { return myTreeView; } @Override public SMTRunnerTreeBuilder getTreeBuilder() { return myTreeBuilder; } public boolean hasTestSuites() { return getRoot().getChildren().size() > 0; } @NotNull public AbstractTestProxy getRoot() { return myTestsRootNode; } /** * Manual test proxy selection in tests tree. E.g. do select root node on * testing started or do select current node if TRACK_RUNNING_TEST is enabled * <p/> * <p/> * Will select proxy in Event Dispatch Thread. Invocation of this * method may be not in event dispatch thread * * @param testProxy Test or suite */ @Override public void selectAndNotify(AbstractTestProxy testProxy) { selectAndNotify(testProxy, null); } private void selectAndNotify(@Nullable final AbstractTestProxy testProxy, @Nullable Runnable onDone) { selectWithoutNotify(testProxy, onDone); } public void addEventsListener(final EventsListener listener) { myEventListeners.add(listener); addTestsTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(final TreeSelectionEvent e) { //We should fire event only if it was generated by this component, //e.g. it is focused. Otherwise it is side effect of selecting proxy in //try by other component //if (myTreeView.isFocusOwner()) { @Nullable final SMTestProxy selectedProxy = (SMTestProxy)getTreeView().getSelectedTest(); listener.onSelected(selectedProxy, SMTestRunnerResultsForm.this, SMTestRunnerResultsForm.this); //} } }); } public void dispose() { super.dispose(); myShowStatisticForProxyHandler = null; myEventListeners.clear(); myDisposed = true; } public void showStatisticsForSelectedProxy() { TestConsoleProperties.SHOW_STATISTICS.set(myProperties, true); final AbstractTestProxy selectedProxy = myTreeView.getSelectedTest(); showStatisticsForSelectedProxy(selectedProxy, true); } private void showStatisticsForSelectedProxy(final AbstractTestProxy selectedProxy, final boolean requestFocus) { if (selectedProxy instanceof SMTestProxy && myShowStatisticForProxyHandler != null) { myShowStatisticForProxyHandler.handlePropagateSelectionRequest((SMTestProxy)selectedProxy, this, requestFocus); } } protected int getTotalTestCount() { return myTotalTestCount; } protected int getStartedTestCount() { return myStartedTestCount; } protected int getFinishedTestCount() { return myFinishedTestCount; } protected int getFailedTestCount() { return myFailedTestCount; } protected int getIgnoredTestCount() { return myIgnoredTestCount; } protected Color getTestsStatusColor() { return myStatusLine.getStatusColor(); } public Set<String> getMentionedCategories() { return myMentionedCategories; } protected long getStartTime() { return myStartTime; } protected long getEndTime() { return myEndTime; } private void _addTestOrSuite(@NotNull final SMTestProxy newTestOrSuite) { final SMTestProxy parentSuite = newTestOrSuite.getParent(); assert parentSuite != null; // Tree final Update update = new Update(parentSuite) { @Override public void run() { myRequests.remove(this); myTreeBuilder.updateTestsSubtree(parentSuite); } }; if (ApplicationManager.getApplication().isUnitTestMode()) { update.run(); } else if (myRequests.add(update) && !myDisposed) { JobScheduler.getScheduler().schedule(update, 100, TimeUnit.MILLISECONDS); } myTreeBuilder.repaintWithParents(newTestOrSuite); myAnimator.setCurrentTestCase(newTestOrSuite); if (TestConsoleProperties.TRACK_RUNNING_TEST.value(myProperties)) { if (myLastSelected == null || myLastSelected == newTestOrSuite) { myLastSelected = null; selectAndNotify(newTestOrSuite); } } } private void fireOnTestNodeAdded(final SMTestProxy test) { for (EventsListener eventListener : myEventListeners) { eventListener.onTestNodeAdded(this, test); } } private void fireOnTestingFinished() { for (EventsListener eventListener : myEventListeners) { eventListener.onTestingFinished(this); } } private void fireOnTestingStarted() { for (EventsListener eventListener : myEventListeners) { eventListener.onTestingStarted(this); } } private void selectWithoutNotify(final AbstractTestProxy testProxy, @Nullable final Runnable onDone) { if (testProxy == null) { return; } SMRunnerUtil.runInEventDispatchThread(() -> { if (myTreeBuilder.isDisposed()) { return; } myTreeBuilder.select(testProxy, onDone); }, ModalityState.NON_MODAL); } private void updateStatusLabel(final boolean testingFinished) { if (myFailedTestCount > 0) { myStatusLine.setStatusColor(ColorProgressBar.RED); } if (testingFinished) { if (myTotalTestCount == 0) { myStatusLine.setStatusColor(myTestsRootNode.wasLaunched() || !myTestsRootNode.isTestsReporterAttached() ? JBColor.LIGHT_GRAY : ColorProgressBar.RED); } // else color will be according failed/passed tests } // launchedAndFinished - is launched and not in progress. If we remove "launched' that onTestingStarted() before // initializing will be "launchedAndFinished" final boolean launchedAndFinished = myTestsRootNode.wasLaunched() && !myTestsRootNode.isInProgress(); if (!TestsPresentationUtil.hasNonDefaultCategories(myMentionedCategories)) { myStatusLine.formatTestMessage(isUndefined() ? -1 : myTotalTestCount, myFinishedTestCount, myFailedTestCount, myIgnoredTestCount, myTestsRootNode.getDuration(), myEndTime); } else { myStatusLine.setText(TestsPresentationUtil.getProgressStatus_Text(myStartTime, myEndTime, myTotalTestCount, myFinishedTestCount, myFailedTestCount, myMentionedCategories, launchedAndFinished)); } } private boolean isUndefined() { return myProperties instanceof SMTRunnerConsoleProperties && ((SMTRunnerConsoleProperties)myProperties).isUndefined(); } /** * for java unit tests */ public void performUpdate() { myTreeBuilder.performUpdate(); } private void updateIconProgress(boolean updateWithAttention) { final int totalTestCount, doneTestCount; if (myTotalTestCount == 0) { totalTestCount = 2; doneTestCount = 1; } else { totalTestCount = myTotalTestCount; doneTestCount = myFinishedTestCount; } TestsUIUtil.showIconProgress(myProject, doneTestCount, totalTestCount, myFailedTestCount, updateWithAttention); } /** * On event change selection and probably requests focus. Is used when we want * navigate from other component to this * * @return Listener */ public PropagateSelectionHandler createSelectMeListener() { return new PropagateSelectionHandler() { public void handlePropagateSelectionRequest(@Nullable final SMTestProxy selectedTestProxy, @NotNull final Object sender, final boolean requestFocus) { SMRunnerUtil.addToInvokeLater(() -> { selectWithoutNotify(selectedTestProxy, null); // Request focus if necessary if (requestFocus) { //myTreeView.requestFocusInWindow(); IdeFocusManager.getInstance(myProject).requestFocus(myTreeView, true); } }); } }; } private void updateCountersAndProgressOnTestCount(final int count, final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; //This is for better support groups of TestSuites //Each group notifies about it's size myTotalTestCount += count; updateStatusLabel(false); } private void updateOnTestStarted(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; // for mixed tests results : mention category only if it contained tests myMentionedCategories .add(myCurrentCustomProgressCategory != null ? myCurrentCustomProgressCategory : TestsPresentationUtil.DEFAULT_TESTS_CATEGORY); myStartedTestCount++; // fix total count if it is corrupted // but if test count wasn't set at all let's process such case separately if (myStartedTestCount > myTotalTestCount && myTotalTestCount != 0) { myTotalTestCount = myStartedTestCount; } updateStatusLabel(false); } private void updateProgressOnTestDone() { int doneTestCount = myFinishedTestCount; // update progress if (isUndefined()) { myStatusLine.setFraction(1.0); } else if (myTotalTestCount != 0) { // if total is set myStatusLine.setFraction((double) doneTestCount / myTotalTestCount); } else { // if at least one test was launcher than just set progress in the middle to show user that tests are running myStatusLine.setFraction(doneTestCount > 0 ? 0.5 : 0); } } private void updateOnTestFailed(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; myFailedTestCount++; updateProgressOnTestDone(); updateStatusLabel(false); } private void updateOnTestFinished(final boolean isCustomMessage) { if (!isModeConsistent(isCustomMessage)) return; myFinishedTestCount++; updateProgressOnTestDone(); } private void updateOnTestIgnored() { myIgnoredTestCount++; updateProgressOnTestDone(); updateStatusLabel(false); } private boolean isModeConsistent(boolean isCustomMessage) { // check that we are in consistent mode return isCustomMessage != (myCurrentCustomProgressCategory == null); } private static class MySaveHistoryTask extends Task.Backgroundable { private final TestConsoleProperties myConsoleProperties; private SMTestProxy.SMRootTestProxy myRoot; private RunConfiguration myConfiguration; private File myOutputFile; public MySaveHistoryTask(TestConsoleProperties consoleProperties, SMTestProxy.SMRootTestProxy root, RunConfiguration configuration) { super(consoleProperties.getProject(), "Save Test Results", true); myConsoleProperties = consoleProperties; myRoot = root; myConfiguration = configuration; } @Override public void run(@NotNull ProgressIndicator indicator) { writeState(); DaemonCodeAnalyzer.getInstance(getProject()).restart(); try { SAXTransformerFactory transformerFactory = (SAXTransformerFactory)TransformerFactory.newInstance(); TransformerHandler handler = transformerFactory.newTransformerHandler(); handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes"); handler.getTransformer().setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); final String configurationNameIncludedDate = PathUtil.suggestFileName(myConfiguration.getName()) + " - " + new SimpleDateFormat(HISTORY_DATE_FORMAT).format(new Date()); myOutputFile = new File(TestStateStorage.getTestHistoryRoot(myProject), configurationNameIncludedDate + ".xml"); FileUtilRt.createParentDirs(myOutputFile); handler.setResult(new StreamResult(new FileWriter(myOutputFile))); final SMTestProxy.SMRootTestProxy root = myRoot; final RunConfiguration configuration = myConfiguration; if (root != null && configuration != null) { TestResultsXmlFormatter.execute(root, configuration, myConsoleProperties, handler); } } catch (ProcessCanceledException e) { throw e; } catch (Exception e) { LOG.info("Export to history failed", e); } } private void writeState() { // read action to prevent project (and storage) from being disposed ApplicationManager.getApplication().runReadAction(() -> { Project project = getProject(); if (project.isDisposed() || myRoot == null) return; TestStateStorage storage = TestStateStorage.getInstance(project); List<SMTestProxy> tests = myRoot.getAllTests(); for (SMTestProxy proxy : tests) { String url = proxy instanceof SMTestProxy.SMRootTestProxy ? ((SMTestProxy.SMRootTestProxy)proxy).getRootLocation() : proxy.getLocationUrl(); if (url != null) { String configurationName = myConfiguration != null ? myConfiguration.getName() : null; storage.writeState(url, new TestStateStorage.Record(proxy.getMagnitude(), new Date(), configurationName == null ? 0 : configurationName.hashCode())); } } }); } @Override public void onSuccess() { if (myOutputFile != null && myOutputFile.exists()) { AbstractImportTestsAction.adjustHistory(myProject); TestHistoryConfiguration.getInstance(myProject).registerHistoryItem(myOutputFile.getName(), myConfiguration.getName(), myConfiguration.getType().getId()); } } public void dispose() { myConfiguration = null; myRoot = null; myOutputFile = null; } } }