This allows exporting to tsv files.
Testing is done with reflection to avoid native dialog issues.
Some issues are present:
* In Linux, an invisible column is added at the end,
while this seems ok, it makes the output inconsistent
between Windows and Linux.
* SWTBot cannot test native windows, therefore, the user actual
actions are untested. All possible logic has been moved
elsewhere to accomodate.
* Tree and Table have no common parents, this will incur code
duplication due to their similarities.
* In Linux, tooltips are not well handled in menu items.
Change-Id: I506d0e7e50bd5ce8ecbd44675a0c404ab2dda431
Signed-off-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Reviewed-on: https://git.eclipse.org/r/82111
Reviewed-by: Hudson CI
Reviewed-by: Genevieve Bastien <gbastien+lttng@versatic.net>
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.List;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.results.BoolResult;
import org.eclipse.swtbot.swt.finder.results.Result;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
+import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
+import org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.statistics.AbstractSegmentStoreStatisticsView;
import org.eclipse.tracecompass.internal.analysis.os.linux.ui.views.latency.statistics.SystemCallLatencyStatisticsView;
import org.eclipse.tracecompass.testtraces.ctf.CtfTestTrace;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.ConditionHelpers;
*
* @throws IOException
* trace not found?
+ * @throws SecurityException
+ * Reflection error
+ * @throws NoSuchMethodException
+ * Reflection error
+ * @throws IllegalArgumentException
+ * Reflection error
*/
@Test
- public void testWithTrace() throws IOException {
+ public void testWithTrace() throws IOException, NoSuchMethodException, SecurityException, IllegalArgumentException {
String tracePath;
tracePath = FileLocator.toFileURL(CtfTestTrace.ARM_64_BIT_HEADER.getTraceURL()).getPath();
SWTWorkbenchBot bot = new SWTWorkbenchBot();
validate(treeItem.getNode(3), "poll", "6.300 µs", "6.800 µs", "6.550 µs", "---", "2");
validate(treeItem.getNode(5), "set_tid_address", "2.300 µs", "2.300 µs", "2.300 µs", "---", "1");
validate(treeItem.getNode(7), "pipe", "27.900 µs", "29.700 µs", "28.800 µs", "---", "2");
+ testToTsv(view);
+ SWTBotMenu menuBot = view.viewMenu().menu("Export to TSV");
+ assertTrue(menuBot.isEnabled());
+ assertTrue(menuBot.isVisible());
bot.closeAllEditors();
SWTBotUtils.deleteProject(PROJECT_NAME, bot);
}
+ private static void testToTsv(SWTBotView view) throws NoSuchMethodException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ assertNotNull(os);
+ IViewPart viewPart = view.getReference().getView(true);
+ assertTrue(viewPart instanceof SystemCallLatencyStatisticsView);
+ Class<@NonNull AbstractSegmentStoreStatisticsView> clazz = AbstractSegmentStoreStatisticsView.class;
+ Method method = clazz.getDeclaredMethod("exportToTsv", java.io.OutputStream.class);
+ method.setAccessible(true);
+ final Exception[] except = new Exception[1];
+ UIThreadRunnable.syncExec(() -> {
+ try {
+ method.invoke((SystemCallLatencyStatisticsView) viewPart, os);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ except[0] = e;
+ }
+ });
+ assertNull(except[0]);
+ @SuppressWarnings("null")
+ String[] lines = String.valueOf(os).split(System.getProperty("line.separator"));
+ assertNotNull(lines);
+ assertEquals("header", "Level\tMinimum\tMaximum\tAverage\tStandard Deviation\tCount\tTotal", lines[0]);
+ assertEquals("line 1", "Total\t1.000 µs\t5.904 s\t15.628 ms\t175.875 ms\t1801\t28.146 s", lines[1]);
+
+ }
+
private static void validate(SWTBotTreeItem treeItem, final String nodeName, final String min, final String max, final String avg, final String stdev, final String count) {
assertEquals(nodeName, treeItem.cell(0));
assertEquals(min, treeItem.cell(MIN_COL));
package org.eclipse.tracecompass.analysis.os.linux.ui.swtbot.tests.latency;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.eclipse.swtbot.swt.finder.results.BoolResult;
import org.eclipse.swtbot.swt.finder.results.Result;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
+import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable;
+import org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.table.AbstractSegmentStoreTableView;
import org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.table.AbstractSegmentStoreTableViewer;
import org.eclipse.tracecompass.internal.analysis.os.linux.ui.views.latency.SystemCallLatencyView;
import org.eclipse.tracecompass.segmentstore.core.BasicSegment;
bot.waitUntil(ConditionHelpers.isTableCellFilled(tableBot, "998,001", 0, 2));
}
+ /**
+ * Test creating a tsv
+ *
+ * @throws NoSuchMethodException
+ * Error creating the tsv
+ */
+ @Test
+ public void testWriteToTsv() throws NoSuchMethodException {
+ List<@NonNull BasicSegment> fixture = new ArrayList<>();
+ for (int i = 1; i <= 20; i++) {
+ int start = i;
+ final int delta = i;
+ int end = start + delta * delta;
+ fixture.add(new BasicSegment(start, end));
+ }
+ assertNotNull(fTable);
+ fTable.updateModel(fixture);
+ SWTBotTable tableBot = new SWTBotTable(fTable.getTableViewer().getTable());
+ SWTBot bot = new SWTBot();
+ bot.waitUntil(ConditionHelpers.isTableCellFilled(tableBot, "1", 0, 2));
+ SWTWorkbenchBot swtWorkbenchBot = new SWTWorkbenchBot();
+ SWTBotView viewBot = swtWorkbenchBot.viewById(VIEW_ID);
+ testToTsv(viewBot);
+ SWTBotMenu menuBot = viewBot.viewMenu().menu("Export to TSV");
+ assertTrue(menuBot.isEnabled());
+ assertTrue(menuBot.isVisible());
+ }
+
+ private void testToTsv(SWTBotView view) throws NoSuchMethodException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ assertNotNull(os);
+ Class<@NonNull AbstractSegmentStoreTableView> clazz = AbstractSegmentStoreTableView.class;
+ Method method = clazz.getDeclaredMethod("exportToTsv", java.io.OutputStream.class);
+ method.setAccessible(true);
+ final Exception[] except = new Exception[1];
+ UIThreadRunnable.syncExec(() -> {
+ try {
+ method.invoke(fLatencyView, os);
+ } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
+ except[0] = e;
+ }
+ });
+ assertNull(except[0]);
+ @SuppressWarnings("null")
+ String[] lines = String.valueOf(os).split(System.getProperty("line.separator"));
+ assertNotNull(lines);
+ assertEquals("number of lines", 21, lines.length);
+ assertEquals("header", "Start Time\tEnd Time\tDuration", lines[0]);
+ // not a straight up string compare due to time zones. Kathmandu and Eucla have 15 minute time zones.
+ assertTrue("line 1", lines[1].matches("\\d\\d:\\d\\d:00\\.000\\s000\\s001\\t\\d\\d:\\d\\d:00.000 000 002\\t1"));
+ }
+
/**
* Test with an actual trace, this is more of an integration test than a
* unit test. This test is a slow one too. If some analyses are not well
*******************************************************************************/
package org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.statistics;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Tree;
+import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.tracecompass.common.core.NonNullUtils;
+import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.ExportToTsvAction;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.ui.views.TmfView;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+
/**
* Abstract view to to be extended to display segment store statistics.
*
*/
public abstract class AbstractSegmentStoreStatisticsView extends TmfView {
- @Nullable private AbstractSegmentStoreStatisticsViewer fStatsViewer = null;
+ private final Action fExportAction = new ExportToTsvAction() {
+ @Override
+ protected void exportToTsv(@Nullable OutputStream stream) {
+ AbstractSegmentStoreStatisticsView.this.exportToTsv(stream);
+ }
+
+ @Override
+ protected @Nullable Shell getShell() {
+ return getViewSite().getShell();
+ }
+
+ };
+
+ private @Nullable AbstractSegmentStoreStatisticsViewer fStatsViewer = null;
/**
* Constructor
statsViewer.loadTrace(trace);
}
fStatsViewer = statsViewer;
+ getViewSite().getActionBars().getMenuManager().add(fExportAction);
}
@Override
*/
protected abstract AbstractSegmentStoreStatisticsViewer createSegmentStoreStatisticsViewer(Composite parent);
+ /**
+ * Export a given items's TSV
+ *
+ * @param stream
+ * an output stream to write the TSV to
+ * @since 1.2
+ */
+ @VisibleForTesting
+ protected void exportToTsv(@Nullable OutputStream stream) {
+ try (PrintWriter pw = new PrintWriter(stream)) {
+ AbstractSegmentStoreStatisticsViewer statsViewer = fStatsViewer;
+ if (statsViewer == null) {
+ return;
+ }
+ Tree tree = statsViewer.getTreeViewer().getTree();
+ int size = tree.getItemCount();
+ List<String> columns = new ArrayList<>();
+ for (int i = 0; i < tree.getColumnCount(); i++) {
+ String valueOf = String.valueOf(tree.getColumn(i).getText());
+ if (valueOf.isEmpty() && i == tree.getColumnCount() - 1) {
+ // Linux "feature", an invisible column is added at the end
+ // with gtk2
+ break;
+ }
+ columns.add(valueOf);
+ }
+ String join = Joiner.on('\t').skipNulls().join(columns);
+ pw.println(join);
+ for (int i = 0; i < size; i++) {
+ TreeItem item = tree.getItem(i);
+ printItem(pw, columns, item);
+ }
+ }
+ }
+
+ private void printItem(PrintWriter pw, List<String> columns, @Nullable TreeItem item) {
+ if (item == null) {
+ return;
+ }
+ List<String> data = new ArrayList<>();
+ for (int col = 0; col < columns.size(); col++) {
+ data.add(String.valueOf(item.getText(col)));
+ }
+ pw.println(Joiner.on('\t').join(data));
+ for (TreeItem child : item.getItems()) {
+ printItem(pw, columns, child);
+ }
+ }
+
}
package org.eclipse.tracecompass.analysis.timing.ui.views.segmentstore.table;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.TableItem;
+import org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.ExportToTsvAction;
import org.eclipse.tracecompass.tmf.ui.views.TmfView;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+
/**
* View for displaying a segment store analysis in a table.
*
// Attributes
// ------------------------------------------------------------------------
+ private final Action fExportAction = new ExportToTsvAction() {
+ @Override
+ protected void exportToTsv(@Nullable OutputStream stream) {
+ AbstractSegmentStoreTableView.this.exportToTsv(stream);
+
+ }
+
+ @Override
+ protected @Nullable Shell getShell() {
+ return getViewSite().getShell();
+ }
+ };
+
private @Nullable AbstractSegmentStoreTableViewer fSegmentStoreViewer;
// ------------------------------------------------------------------------
SashForm sf = new SashForm(parent, SWT.NONE);
TableViewer tableViewer = new TableViewer(sf, SWT.FULL_SELECTION | SWT.VIRTUAL);
fSegmentStoreViewer = createSegmentStoreViewer(tableViewer);
+ getViewSite().getActionBars().getMenuManager().add(fExportAction);
setInitialData();
}
fSegmentStoreViewer.setData(fSegmentStoreViewer.getSegmentProvider());
}
}
+
+ /**
+ * Export a given items's TSV
+ *
+ * @param stream
+ * an output stream to write the TSV to
+ * @since 1.2
+ */
+ @VisibleForTesting
+ protected void exportToTsv(@Nullable OutputStream stream) {
+ try (PrintWriter pw = new PrintWriter(stream)) {
+ AbstractSegmentStoreTableViewer segmentStoreViewer = getSegmentStoreViewer();
+ if (segmentStoreViewer == null) {
+ return;
+ }
+ Table table = segmentStoreViewer.getTableViewer().getTable();
+ int size = table.getItemCount();
+ List<String> columns = new ArrayList<>();
+ for (int i = 0; i < table.getColumnCount(); i++) {
+ TableColumn column = table.getColumn(i);
+ if (column == null) {
+ return;
+ }
+ String columnName = String.valueOf(column.getText());
+ if (columnName.isEmpty() && i == table.getColumnCount() - 1) {
+ // Linux GTK2 undocumented feature
+ break;
+ }
+ columns.add(columnName);
+ }
+ pw.println(Joiner.on('\t').join(columns));
+ for (int i = 0; i < size; i++) {
+ TableItem item = table.getItem(i);
+ if (item == null) {
+ continue;
+ }
+ List<String> data = new ArrayList<>();
+ for (int col = 0; col < columns.size(); col++) {
+ data.add(String.valueOf(item.getText(col)));
+ }
+ pw.println(Joiner.on('\t').join(data));
+ }
+ }
+ }
}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jface.action.Action;
+import org.eclipse.swt.widgets.FileDialog;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.tracecompass.internal.analysis.timing.ui.Activator;
+
+/**
+ * The export to TSV abstract action
+ *
+ * TODO: improve testing when there is a way to test native widgets
+ *
+ * @author Matthew Khouzam
+ */
+public abstract class ExportToTsvAction extends Action {
+
+ private static final String[] EXTENSTIONS = { "*.tsv", "*.*" };//$NON-NLS-1$//$NON-NLS-2$
+
+ /**
+ * Gets the extension of TSV
+ *
+ * @return the extension of TSV
+ */
+ protected String[] getExtension() {
+ return EXTENSTIONS;
+ }
+
+ @Override
+ public String getText() {
+ return String.valueOf(Messages.AbstractSegmentStoreTableView_exportToTsv);
+ }
+
+ @Override
+ public String getToolTipText() {
+ return String.valueOf(Messages.ExportToTsvAction_exportToTsvToolTip);
+ }
+
+ @Override
+ public void run() {
+ Shell shell = getShell();
+ if (shell == null) {
+ return;
+ }
+ FileDialog fd = new FileDialog(shell);
+ fd.setFilterExtensions(getExtension());
+ String fileName = fd.open();
+ if (fileName == null) {
+ return;
+ }
+ try (FileOutputStream fos = new FileOutputStream(fileName)) {
+ exportToTsv(fos);
+ } catch (IOException e) {
+ Activator.getDefault().logError("IO Error " + fileName, e); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Get the shell to open the file dialog
+ *
+ * @return the shell
+ */
+ protected abstract @Nullable Shell getShell();
+
+ /**
+ * Export a given items's TSV
+ *
+ * @param stream
+ * an output stream to write the TSV to
+ */
+ protected abstract void exportToTsv(OutputStream stream);
+}
--- /dev/null
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * Messages for segment store views
+ *
+ * @author Matthew Khouzam
+ */
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.tracecompass.internal.analysis.timing.ui.views.segmentstore.messages"; //$NON-NLS-1$
+ /**
+ * Export to TSV message
+ */
+ public static @Nullable String AbstractSegmentStoreTableView_exportToTsv;
+ /**
+ * Export to TSV tooltip
+ */
+ public static @Nullable String ExportToTsvAction_exportToTsvToolTip;
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
--- /dev/null
+###############################################################################
+# Copyright (c) 2016 Ericsson
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+###############################################################################
+
+AbstractSegmentStoreTableView_exportToTsv=Export to TSV
+ExportToTsvAction_exportToTsvToolTip=Output the content of this view in a tab separated value text file. This can then be imported into any spreadsheet software.
fTreeViewer.setLabelProvider(new TreeLabelProvider());
List<TmfTreeColumnData> columns = getColumnDataProvider().getColumnData();
this.setTreeColumns(columns);
+
}
/**
* Get the tree viewer object
*
* @return The tree viewer object displayed by this viewer
+ * @since 2.2
*/
- protected TreeViewer getTreeViewer() {
+ public TreeViewer getTreeViewer() {
return fTreeViewer;
}