blob: 2f793b7da846bf8e3db8f08b316de5a43fce767b [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2017, 2019 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.tracecompass.tmf.ui.swtbot.tests.views;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.SimpleLayout;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.SWTBot;
import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes;
import org.eclipse.swtbot.swt.finder.results.IntResult;
import org.eclipse.swtbot.swt.finder.results.Result;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.waits.Conditions;
import org.eclipse.swtbot.swt.finder.waits.DefaultCondition;
import org.eclipse.swtbot.swt.finder.waits.ICondition;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotText;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.presentation.IPaletteProvider;
import org.eclipse.tracecompass.tmf.core.presentation.QualitativePaletteProvider;
import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor;
import org.eclipse.tracecompass.tmf.core.presentation.SequentialPaletteProvider;
import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceClosedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfTraceOpenedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfWindowRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.TmfContext;
import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;
import org.eclipse.tracecompass.tmf.tests.stubs.trace.TmfTraceStub;
import org.eclipse.tracecompass.tmf.ui.colors.RGBAUtil;
import org.eclipse.tracecompass.tmf.ui.dialog.TmfFileDialogFactory;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.ConditionHelpers;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.ImageHelper;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.SWTBotTimeGraph;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.SWTBotTimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.swtbot.tests.shared.SWTBotUtils;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.ITimeDataProvider;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl;
import org.eclipse.ui.IWorkbenchPart;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multisets;
/**
* Test for Timegraph views in trace compass
*/
@RunWith(SWTBotJunit4ClassRunner.class)
public class TimeGraphViewTest {
private static final Logger fLogger = Logger.getRootLogger();
private static RGB fHair;
private static RGB fHat;
private static RGB fLaser;
private static final int MIN_FILE_SIZE = 1000;
/**
* The legend tooltip
*/
private static final String SHOW_LEGEND = "Show Legend";
/**
* Legend title
*/
private static final String LEGEND_NAME = "Legend";
/**
* OK button
*/
private static final String OK_BUTTON = "OK";
/**
* Export to png button
*/
private static final String EXPORT_MENU = "Export...";
/**
* File extension
*/
private static final String EXTENSION = ".png";
/**
* Reference image
*/
private static final String REFERENCE_LOC = "reference";
/**
* image after making the line thinner
*/
private static final String SKINNY_LOC = "skinny";
/**
* Image after resetting
*/
private static final String RESET_LOC = "reset";
private static final TmfTimeRange INITIAL_WINDOW_RANGE = new TmfTimeRange(TmfTimestamp.fromNanos(20), TmfTimestamp.fromNanos(100));
/**
* Legend id key
*/
private static final String LEGEND_ENTRY_KEY = "legend.entry.key";
/**
* Hair entry id
*/
private static final String HAIR_ID = "HAIR";
/**
* Laser entry id
*/
private static final String QUOTE_LASER_UNQUOTE = "\"LASER\"";
private SWTBotView fViewBot;
private SWTBotTimeGraph fTimeGraph;
private TmfTraceStub fTrace;
private Rectangle fBounds;
private SWTWorkbenchBot fBot;
private final ICondition fTimeGraphIsDirty = new ConditionHelper() {
@Override
public boolean test() throws Exception {
SWTBotView viewBot = fViewBot;
if (viewBot == null) {
return false;
}
return getView().isDirty();
}
};
/**
* Set up for test
*/
@BeforeClass
public static void beforeClass() {
SWTBotUtils.initialize();
SWTBotPreferences.TIMEOUT = 20000; /* 20 second timeout */
SWTBotPreferences.KEYBOARD_LAYOUT = "EN_US";
fLogger.removeAllAppenders();
fLogger.addAppender(new ConsoleAppender(new SimpleLayout(), ConsoleAppender.SYSTEM_OUT));
fHair = ImageHelper.adjustExpectedColor(new RGB(0, 64, 128));
fHat = ImageHelper.adjustExpectedColor(new RGB(0, 255, 0));
fLaser = ImageHelper.adjustExpectedColor(new RGB(255, 0, 0));
}
/**
* Before the test is run, make the view see the items.
*
* Reset the perspective and close all the views.
*
* @throws TmfTraceException
* could not load a trace
*/
@Before
public void before() throws TmfTraceException {
fBot = new SWTWorkbenchBot();
fBot.closeAllEditors();
for (SWTBotView viewBot : fBot.views()) {
viewBot.close();
}
SWTBotUtils.openView(TimeGraphViewStub.ID);
fViewBot = fBot.viewById(TimeGraphViewStub.ID);
fViewBot.show();
fTrace = new TmfTraceStub() {
@Override
public @NonNull String getName() {
return "Stub";
}
@Override
public TmfContext seekEvent(ITmfLocation location) {
return new TmfContext();
}
};
fTrace.setStartTime(TmfTimestamp.fromNanos(0));
fTrace.setEndTime(TmfTimestamp.fromNanos(180));
TmfTraceStub trace = fTrace;
trace.initialize(null, "", ITmfEvent.class);
assertNotNull(trace);
fTimeGraph = new SWTBotTimeGraph(fViewBot.bot());
// Wait for trace to be loaded
fViewBot.bot().waitUntil(new TgConditionHelper(t -> fTimeGraph.getEntries().length == 0));
fBounds = getBounds();
UIThreadRunnable.syncExec(() -> TmfSignalManager.dispatchSignal(new TmfTraceOpenedSignal(this, trace, null)));
// Wait for trace to be loaded
fViewBot.bot().waitUntil(new TgConditionHelper(t -> fTimeGraph.getEntries().length >= 2));
resetTimeRange();
// Make sure the thumb is over 1 in size
fBot.waitUntil(new TgConditionHelper(t -> fViewBot.bot().slider().getThumb() > 1));
}
private void resetTimeRange() {
TmfTimeRange fullTimeRange = fTrace.getTimeRange();
TmfWindowRangeUpdatedSignal signal = new TmfWindowRangeUpdatedSignal(this, INITIAL_WINDOW_RANGE);
TimeGraphViewStub view = getView();
// This is an oddity: the signals may be lost if a view is not
// initialized, so
// they are send in the condition.
//
// NOTE: maybe use a shorter poll delay?
//
// Workflow: it should always fail at first and then a signal is sent.
// it will either time out or work.
fBot.waitUntil(new TgConditionHelper(t -> {
if (INITIAL_WINDOW_RANGE.equals(view.getWindowRange())) {
return true;
}
TmfSignalManager.dispatchSignal(signal);
return false;
}));
// same as above: re-run as much as you need.
fBot.waitUntil(new TgConditionHelper(t -> {
if (fullTimeRange.equals(view.getWindowRange())) {
return true;
}
fViewBot.toolbarButton("Reset the Time Scale to Default").click();
return false;
}));
}
private Rectangle getBounds() {
return UIThreadRunnable.syncExec((Result<Rectangle>) () -> {
Control control = fTimeGraph.widget;
Rectangle ctrlRelativeBounds = control.getBounds();
Point res = control.toDisplay(new Point(fTimeGraph.getNameSpace(), 0));
ctrlRelativeBounds.width -= fTimeGraph.getNameSpace();
ctrlRelativeBounds.x = res.x;
ctrlRelativeBounds.y = res.y;
return ctrlRelativeBounds;
});
}
private TimeGraphViewStub getView() {
IWorkbenchPart part = fViewBot.getViewReference().getPart(true);
assertTrue(part.getClass().getCanonicalName(), part instanceof TimeGraphViewStub);
TimeGraphViewStub stubView = (TimeGraphViewStub) part;
return stubView;
}
/**
* Clean up after a test, reset the views and reset the states of the
* timegraph by pressing reset on all the resets of the legend
*/
@After
public void after() {
// reset all
fViewBot.toolbarButton(SHOW_LEGEND).click();
SWTBotShell legendShell = fBot.shell(LEGEND_NAME);
SWTBot legendBot = legendShell.bot();
for (int i = 0; i < StubPresentationProvider.STATES.length; i++) {
SWTBotButton resetButton = legendBot.button(i);
if (resetButton.isEnabled()) {
resetButton.click();
}
}
legendBot.button(OK_BUTTON).click();
TmfTraceStub trace = fTrace;
assertNotNull(trace);
UIThreadRunnable.syncExec(() -> TmfSignalManager.dispatchSignal(new TmfTraceClosedSignal(this, trace)));
fBot.waitUntil(Conditions.shellCloses(legendShell));
fViewBot.close();
fBot.waitUntil(ConditionHelpers.viewIsClosed(fViewBot));
fTrace.dispose();
}
/**
* Put things back the way they were
*/
@AfterClass
public static void afterClass() {
SWTWorkbenchBot bot = new SWTWorkbenchBot();
bot.closeAllEditors();
fLogger.removeAllAppenders();
}
/**
* Test the legend operation for an arrow. Change sliders and reset, do not
* change colors as there is not mock of the color picker yet
*
* TODO: mock color picker
*
* TODO: make stable
*/
@Test
public void testLegendArrow() {
resetTimeRange();
Rectangle bounds = fBounds;
ImageHelper ref = ImageHelper.grabImage(bounds);
// Set the widths to 0.25
fViewBot.toolbarButton(SHOW_LEGEND).click();
SWTBotShell legendShell = fBot.shell(LEGEND_NAME);
legendShell.activate();
SWTBot legendBot = legendShell.bot();
assertFalse(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) QUOTE_LASER_UNQUOTE).isEnabled());
int defaultValue = legendBot.scaleWithId(LEGEND_ENTRY_KEY, QUOTE_LASER_UNQUOTE).getValue();
legendBot.scaleWithId(LEGEND_ENTRY_KEY, QUOTE_LASER_UNQUOTE).setValue(100);
assertTrue(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) QUOTE_LASER_UNQUOTE).isEnabled());
legendShell.bot().button(OK_BUTTON).click();
fBot.waitUntil(Conditions.shellCloses(legendShell));
resetTimeRange();
// Take another picture
ImageHelper thick = ImageHelper.waitForNewImage(bounds, ref);
// Compare with the original, they should be different
int refCount = ref.getHistogram().count(fLaser);
int thickCount = thick.getHistogram().count(fLaser);
assertTrue(String.format("Count of \"\"LASER\"\" (%s) did not get change despite change of width before: %d after:%d histogram:%s", fLaser, refCount, thickCount, Multisets.copyHighestCountFirst(thick.getHistogram())), thickCount > refCount);
// reset all
fViewBot.toolbarButton(SHOW_LEGEND).click();
legendShell = fBot.shell(LEGEND_NAME);
legendBot = legendShell.bot();
assertTrue(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) QUOTE_LASER_UNQUOTE).isEnabled());
legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) QUOTE_LASER_UNQUOTE).click();
assertEquals(defaultValue, legendBot.scaleWithId(LEGEND_ENTRY_KEY, QUOTE_LASER_UNQUOTE).getValue());
assertFalse(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) QUOTE_LASER_UNQUOTE).isEnabled());
legendBot.button(OK_BUTTON).click();
fBot.waitUntil(Conditions.shellCloses(legendShell));
resetTimeRange();
// take a third picture
ImageHelper reset = ImageHelper.waitForNewImage(bounds, thick);
// Compare with the original, they should be the same
int resetCount = reset.getHistogram().count(fLaser);
assertEquals("Count of \"\"LASER\"\" did not get change despite reset of width", refCount, resetCount);
}
/**
* Test the event styling methods, should put a box around the events.
*/
@Test
public void testEventStyling() {
resetTimeRange();
Rectangle bounds = fBounds;
ImageHelper precollapse = ImageHelper.grabImage(bounds);
SWTBotTimeGraph tg = fTimeGraph;
tg.getEntry("Plumber guy").collapse();
TimeGraphViewStub view = getView();
// take a first saved picture, the original is pre-collapse
ImageHelper ref = ImageHelper.waitForNewImage(bounds, precollapse);
/*
* Regex, not a question
*/
view.setFilterRegex("row?");
UIThreadRunnable.syncExec(() -> view.restartZoomThread());
// Take another picture/
ImageHelper highlighted = ImageHelper.waitForNewImage(bounds, ref);
view.setFilterRegex(null);
UIThreadRunnable.syncExec(() -> view.restartZoomThread());
// take a third picture
ImageHelper reset = ImageHelper.waitForNewImage(bounds, highlighted);
ImageHelper diff = ref.diff(reset);
assertTrue((double) diff.getHistogram().count(new RGB(0, 0, 0)) / diff.getHistogram().size() > 0.99);
ImageHelper delta = highlighted.diff(reset);
tg.expandAll();
assertTrue("Some highlighting", delta.getHistogram().elementSet().size() > 1);
}
/**
* Test Mouse operations, drag, select and middle-drag
*/
@Test
public void testDrag() {
String pg = "Plumber guy";
SWTBotTimeGraph timegraph = fTimeGraph;
resetTimeRange();
SWTBotTimeGraphEntry[] entries = timegraph.getEntries();
assertNotNull(entries);
SWTBotTimeGraphEntry entry1 = timegraph.getEntry(pg, "Hat2");
assertNotNull(entry1);
SWTBotTimeGraphEntry entry2 = timegraph.getEntry(pg, "Head3");
assertNotNull(entry2);
ITimeDataProvider timeprovider = timegraph.widget.getTimeDataProvider();
validateRanges(timeprovider, 0, 0, 20, 100);
// Change selection
Point down = entry1.getPointForTime(40);
Point up = entry2.getPointForTime(80);
fTimeGraph.drag(down, up, SWT.BUTTON1);
validateRanges(timeprovider, 40, 80, 20, 100);
// Zoom window
down = entry1.getPointForTime(70);
up = entry2.getPointForTime(30);
fTimeGraph.drag(down, up, SWT.BUTTON3);
validateRanges(timeprovider, 40, 80, 30, 70);
// Drag window
down = entry1.getPointForTime(65);
up = entry2.getPointForTime(35);
fTimeGraph.drag(down, up, SWT.BUTTON2);
validateRanges(timeprovider, 40, 80, 60, 100);
// Check for aliasing
up = entry1.getPointForTime(99);
for (int selectionDown = 61; selectionDown < 99; selectionDown++) {
down = entry1.getPointForTime(selectionDown);
fTimeGraph.drag(down, up, SWT.BUTTON1);
validateRanges(timeprovider, selectionDown, 99, 60, 100);
}
down = entry1.getPointForTime(61);
for (int selectionUp = 62; selectionUp < 100; selectionUp++) {
// drag the cursor by one ns per cycle
up = entry1.getPointForTime(selectionUp);
fTimeGraph.drag(down, up, SWT.BUTTON1);
down = up;
validateRanges(timeprovider, 61, selectionUp, 60, 100);
}
// drag cursor around
down = entry1.getPointForTime(65);
up = entry1.getPointForTime(85);
fTimeGraph.drag(down, up, SWT.BUTTON1);
validateRanges(timeprovider, 65, 85, 60, 100);
up = entry1.getPointForTime(70);
fTimeGraph.drag(down, up, SWT.BUTTON1);
validateRanges(timeprovider, 70, 85, 60, 100);
}
private static void validateRanges(ITimeDataProvider timeprovider,
long selectionBegin, long selectionEnd,
long time0, long time1) {
assertEquals("Selection Begin", selectionBegin, timeprovider.getSelectionBegin());
assertEquals("Selection End", selectionEnd, timeprovider.getSelectionEnd());
assertEquals("Window 0", time0, timeprovider.getTime0());
assertEquals("Window 1", time1, timeprovider.getTime1());
}
/**
* Test bookmark operations
*/
@Test
public void testBookmark() {
String pg = "Plumber guy";
SWTBotTimeGraph timegraph = fTimeGraph;
resetTimeRange();
SWTBotTimeGraphEntry[] entries = null;
entries = timegraph.getEntries();
assertNotNull(entries);
SWTBotTimeGraphEntry entry1 = timegraph.getEntry(pg, "Hat2");
SWTBotTimeGraphEntry entry2 = timegraph.getEntry(pg, "Head3");
assertNotNull(entry1);
assertNotNull(entry2);
ITimeDataProvider timeprovider = timegraph.widget.getTimeDataProvider();
// initial settings
long unsetTime = 0;
long time0 = 20;
long time1 = 100;
// Bookmark Range
long bookmarkBegin = 40;
long bookmarkEnd = 80;
// Other marker
long previousMarker = 38;
long nextMarker = 44;
Point down = entry1.getPointForTime(bookmarkBegin);
Point up = entry2.getPointForTime(bookmarkEnd);
validateRanges(timeprovider, unsetTime, unsetTime, time0, time1);
fTimeGraph.drag(down, up, SWT.BUTTON1);
// Contiguous time?
validateRanges(timeprovider, bookmarkBegin, bookmarkEnd, time0, time1);
fViewBot.toolbarButton("Add Bookmark...").click();
SWTBotShell bookmarkShell = fBot.shell("Add Bookmark");
bookmarkShell.bot().text().setText("Bookmark");
bookmarkShell.bot().button(OK_BUTTON).click();
fViewBot.toolbarButton("Previous Marker").click();
validateRanges(timeprovider, previousMarker, previousMarker, time0, time1);
fViewBot.toolbarButton("Next Marker").click();
validateRanges(timeprovider, bookmarkBegin, bookmarkEnd, time0, time1);
fViewBot.toolbarButton("Remove Bookmark").click();
validateRanges(timeprovider, bookmarkBegin, bookmarkEnd, time0, time1);
fViewBot.toolbarButton("Previous Marker").click();
validateRanges(timeprovider, previousMarker, previousMarker, time0, time1);
fViewBot.toolbarButton("Next Marker").click();
validateRanges(timeprovider, nextMarker, nextMarker, time0, time1);
}
/**
* Test the legend operation. Change sliders and reset, do not change colors
* as there is not mock of the color picker yet
*
* TODO: mock color picker
*/
@Test
public void testLegend() {
resetTimeRange();
Rectangle bounds = fBounds;
ImageHelper ref = ImageHelper.grabImage(bounds);
// Set the widths to 0.25
fViewBot.toolbarButton(SHOW_LEGEND).click();
SWTBotShell legendShell = fBot.shell(LEGEND_NAME);
legendShell.activate();
SWTBot legendBot = legendShell.bot();
assertFalse(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) HAIR_ID).isEnabled());
int defaultValue = legendBot.scaleWithId(LEGEND_ENTRY_KEY, HAIR_ID).getValue();
legendBot.scaleWithId(LEGEND_ENTRY_KEY, HAIR_ID).setValue(25);
assertTrue(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) HAIR_ID).isEnabled());
legendShell.bot().button(OK_BUTTON).click();
fBot.waitUntil(Conditions.shellCloses(legendShell));
resetTimeRange();
// Take another picture
ImageHelper skinny = ImageHelper.waitForNewImage(bounds, ref);
/* Compare with the original, they should be different */
int refCount = ref.getHistogram().count(fHair);
int skinnyCount = skinny.getHistogram().count(fHair);
assertTrue(String.format("Count of \"\"HAIR\"\" (%s) did not get change despite change of width before: %d after:%d histogram:%s", fHair, refCount, skinnyCount, Multisets.copyHighestCountFirst(skinny.getHistogram())), skinnyCount < refCount);
// reset all
fViewBot.toolbarButton(SHOW_LEGEND).click();
legendShell = fBot.shell(LEGEND_NAME);
legendBot = legendShell.bot();
assertTrue(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) HAIR_ID).isEnabled());
legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) HAIR_ID).click();
assertEquals(defaultValue, legendBot.scaleWithId(LEGEND_ENTRY_KEY, HAIR_ID).getValue());
assertFalse(legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) HAIR_ID).isEnabled());
legendBot.button(OK_BUTTON).click();
fBot.waitUntil(Conditions.shellCloses(legendShell));
resetTimeRange();
// take a third picture
ImageHelper reset = ImageHelper.waitForNewImage(bounds, skinny);
// Compare with the original, they should be the same
int resetCount = reset.getHistogram().count(fHair);
assertEquals("Count of \"HAIR\" did not get change despite reset of width", refCount, resetCount);
}
/**
* Test the legend and export operations. Resize states, take screenshots
* and compare, they should be different. then reset the sizes and compare,
* they should be the same.
*
* NOTE: This utterly fails in GTK3.
*
* @throws IOException
* file not found, someone deleted files while the test is
* running
*/
@Ignore
@Test
public void testExport() throws IOException {
resetTimeRange();
/*
* Set up temp files
*/
File ref = File.createTempFile(REFERENCE_LOC, EXTENSION);
File skinny = File.createTempFile(SKINNY_LOC, EXTENSION);
File reset = File.createTempFile(RESET_LOC, EXTENSION);
ref.deleteOnExit();
skinny.deleteOnExit();
reset.deleteOnExit();
/* Take a picture */
TmfFileDialogFactory.setOverrideFiles(ref.getAbsolutePath());
fViewBot.viewMenu(EXPORT_MENU).click();
ImageHelper refImage = ImageHelper.fromFile(ref);
fBot.waitUntil(new FileWritten(ref, MIN_FILE_SIZE));
/* Set the widths to skinny */
fViewBot.toolbarButton(SHOW_LEGEND).click();
SWTBotShell legendShell = fBot.shell(LEGEND_NAME);
legendShell.activate();
SWTBot legendBot = legendShell.bot();
legendBot.scaleWithId(LEGEND_ENTRY_KEY, HAIR_ID).setValue(50);
legendShell.bot().button(OK_BUTTON).click();
fBot.waitUntil(Conditions.shellCloses(legendShell));
resetTimeRange();
/* Take another picture */
TmfFileDialogFactory.setOverrideFiles(skinny.getAbsolutePath());
fViewBot.viewMenu(EXPORT_MENU).click();
ImageHelper skinnyImage = ImageHelper.fromFile(skinny);
fBot.waitUntil(new FileWritten(skinny, MIN_FILE_SIZE));
/* Compare with the original, they should be different */
int refCount = refImage.getHistogram().count(fHair);
int skinnyCount = skinnyImage.getHistogram().count(fHair);
assertTrue(String.format("Count of \"\"HAIR\"\" (%s) did not get change despite change of width before: %d after:%d histogram:%s", fHair, refCount, skinnyCount, Multisets.copyHighestCountFirst(skinnyImage.getHistogram())),
skinnyCount < refCount);
/* reset all */
fViewBot.toolbarButton(SHOW_LEGEND).click();
legendShell = fBot.shell(LEGEND_NAME);
legendBot = legendShell.bot();
legendBot.buttonWithId(LEGEND_ENTRY_KEY, (Object) HAIR_ID).click();
legendBot.button(OK_BUTTON).click();
fBot.waitUntil(Conditions.shellCloses(legendShell));
resetTimeRange();
/* take a third picture */
TmfFileDialogFactory.setOverrideFiles(reset.getAbsolutePath());
fViewBot.viewMenu(EXPORT_MENU).click();
fBot.waitUntil(new FileWritten(reset, MIN_FILE_SIZE));
ImageHelper resetImage = ImageHelper.fromFile(reset);
/* Compare with the original, they should be the same */
int resetCount = resetImage.getHistogram().count(fHair);
assertEquals("Count of \"HAIR\" did not get change despite reset of width", refCount, resetCount);
}
/**
* Test expand and collapes of a timegraph view
*/
@Test
public void testExpandAndCollapse() {
String pg = "Plumber guy";
String hpc = "Hungry pie chart";
String element = "row2";
int totalItems = 17;
resetTimeRange();
SWTBotTimeGraph timegraph = fTimeGraph;
assertEquals(totalItems, getVisibleItems(timegraph));
SWTBotTimeGraphEntry[] entries = null;
entries = timegraph.getEntries();
assertNotNull(entries);
assertNotNull(timegraph.getEntry(hpc, element));
timegraph.collapseAll();
entries = timegraph.getEntries();
assertEquals(3, getVisibleItems(timegraph));
timegraph.getEntry(pg).select();
fireKey(timegraph, true, '+');
assertEquals(10, getVisibleItems(timegraph));
timegraph.getEntry(pg).select();
fireKey(timegraph, true, '-');
assertEquals(3, getVisibleItems(timegraph));
timegraph.getEntry(hpc).select();
fireKey(timegraph, true, '+');
assertEquals(10, getVisibleItems(timegraph));
assertNotNull(timegraph.getEntry(hpc, element));
timegraph.getEntry(pg).select();
fireKey(timegraph, true, '*');
timegraph.getEntry(hpc).select();
fireKey(timegraph, true, '*');
assertEquals(totalItems, getVisibleItems(timegraph));
assertNotNull(timegraph.getEntry(hpc, element));
}
/**
* Test vertical zoom in and out
*/
@Test
public void testVerticalZoom() {
resetTimeRange();
int threshold = 10;
SWTBotTimeGraph timegraph = fTimeGraph;
Rectangle bounds = fBounds;
ImageHelper ref = ImageHelper.grabImage(bounds);
fireKeyAndWait(timegraph, bounds, false, '+', SWT.CTRL);
fireKeyAndWait(timegraph, bounds, false, '-', SWT.CTRL);
ImageHelper bigSmall = ImageHelper.grabImage(bounds);
ImageHelper diff = ref.diff(bigSmall);
// 3% of the image
threshold = (int) (diff.getHistogram().size() * 0.03);
List<RGB> colors = filter(diff.getHistogram(), threshold);
assertEquals(colors.toString(), 1, colors.size());
fireKeyAndWait(timegraph, bounds, false, '+', SWT.CTRL);
fireKeyAndWait(timegraph, bounds, false, '+', SWT.CTRL);
ImageHelper bigBig = ImageHelper.grabImage(bounds);
diff = ref.diff(bigBig);
colors = filter(diff.getHistogram(), threshold);
assertNotEquals(colors.toString(), 1, colors.size());
fireKeyAndWait(timegraph, bounds, false, '+', SWT.CTRL);
fireKeyAndWait(timegraph, bounds, false, '-', SWT.CTRL);
fireKeyAndWait(timegraph, bounds, false, '0', SWT.CTRL);
ImageHelper bigSmallReset = ImageHelper.grabImage(bounds);
diff = ref.diff(bigSmallReset);
colors = filter(diff.getHistogram(), threshold);
assertEquals(colors.toString(), 1, colors.size());
fireKeyAndWait(timegraph, bounds, false, '-', SWT.CTRL);
fireKeyAndWait(timegraph, bounds, false, '-', SWT.CTRL);
ImageHelper smallSmall = ImageHelper.grabImage(bounds);
diff = ref.diff(smallSmall);
colors = filter(diff.getHistogram(), threshold);
assertNotEquals(colors.toString(), 1, colors.size());
fireKeyAndWait(timegraph, bounds, false, '-', SWT.CTRL);
fireKeyAndWait(timegraph, bounds, false, '+', SWT.CTRL);
fireKeyAndWait(timegraph, bounds, false, '0', SWT.CTRL);
ImageHelper smallBigReset = ImageHelper.grabImage(bounds);
diff = ref.diff(smallBigReset);
colors = filter(diff.getHistogram(), threshold);
assertEquals(colors.toString(), 1, colors.size());
}
private static List<RGB> filter(Multiset<RGB> histogram, int threshold) {
return histogram.elementSet().stream().filter(e -> histogram.count(e) > threshold).collect(Collectors.toList());
}
/**
* Test horizontal zoom, we can see a rounding error
*/
@Test
public void testHorizontalZoom() {
resetTimeRange();
SWTBotTimeGraph timegraph = fTimeGraph;
TimeGraphViewStub view = getView();
timegraph.setFocus();
assertEquals(80, getDuration(view.getWindowRange()));
fireKeyInGraph(timegraph, '=');
fViewBot.bot().waitUntil(new WindowRangeCondition(view, 52));
fireKeyInGraph(timegraph, '+');
fViewBot.bot().waitUntil(new WindowRangeCondition(view, 34));
fireKeyInGraph(timegraph, '-');
fViewBot.bot().waitUntil(new WindowRangeCondition(view, 51));
fireKeyInGraph(timegraph, '-');
fViewBot.bot().waitUntil(new WindowRangeCondition(view, 77));
/*
* Note that 'w' and 's' zooming is based on mouse position. Just check
* if window range was increased or decreased to avoid inaccuracy due to
* the mouse position in test environment.
*/
long previousRange = getDuration(view.getWindowRange());
fireKeyInGraph(timegraph, 'w');
fViewBot.bot().waitUntil(new WindowRangeUpdatedCondition(view, previousRange, false));
previousRange = getDuration(view.getWindowRange());
fireKeyInGraph(timegraph, 's');
fViewBot.bot().waitUntil(new WindowRangeUpdatedCondition(view, previousRange, true));
}
/**
* Test name space navigation using the keyboard.
*/
@Test
public void testKeyboardNamespaceNavigation() {
String pg = "Plumber guy";
resetTimeRange();
SWTBotTimeGraph timegraph = fTimeGraph;
timegraph.getEntry(pg).select();
fireKey(timegraph, true, SWT.ARROW_DOWN);
assertEquals("[Hat1]", timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.ARROW_DOWN);
assertEquals("[Hat2]", timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.ARROW_DOWN);
assertEquals("[Head1]", timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.ARROW_UP);
assertEquals("[Hat2]", timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.ARROW_DOWN);
assertEquals("[Head1]", timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.HOME);
assertEquals('[' + pg + ']', timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.PAGE_DOWN);
assertNotEquals('[' + pg + ']', timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.PAGE_UP);
assertEquals('[' + pg + ']', timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.END);
assertEquals("[pulse]", timegraph.selection().get(0).toString());
fireKey(timegraph, true, SWT.HOME);
assertEquals('[' + pg + ']', timegraph.selection().get(0).toString());
}
/**
* Test the enter key, which toggles expand/collapse.
*/
@Test
public void testCollapseExpandUsingEnter() {
String pg = "Plumber guy";
resetTimeRange();
SWTBotTimeGraph timegraph = fTimeGraph;
assertEquals(0, timegraph.selection().columnCount());
timegraph.getEntry(pg).select();
assertEquals(1, timegraph.selection().columnCount());
assertEquals(17, getVisibleItems(timegraph));
fireKey(timegraph, true, SWT.CR);
assertEquals(1, timegraph.selection().columnCount());
assertEquals('[' + pg + ']', timegraph.selection().get(0).toString());
assertEquals(10, getVisibleItems(timegraph));
fireKey(timegraph, true, SWT.CR);
assertEquals(1, timegraph.selection().columnCount());
assertEquals('[' + pg + ']', timegraph.selection().get(0).toString());
assertEquals(17, getVisibleItems(timegraph));
timegraph.getEntry(pg, "Hat1").select();
fireKey(timegraph, true, SWT.CR);
assertEquals(1, timegraph.selection().columnCount());
assertEquals("[Hat1]", timegraph.selection().get(0).toString());
assertEquals(17, getVisibleItems(timegraph));
}
/**
* Test the line entries
*/
@Test
public void testTimeLine() {
String lines = "pulse";
resetTimeRange();
SWTBotTimeGraph timegraph = fTimeGraph;
assertEquals(0, timegraph.selection().columnCount());
ImageHelper currentImage = ImageHelper.waitForNewImage(fBounds, null);
SWTBotTimeGraphEntry entry = timegraph.getEntry(lines);
// make sure it's visible
entry = timegraph.getEntry(lines).select();
ImageHelper.waitForNewImage(fBounds, currentImage);
Rectangle rect = entry.absoluteLocation();
ImageHelper image = ImageHelper.grabImage(rect);
ImmutableMultiset<RGB> ms = Multisets.copyHighestCountFirst(image.getHistogram());
int black = ms.count(new RGB(0, 0, 0));
RGB bgColor = ms.elementSet().iterator().next();
int bgCount = ms.count(bgColor);
float actual = ((float) black) / (bgCount);
assertEquals(0.113f, actual, 0.05f);
}
/**
* Test zoom to selection
*/
@Test
public void testZoomToSelection() {
resetTimeRange();
SWTBotTimeGraph timegraph = fTimeGraph;
TimeGraphViewStub view = getView();
timegraph.setFocus();
assertEquals(80, getDuration(view.getWindowRange()));
/* set selection to trace start time */
ITmfTimestamp selStartTime = TmfTimestamp.fromNanos(30L);
ITmfTimestamp selEndTime = TmfTimestamp.fromNanos(80L);
TmfSignalManager.dispatchSignal(new TmfSelectionRangeUpdatedSignal(this, selStartTime, selEndTime));
timeGraphIsReadyCondition(new TmfTimeRange(selStartTime, selEndTime));
fireKeyInGraph(timegraph, 'z');
fViewBot.bot().waitUntil(new WindowRangeCondition(view, 50));
}
/**
* Test 'a' and 'd' navigation
*/
@Test
public void testKeyboardNavigation() {
resetTimeRange();
SWTBotTimeGraph timegraph = fTimeGraph;
TimeGraphViewStub view = getView();
timegraph.setFocus();
assertEquals(80, getDuration(view.getWindowRange()));
TmfTimeRange updatedWindowRange = new TmfTimeRange(TmfTimestamp.fromNanos(40), TmfTimestamp.fromNanos(120));
// move to the right
fireKeyInGraph(timegraph, 'd');
fViewBot.bot().waitUntil(new TgConditionHelper(t -> updatedWindowRange.equals(view.getWindowRange())));
// move to the left
fireKeyInGraph(timegraph, 'a');
fViewBot.bot().waitUntil(new TgConditionHelper(t -> INITIAL_WINDOW_RANGE.equals(view.getWindowRange())));
}
private static long getDuration(TmfTimeRange refRange) {
return refRange.getEndTime().toNanos() - refRange.getStartTime().toNanos();
}
// Slow but safe
private static void fireKeyAndWait(SWTBotTimeGraph timegraph, Rectangle bounds, boolean inNameSpace, int c, int... modifiers) {
ImageHelper ref = ImageHelper.grabImage(bounds);
fireKey(timegraph, inNameSpace, c, modifiers);
ImageHelper.waitForNewImage(bounds, ref);
}
private static void fireKey(SWTBotTimeGraph timegraph, boolean inNameSpace, int c, int... modifiers) {
Event event = new Event();
event.widget = timegraph.widget;
if ((c & SWT.KEYCODE_BIT) == 0 && c != SWT.CR) {
event.character = (char) c;
} else {
event.keyCode = c;
}
event.doit = true;
UIThreadRunnable.syncExec(() -> {
event.display = Display.getCurrent();
resetMousePosition(timegraph, inNameSpace, new MouseEvent(event));
KeyEvent e = new KeyEvent(event);
for (int modifier : modifiers) {
e.stateMask |= modifier;
}
timegraph.widget.keyPressed(e);
});
UIThreadRunnable.syncExec(() -> {
event.display = Display.getCurrent();
resetMousePosition(timegraph, inNameSpace, new MouseEvent(event));
KeyEvent e = new KeyEvent(event);
for (int modifier : modifiers) {
e.stateMask |= modifier;
}
timegraph.widget.keyReleased(e);
});
}
private static void fireKeyInGraph(SWTBotTimeGraph timegraph, char c, int... modifiers) {
timegraph.setFocus();
// Move mouse to middle of the timegraph
timegraph.moveMouseToWidget();
int mask = 0;
for (int modifier : modifiers) {
mask |= modifier;
}
timegraph.pressShortcut(mask, c);
}
private static void resetMousePosition(SWTBotTimeGraph timegraph, boolean inNs, MouseEvent mouseEvent) {
Rectangle rect = timegraph.widget.getBounds();
if (!inNs) {
mouseEvent.x = rect.width - 1;
} else {
mouseEvent.x = 1;
}
mouseEvent.y = rect.height / 2 + rect.y;
timegraph.widget.mouseMove(mouseEvent);
}
private static class PaletteIsPresent extends DefaultCondition {
private String fFailureMessage;
private List<RGB> fRgbs;
private Rectangle fRect;
public PaletteIsPresent(List<RGB> rgbs, Rectangle bounds) {
fRgbs = rgbs;
fRect = bounds;
}
@Override
public boolean test() throws Exception {
ImageHelper image = ImageHelper.grabImage(fRect);
if (image == null) {
fFailureMessage = "Grabbed image is null";
return false;
}
Multiset<RGB> histogram = image.getHistogram();
for (RGB rgb : fRgbs) {
if (histogram.count(rgb) <= 0) {
fFailureMessage = "Color not found: " + rgb;
return false;
}
}
return true;
}
@Override
public String getFailureMessage() {
return fFailureMessage;
}
}
/**
* Test time graph with color palettes
*/
@Ignore
@Test
public void testPalettes() {
resetTimeRange();
TimeGraphViewStub view = getView();
Rectangle bounds = fBounds;
IPaletteProvider paletteBlue = SequentialPaletteProvider.create(new RGBAColor(0x23, 0x67, 0xf3, 0xff), 5);
UIThreadRunnable.syncExec(() -> view.setPresentationProvider(new PalettedPresentationProvider() {
@Override
public IPaletteProvider getPalette() {
return paletteBlue;
}
}));
List<RGB> rgbs = Lists.transform(paletteBlue.get(), a -> RGBAUtil.fromInt(a.toInt()).rgb);
fViewBot.bot().waitUntil(new PaletteIsPresent(rgbs, bounds));
IPaletteProvider paletteGreen = SequentialPaletteProvider.create(new RGBAColor(0x23, 0xf3, 0x67, 0xff), 5);
UIThreadRunnable.syncExec(() -> view.setPresentationProvider(new PalettedPresentationProvider() {
@Override
public IPaletteProvider getPalette() {
return paletteGreen;
}
}));
rgbs = Lists.transform(paletteGreen.get(), a -> RGBAUtil.fromInt(a.toInt()).rgb);
fViewBot.bot().waitUntil(new PaletteIsPresent(rgbs, bounds));
IPaletteProvider rotating = new QualitativePaletteProvider.Builder().setAttenuation(0.5f).setBrightness(1.0f).setNbColors(4).build();
UIThreadRunnable.syncExec(() -> view.setPresentationProvider(new PalettedPresentationProvider() {
@Override
public IPaletteProvider getPalette() {
return rotating;
}
}));
rgbs = Lists.transform(rotating.get(), a -> RGBAUtil.fromInt(a.toInt()).rgb);
fViewBot.bot().waitUntil(new PaletteIsPresent(rgbs, bounds));
}
/**
* Integration test for the time event filtering dialog
*/
@Test
public void testTimegraphEventFiltering() {
SWTWorkbenchBot bot = fBot;
resetTimeRange();
SWTBot viewBot = fViewBot.bot();
SWTBotTimeGraph timegraph = fTimeGraph;
assertTrue("timegraph visible", timegraph.isVisible());
timegraph.setFocus();
Rectangle bounds = fBounds;
ImageHelper ref = ImageHelper.grabImage(bounds);
timegraph.setFocus();
// Move mouse to middle of the timegraph
timegraph.moveMouseToWidget();
// Press '/' to open the filter dialog
timegraph.pressShortcut(KeyStroke.getInstance('/'));
SWTBotShell dialogShell = viewBot.shell("Time Event Filter").activate();
SWTBot shellBot = dialogShell.bot();
SWTBotText text = shellBot.text();
text.setText("Hat1");
bot.waitWhile(fTimeGraphIsDirty);
timegraph.setFocus();
ImageHelper filtered = ImageHelper.waitForNewImage(bounds, ref);
/* Compare with the original, they should be different */
int refHatCount = ref.getHistogram().count(fHat);
int filteredHatCount = filtered.getHistogram().count(fHat);
int refHairCount = ref.getHistogram().count(fHair);
int filteredHairCount = filtered.getHistogram().count(fHair);
assertTrue("Count of \"HAT\" did not decrease to non-zero", filteredHatCount < refHatCount && filteredHatCount > 0);
assertTrue("Count of \"HAIR\" did not decrease to zero", filteredHairCount < refHairCount && filteredHairCount == 0);
int count = getVisibleItems(timegraph);
dialogShell = viewBot.shell("Time Event Filter").activate();
shellBot = dialogShell.bot();
text = shellBot.text();
text.setFocus();
SWTBotUtils.pressShortcut(text, Keystrokes.CR);
bot.waitWhile(fTimeGraphIsDirty);
int newCount = getVisibleItems(timegraph);
assertTrue("Fewer entries should be visible here. Current value is " + newCount + " previous was " + count, newCount < count);
}
private static int getVisibleItems(SWTBotTimeGraph timegraph) {
return UIThreadRunnable.syncExec(Display.getDefault(), new IntResult() {
@Override
public Integer run() {
int count = 0;
TimeGraphControl control = timegraph.widget;
ITimeGraphEntry[] expandedElements = control.getExpandedElements();
for (ITimeGraphEntry entry : expandedElements) {
Rectangle itemBounds = control.getItemBounds(entry);
if (itemBounds.height > 0) {
count++;
}
}
return count;
}
});
}
private void timeGraphIsReadyCondition(@NonNull TmfTimeRange selectionRange) {
IWorkbenchPart part = fViewBot.getViewReference().getPart(false);
fBot.waitUntil(ConditionHelpers.timeGraphIsReadyCondition((AbstractTimeGraphView) part, selectionRange, selectionRange.getEndTime()));
}
private abstract class ConditionHelper implements ICondition {
@Override
public final String getFailureMessage() {
return null;
}
@Override
public final void init(SWTBot bot) {
// Do nothing
}
}
private class TgConditionHelper extends ConditionHelper {
private final Predicate<Object> fTestLogic;
public TgConditionHelper(Predicate<Object> testLogic) {
assertNotNull(testLogic);
fTestLogic = testLogic;
}
@Override
public final boolean test() throws Exception {
if (!fTimeGraphIsDirty.test()) {
return true;
}
return fTestLogic.test(null);
}
}
class FileWritten extends ConditionHelper {
private File fFile;
private int fAmount;
/**
* constructor
*
* @param file
* the file
* @param amount
* the minimum size of number of bytes
*/
public FileWritten(File file, int amount) {
fFile = file;
fAmount = amount;
}
@Override
public boolean test() throws Exception {
return fFile.length() >= fAmount;
}
}
private class WindowRangeCondition extends DefaultCondition {
TimeGraphViewStub fView;
long fExpectedRange;
public WindowRangeCondition(TimeGraphViewStub view, long expectedRange) {
fView = view;
fExpectedRange = expectedRange;
}
@Override
public boolean test() throws Exception {
return getDuration(fView.getWindowRange()) == fExpectedRange;
}
@Override
public String getFailureMessage() {
return "Expected window range (" + fExpectedRange + ") not achieved. Actual=" + getDuration(fView.getWindowRange());
}
}
private class WindowRangeUpdatedCondition extends DefaultCondition {
TimeGraphViewStub fView;
long fPreviousRange;
boolean fIsIncreased;
public WindowRangeUpdatedCondition(TimeGraphViewStub view, long previousRange, boolean increased) {
fView = view;
fPreviousRange = previousRange;
fIsIncreased = increased;
}
@Override
public boolean test() throws Exception {
long newRange = getDuration(fView.getWindowRange());
if (fIsIncreased) {
return newRange > fPreviousRange;
}
return newRange < fPreviousRange;
}
@Override
public String getFailureMessage() {
return "Window range didn't " + (fIsIncreased ? "increase" : "decrease") +
" (previous: " + fPreviousRange + ", actual: " + getDuration(fView.getWindowRange()) + ")";
}
}
}