diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/history/actions/ImportTestResultsDialog.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/history/actions/ImportTestResultsDialog.java new file mode 100644 index 000000000000..ed47bc8bf18f --- /dev/null +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/history/actions/ImportTestResultsDialog.java @@ -0,0 +1,152 @@ +/* + * 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.history.actions; + +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.LabeledComponent; +import com.intellij.openapi.ui.TextFieldWithBrowseButton; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.ui.*; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBRadioButton; +import com.intellij.ui.components.JBTextField; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.ArrayList; + +public class ImportTestResultsDialog extends DialogWrapper { + private final TextFieldWithBrowseButton myFileFieldComp; + private final JBTextField myHistorySizeField = new JBTextField(); + private final JBList myList; + private final JRadioButton myFileRb = new JBRadioButton("From File:"); + private final JRadioButton myHistoryRb = new JBRadioButton("From History:"); + + + protected ImportTestResultsDialog(@Nullable Project project) { + super(project, false); + setTitle("Import Test Results"); + final FileChooserDescriptor xmlDescriptor = FileChooserDescriptorFactory.createSingleFileDescriptor(StdFileTypes.XML); + myFileFieldComp = new TextFieldWithBrowseButton(); + myFileFieldComp.addBrowseFolderListener("Choose a File with Tests Result", null, project, xmlDescriptor); + + final DefaultListModel model = new DefaultListModel(); + final String[] historyFiles = ImportTestsAction.TEST_HISTORY_PATH.list(); + if (historyFiles != null) { + for (String fileName : historyFiles) { + model.addElement(fileName); + } + } + myList = new JBList(model); + myList.setSelectedIndex(0); + + final ButtonGroup group = new ButtonGroup(); + group.add(myFileRb); + group.add(myHistoryRb); + + init(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + final JPanel wholePanel = new JPanel(new BorderLayout()); + + final JPanel panelWithFileTextField = new JPanel(new BorderLayout()); + panelWithFileTextField.add(myFileRb, BorderLayout.NORTH); + panelWithFileTextField.add(myFileFieldComp, BorderLayout.CENTER); + + final JPanel panelWithHistory = new JPanel(new BorderLayout()); + panelWithHistory.add(myHistoryRb, BorderLayout.NORTH); + final JPanel historyPanel = new JPanel(new BorderLayout()); + final ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myList) + .disableUpAction() + .disableDownAction() + .disableAddAction() + .setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + ListUtil.removeSelectedItems(myList); + } + }); + historyPanel.add(decorator.createPanel(), BorderLayout.CENTER); + + myHistorySizeField.setText(PropertiesComponent.getInstance().getValue(ImportTestsAction.TEST_HISTORY_SIZE, "5")); + final LabeledComponent sizeComponent = new LabeledComponent(); + sizeComponent.setText("History size:"); + sizeComponent.setComponent(myHistorySizeField); + sizeComponent.setLabelLocation(BorderLayout.WEST); + + historyPanel.add(sizeComponent, BorderLayout.SOUTH); + panelWithHistory.add(historyPanel, BorderLayout.CENTER); + + wholePanel.add(panelWithFileTextField, BorderLayout.NORTH); + wholePanel.add(panelWithHistory, BorderLayout.CENTER); + + final ActionListener enableListener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateComponents(historyPanel); + } + }; + myFileRb.addActionListener(enableListener); + myHistoryRb.addActionListener(enableListener); + final boolean hasHistory = !myList.isEmpty(); + myFileRb.setSelected(!hasHistory); + myHistoryRb.setSelected(hasHistory); + updateComponents(historyPanel); + return wholePanel; + } + + private void updateComponents(JPanel historyPanel) { + UIUtil.setEnabled(historyPanel, !myFileRb.isSelected(), true); + UIUtil.setEnabled(myFileFieldComp, !myHistoryRb.isSelected(), true); + } + + @Override + protected void doOKAction() { + PropertiesComponent.getInstance().setValue(ImportTestsAction.TEST_HISTORY_SIZE, myHistorySizeField.getText().trim()); + final String[] historyFiles = ImportTestsAction.TEST_HISTORY_PATH.list(); + final DefaultListModel model = (DefaultListModel)myList.getModel(); + final ArrayList toDelete = new ArrayList(); + for (String fileName : historyFiles) { + if (!model.contains(fileName)) { + toDelete.add(new File(ImportTestsAction.TEST_HISTORY_PATH, fileName)); + } + } + for (File file : toDelete) { + FileUtil.delete(file); + } + super.doOKAction(); + + } + + @Nullable + public String getFilePath() { + return myFileRb.isSelected() ? myFileFieldComp.getText() + : FileUtil.toSystemIndependentName(ImportTestsAction.TEST_HISTORY_PATH.getPath()) + "/" + myList.getSelectedValue(); + } +} diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/history/actions/ImportTestsAction.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/history/actions/ImportTestsAction.java index 1aeb1f8e24fe..2115fe021467 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/history/actions/ImportTestsAction.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/history/actions/ImportTestsAction.java @@ -30,8 +30,10 @@ import com.intellij.execution.testframework.sm.runner.history.ImportedTestRunnab import com.intellij.execution.testframework.sm.runner.SMRunnerConsolePropertiesProvider; import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties; import com.intellij.icons.AllIcons; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; @@ -41,6 +43,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import org.jdom.Document; @@ -49,6 +53,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.io.File; +import java.util.Arrays; +import java.util.Comparator; /** * 1. chooses file where test results were saved @@ -59,6 +66,8 @@ import javax.swing.*; */ public class ImportTestsAction extends AnAction { private static final Logger LOG = Logger.getInstance("#" + ImportTestsAction.class.getName()); + public static final File TEST_HISTORY_PATH = new File(PathManager.getSystemPath(), "testHistory"); + public static final String TEST_HISTORY_SIZE = "test_history_size"; private SMTRunnerConsoleProperties myProperties; public ImportTestsAction() { @@ -77,11 +86,17 @@ public class ImportTestsAction extends AnAction { @Override public void actionPerformed(AnActionEvent e) { - final FileChooserDescriptor xmlDescriptor = FileChooserDescriptorFactory.createSingleFileDescriptor(StdFileTypes.XML); - xmlDescriptor.setTitle("Choose a File with Tests Result"); final Project project = e.getProject(); LOG.assertTrue(project != null); - final VirtualFile file = FileChooser.chooseFile(xmlDescriptor, project, null); + final ImportTestResultsDialog dialog = new ImportTestResultsDialog(project); + if (!dialog.showAndGet()) { + return; + } + final String filePath = dialog.getFilePath(); + if (filePath == null) { + return; + } + final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(filePath); if (file != null) { try { final ImportRunProfile profile = new ImportRunProfile(file, project); @@ -100,6 +115,30 @@ public class ImportTestsAction extends AnAction { } } } + + public static void adjustHistory() { + int historySize; + try { + historySize = Math.max(0, Integer.parseInt(PropertiesComponent.getInstance().getValue(TEST_HISTORY_SIZE, "5"))); + } + catch (NumberFormatException e) { + historySize = 5; + } + + final File[] files = TEST_HISTORY_PATH.listFiles(); + if (files != null && files.length >= historySize) { + Arrays.sort(files, new Comparator() { + @Override + public int compare(File o1, File o2) { + final long l1 = o1.lastModified(); + final long l2 = o2.lastModified(); + if (l1 == l2) return 0; + return l1 < l2 ? -1 : 1; + } + }); + FileUtil.delete(files[0]); + } + } public static class ImportRunProfile implements RunProfile { private final VirtualFile myFile; diff --git a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java index 9d5f5c4b0a61..5631d4b0f219 100644 --- a/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java +++ b/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/SMTestRunnerResultsForm.java @@ -15,24 +15,38 @@ */ package com.intellij.execution.testframework.sm.runner.ui; +import com.intellij.execution.ExecutionBundle; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.testframework.*; +import com.intellij.execution.testframework.export.TestResultsXmlFormatter; import com.intellij.execution.testframework.sm.SMRunnerUtil; 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.ImportTestsAction; import com.intellij.execution.testframework.sm.runner.ui.statistics.StatisticsPanel; import com.intellij.execution.testframework.ui.TestResultsPanel; import com.intellij.execution.testframework.ui.TestsProgressAnimator; 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.Disposer; import com.intellij.openapi.util.Pass; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.JBColor; import com.intellij.util.Alarm; +import com.intellij.util.PathUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.update.Update; @@ -43,9 +57,18 @@ 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.awt.event.InputEvent; import java.awt.event.KeyEvent; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.text.SimpleDateFormat; import java.util.*; import java.util.List; @@ -57,6 +80,7 @@ public class SMTestRunnerResultsForm extends TestResultsPanel @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.getName()); private SMTRunnerTestTreeView myTreeView; @@ -91,6 +115,7 @@ public class SMTestRunnerResultsForm extends TestResultsPanel private AbstractTestProxy myLastSelected; private Alarm myUpdateQueue; private Set myRequests = Collections.synchronizedSet(new HashSet()); + private BackgroundableProcessIndicator mySaveToHistoryIndicator; public SMTestRunnerResultsForm(@NotNull final JComponent console, final TestConsoleProperties consoleProperties) { @@ -303,6 +328,56 @@ public class SMTestRunnerResultsForm extends TestResultsPanel final TestsUIUtil.TestResultPresentation presentation = new TestsUIUtil.TestResultPresentation(testsRoot, myStartTime > 0, null) .getPresentation(myFailedTestCount, myFinishedTestCount - myFailedTestCount - myIgnoredTestCount, myTotalTestCount - myFinishedTestCount, myIgnoredTestCount); TestsUIUtil.notifyByBalloon(myConsoleProperties.getProject(), testsRoot, myConsoleProperties, presentation); + addToHistory(testsRoot); + } + + private void addToHistory(final SMTestProxy.SMRootTestProxy root) { + final RunProfile configuration = myConsoleProperties.getConfiguration(); + if (configuration instanceof RunConfiguration && !(myConsoleProperties instanceof ImportedTestConsoleProperties)) { + final Task.Backgroundable backgroundable = new Task.Backgroundable(myProject, "Save Test Results", true) { + + private String myOutput; + + @Override + public void run(@NotNull ProgressIndicator indicator) { + 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"); + + StringWriter w = new StringWriter(); + handler.setResult(new StreamResult(w)); + TestResultsXmlFormatter.execute(root, (RunConfiguration)configuration, handler); + myOutput = w.toString(); + } + catch (ProcessCanceledException ignore) { + //canceled by user or frame dispose + } + catch (Exception e) { + LOG.info("Export to history failed", e); + } + } + + @Override + public void onSuccess() { + if (myOutput != null) { + try { + ImportTestsAction.adjustHistory(); + final String configurationNameIncludedDate = PathUtil.suggestFileName(configuration.getName()) + " " + + FileUtil.sanitizeFileName(new SimpleDateFormat().format(new Date())); + final String presentableFileName = ExecutionBundle.message("export.test.results.filename", configurationNameIncludedDate); + FileUtil.writeToFile(new File(ImportTestsAction.TEST_HISTORY_PATH, presentableFileName + ".xml"), myOutput); + } + catch (IOException e) { + LOG.info("Fail to write test history", e); + } + } + } + }; + mySaveToHistoryIndicator = new BackgroundableProcessIndicator(backgroundable); + ProgressManager.getInstance().runProcessWithProgressAsynchronously(backgroundable, mySaveToHistoryIndicator); + } } public void onTestsCountInSuite(final int count) { @@ -464,6 +539,9 @@ public class SMTestRunnerResultsForm extends TestResultsPanel myShowStatisticForProxyHandler = null; myEventListeners.clear(); myStatisticsPane.doDispose(); + if (mySaveToHistoryIndicator != null) { + mySaveToHistoryIndicator.cancel(); + } } public void showStatisticsForSelectedProxy() { diff --git a/platform/testRunner/src/com/intellij/execution/testframework/export/ExportTestResultsAction.java b/platform/testRunner/src/com/intellij/execution/testframework/export/ExportTestResultsAction.java index 79f78e99b10e..644ddfa40a67 100644 --- a/platform/testRunner/src/com/intellij/execution/testframework/export/ExportTestResultsAction.java +++ b/platform/testRunner/src/com/intellij/execution/testframework/export/ExportTestResultsAction.java @@ -29,10 +29,7 @@ import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.progress.PerformInBackgroundOption; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.*; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; @@ -309,7 +306,12 @@ public class ExportTestResultsAction extends DumbAwareAction { StringWriter w = new StringWriter(); handler.setResult(new StreamResult(w)); - TestResultsXmlFormatter.execute(myModel.getRoot(), myRunConfiguration, handler); + try { + TestResultsXmlFormatter.execute(myModel.getRoot(), myRunConfiguration, handler); + } + catch (ProcessCanceledException e) { + return null; + } return w.toString(); } diff --git a/platform/testRunner/src/com/intellij/execution/testframework/export/TestResultsXmlFormatter.java b/platform/testRunner/src/com/intellij/execution/testframework/export/TestResultsXmlFormatter.java index cedadab94e15..5ab983e77cb9 100644 --- a/platform/testRunner/src/com/intellij/execution/testframework/export/TestResultsXmlFormatter.java +++ b/platform/testRunner/src/com/intellij/execution/testframework/export/TestResultsXmlFormatter.java @@ -16,7 +16,6 @@ package com.intellij.execution.testframework.export; import com.intellij.execution.ExecutionBundle; -import com.intellij.execution.RunnerAndConfigurationSettings; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.filters.*; import com.intellij.execution.impl.RunManagerImpl; @@ -28,7 +27,7 @@ import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; @@ -166,6 +165,7 @@ public class TestResultsXmlFormatter { } private void processNode(AbstractTestProxy node, final Filter filter) throws SAXException { + ProgressManager.checkCanceled(); Map attrs = new HashMap(); attrs.put(ATTR_NAME, node.getName()); attrs.put(ATTR_STATUS, getStatusString(node));