blob: 75d35e59d295839fa5c2c79ba44a06a0d7faa8fb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2017 Ericsson and others
*
* 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
*
* Contributors:
* Matthew Khouzam - Initial API and implementation
* Mikael Ferland - Add tests for BasicSymbolProvider dialog
*******************************************************************************/
package org.eclipse.tracecompass.lttng2.ust.ui.swtbot.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
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.SWTBot;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences;
import org.eclipse.swtbot.swt.finder.waits.Conditions;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotToolbarButton;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.eclipse.tracecompass.analysis.profiling.ui.views.flamechart.FlameChartView;
import org.eclipse.tracecompass.testtraces.ctf.CtfTestTrace;
import org.eclipse.tracecompass.tmf.core.signal.TmfSelectionRangeUpdatedSignal;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
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.ctf.core.tests.shared.CtfTmfTestTraceUtils;
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.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.tests.shared.WaitUtils;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView;
import org.eclipse.ui.IWorkbenchPart;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
/**
* Test for the Call Stack view in trace compass
*/
@RunWith(SWTBotJunit4ClassRunner.class)
public class FlameChartViewTest {
private static final String UST_ID = "org.eclipse.linuxtools.lttng2.ust.tracetype";
private static final String PROJECT_NAME = "TestForCallstack";
private static final String TRACE = "glxgears-cyg-profile";
private static final String PROCESS = "UNKNOWN";
private static final @NonNull String THREAD = "glxgears-16073";
/** The Log4j logger instance. */
private static final Logger sfLogger = Logger.getRootLogger();
private static SWTWorkbenchBot sfBot;
/**
* Start time of the trace
*/
private static final @NonNull ITmfTimestamp START_TIME = TmfTimestamp.fromNanos(1378850463596911581l);
/**
* Timestamp for loading mapping files test
*/
private static final long TIMESTAMP = 1378850463804917148l;
/**
* Timestamps of consecutive events in the trace
*/
private static final long TIMESTAMPS[] = new long[] {
1378850463804898643l,
1378850463804899057l,
1378850463804900219l,
1378850463804900678l,
1378850463804901308l,
1378850463804901909l,
1378850463804902763l,
1378850463804903168l,
1378850463804903766l,
1378850463804904165l,
1378850463804904970l
};
/**
* Stack frames of consecutive events in the trace
*/
private static final String[] STACK_FRAMES[] = new String[][] {
{ "0x40472b", "0x4045c8", "0x404412" },
{ "0x40472b", "0x4045c8", "0x404412", "0x40392b" },
{ "0x40472b", "0x4045c8", "0x404412" },
{ "0x40472b", "0x4045c8" },
{ "0x40472b", "0x4045c8", "0x404412" },
{ "0x40472b", "0x4045c8", "0x404412", "0x40392b" },
{ "0x40472b", "0x4045c8", "0x404412" },
{ "0x40472b", "0x4045c8" },
{ "0x40472b", "0x4045c8", "0x404412" },
{ "0x40472b", "0x4045c8", "0x404412", "0x40392b" },
{ "0x40472b", "0x4045c8", "0x404412" },
};
/** Tooltips of the toolbar buttons */
private static final @NonNull String CONFIGURE_SYMBOL_PROVIDERS = "Configure how the addresses are mapped to function names";
// Separator
private static final @NonNull String SORT_BY_NAME = "Name";
private static final @NonNull String SORT_BY_ID = "PID/TID";
private static final @NonNull String SORT_BY_START = "Start time";
// Separator
private static final @NonNull String SHOW_VIEW_FILTERS = "Show View Filters";
// Separator
private static final @NonNull String RESET_TIME_SCALE = "Reset the Time Scale to Default";
private static final @NonNull String SELECT_PREVIOUS_STATE_CHANGE = "Select Previous State Change";
private static final @NonNull String SELECT_NEXT_STATE_CHANGE = "Select Next State Change";
// Separator
private static final @NonNull String ADD_BOOKMARK = "Add Bookmark...";
private static final @NonNull String PREVIOUS_MARKER = "Previous Marker";
private static final @NonNull String NEXT_MARKER = "Next Marker";
// Separator
private static final @NonNull String SELECT_PREVIOUS_ITEM = "Select Previous Item";
private static final @NonNull String SELECT_NEXT_ITEM = "Select Next Item";
private static final @NonNull String ZOOM_IN = "Zoom In";
private static final @NonNull String ZOOM_OUT = "Zoom Out";
// Separator
private static final @NonNull String PIN_VIEW = "Pin View";
private static final List<String> TOOLBAR_BUTTONS_TOOLTIPS = ImmutableList.of(
CONFIGURE_SYMBOL_PROVIDERS,
"",
SHOW_VIEW_FILTERS,
"",
RESET_TIME_SCALE, SELECT_PREVIOUS_STATE_CHANGE, SELECT_NEXT_STATE_CHANGE,
"",
ADD_BOOKMARK, PREVIOUS_MARKER, NEXT_MARKER,
"",
SELECT_PREVIOUS_ITEM, SELECT_NEXT_ITEM, ZOOM_IN, ZOOM_OUT,
"",
PIN_VIEW);
/**
* Initialization
*/
@BeforeClass
public static void init() {
SWTBotUtils.initialize();
Thread.currentThread().setName("SWTBot Thread"); // for the debugger
/* set up for swtbot */
SWTBotPreferences.TIMEOUT = 20000; /* 20 second timeout */
sfLogger.removeAllAppenders();
sfLogger.addAppender(new ConsoleAppender(new SimpleLayout()));
sfBot = new SWTWorkbenchBot();
SWTBotUtils.closeView("Statistics", sfBot);
/* finish waiting for eclipse to load */
WaitUtils.waitForJobs();
}
/**
* Clean up
*/
@AfterClass
public static void afterClass() {
sfLogger.removeAllAppenders();
}
/**
* Open a trace in an editor
*/
@Before
public void beforeTest() {
SWTBotUtils.createProject(PROJECT_NAME);
SWTBotTreeItem treeItem = SWTBotUtils.selectTracesFolder(sfBot, PROJECT_NAME);
assertNotNull(treeItem);
final CtfTestTrace cygProfile = CtfTestTrace.CYG_PROFILE;
final File file = new File(CtfTmfTestTraceUtils.getTrace(cygProfile).getPath());
CtfTmfTestTraceUtils.dispose(cygProfile);
SWTBotUtils.openTrace(PROJECT_NAME, file.getAbsolutePath(), UST_ID);
SWTBotUtils.activateEditor(sfBot, file.getName());
SWTBotUtils.openView(FlameChartView.ID);
WaitUtils.waitForJobs();
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
IWorkbenchPart part = viewBot.getViewReference().getPart(false);
sfBot.waitUntil(ConditionHelpers.timeGraphIsReadyCondition((AbstractTimeGraphView) part, new TmfTimeRange(START_TIME, START_TIME), START_TIME));
}
/**
* Close the editor
*/
@After
public void tearDown() {
sfBot.closeAllEditors();
SWTBotUtils.deleteProject(PROJECT_NAME, sfBot);
}
/**
* Test if callstack is populated
*/
@Test
public void testOpenCallstack() {
SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
viewBot.setFocus();
waitForSymbolNames("0x40472b");
}
/**
* Test check callstack at a time
*/
@Test
public void testGoToTimeAndCheckStack() {
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
viewBot.setFocus();
WaitUtils.waitForJobs();
goToTime(TIMESTAMPS[0]);
waitForSymbolNames(STACK_FRAMES[0]);
}
/**
* Test check callstack at a time after navigating
*/
@Test
public void testGoToTimeGoBackAndForthAndCheckStack() {
int currentEventOffset = 0;
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
goToTime(TIMESTAMPS[currentEventOffset]);
// forward 10 times
for (int i = 0; i < 10; i++) {
viewBot.toolbarPushButton(SELECT_NEXT_STATE_CHANGE).click();
currentEventOffset++;
sfBot.waitUntil(ConditionHelpers.selectionInEventsTable(sfBot, TIMESTAMPS[currentEventOffset]));
WaitUtils.waitForJobs();
waitForSymbolNames(STACK_FRAMES[currentEventOffset]);
}
// back twice
for (int i = 0; i < 2; i++) {
viewBot.toolbarPushButton(SELECT_PREVIOUS_STATE_CHANGE).click();
currentEventOffset--;
sfBot.waitUntil(ConditionHelpers.selectionInEventsTable(sfBot, TIMESTAMPS[currentEventOffset]));
WaitUtils.waitForJobs();
waitForSymbolNames(STACK_FRAMES[currentEventOffset]);
}
// move up and down once to make sure it doesn't explode
viewBot.toolbarPushButton(SELECT_PREVIOUS_ITEM).click();
WaitUtils.waitForJobs();
viewBot.toolbarPushButton(SELECT_NEXT_ITEM).click();
WaitUtils.waitForJobs();
// Zoom in and out too
viewBot.toolbarPushButton(ZOOM_IN).click();
WaitUtils.waitForJobs();
viewBot.toolbarPushButton(ZOOM_OUT).click();
WaitUtils.waitForJobs();
}
/**
* Test check callstack at a time with sorting, the trace is not sortable,
* this is a smoke test
*/
@Test
public void testGoToTimeSortAndCheckStack() {
goToTime(TIMESTAMPS[0]);
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
viewBot.setFocus();
SWTBotTree tree = viewBot.bot().tree();
tree.header(SORT_BY_NAME).click();
tree.header(SORT_BY_ID).click();
tree.header(SORT_BY_START).click();
viewBot.setFocus();
WaitUtils.waitForJobs();
waitForSymbolNames(STACK_FRAMES[0]);
}
private static void waitForSymbolNames(String... symbolNames) {
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
List<String> symbolNameList = Lists.newArrayList(symbolNames);
WaitUtils.waitUntil(vBot -> symbolNameList.equals(getVisibleStackFrames(vBot)),
viewBot, "Wrong symbol names, expected:" + symbolNameList
+ ", got: " + getVisibleStackFrames(viewBot));
}
private static List<String> getVisibleStackFrames(final SWTBotView viewBot) {
SWTBotTimeGraph timeGraph = new SWTBotTimeGraph(viewBot.bot());
List<String> stackFrames = new ArrayList<>();
for (SWTBotTimeGraphEntry entry : timeGraph.getEntry(TRACE, PROCESS, THREAD).getEntries()) {
String name = entry.getText();
if (!name.isEmpty()) {
stackFrames.add(name);
}
}
return stackFrames;
}
private static void goToTime(long timestamp) {
ITmfTimestamp time = TmfTimestamp.fromNanos(timestamp);
SWTBotTable table = sfBot.activeEditor().bot().table();
table.setFocus();
WaitUtils.waitForJobs();
TmfSignalManager.dispatchSignal(new TmfSelectionRangeUpdatedSignal(table.widget, time));
sfBot.waitUntil(ConditionHelpers.selectionInEventsTable(sfBot, timestamp));
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
IWorkbenchPart part = viewBot.getViewReference().getPart(false);
sfBot.waitUntil(ConditionHelpers.timeGraphIsReadyCondition((AbstractTimeGraphView) part, new TmfTimeRange(time, time), time));
}
/**
* Test manipulating valid and invalid mapping files (add, remove and change
* priority of files)
*
* @throws IOException
* Missing file
*/
@Test
public void testManipulatingMappingFiles() throws IOException {
// 1- Open valid mapping files and invalid mapping file
URL mapUrlA = CtfTmfTestTraceUtils.class.getResource("cyg-profile-mapping.txt");
URL mapUrlB = CtfTmfTestTraceUtils.class.getResource("dummy-mapping.txt");
URL mapUrlC = CtfTmfTestTraceUtils.class.getResource("invalid-cyg-profile-mapping.txt");
URL mapUrlD = CtfTmfTestTraceUtils.class.getResource("random.out");
URL mapUrlE = CtfTmfTestTraceUtils.class.getResource("win32Random.exe");
String absoluteFileA = FileLocator.toFileURL(mapUrlA).getFile();
String absoluteFileB = FileLocator.toFileURL(mapUrlB).getFile();
String absoluteFileC = FileLocator.toFileURL(mapUrlC).getFile();
String absoluteFileD = FileLocator.toFileURL(mapUrlD).getFile();
String absoluteFileE = FileLocator.toFileURL(mapUrlE).getFile();
String[] overrideFiles = { absoluteFileA, absoluteFileA, absoluteFileB, absoluteFileC, absoluteFileD, absoluteFileE };
TmfFileDialogFactory.setOverrideFiles(overrideFiles);
SWTBotShell shell = openSymbolProviderDialog();
final SWTBot symbolDialog = shell.bot();
symbolDialog.button("Add...").click();
final SWTBot errorDialog = sfBot.shell("Import failure").bot();
errorDialog.button("OK").click();
final SWTBotTable table = symbolDialog.table();
assertEquals(4, table.rowCount());
assertEquals(absoluteFileA, table.getTableItem(0).getText());
assertEquals(absoluteFileB, table.getTableItem(1).getText());
assertEquals(absoluteFileD, table.getTableItem(2).getText());
assertEquals(absoluteFileE, table.getTableItem(3).getText());
// 2- Change priority of mapping files
table.select(0);
symbolDialog.button("Down").click().click().click();
assertEquals(absoluteFileB, table.getTableItem(0).getText());
assertEquals(absoluteFileD, table.getTableItem(1).getText());
assertEquals(absoluteFileE, table.getTableItem(2).getText());
assertEquals(absoluteFileA, table.getTableItem(3).getText());
symbolDialog.button("Up").click().click().click();
assertEquals(absoluteFileA, table.getTableItem(0).getText());
assertEquals(absoluteFileB, table.getTableItem(1).getText());
assertEquals(absoluteFileD, table.getTableItem(2).getText());
assertEquals(absoluteFileE, table.getTableItem(3).getText());
// 3- Remove multiple mapping files
table.select(0, 1);
symbolDialog.button("Remove").click();
assertEquals(2, table.rowCount());
// 4- Close symbol provider dialog
symbolDialog.button("Cancel").click();
}
/**
* Test loading conflicting mapping files.
*
* @throws IOException
* Missing file
*/
@Test
public void testLoadingMappingFiles() throws IOException {
// 1- Open conflicting mapping files
URL mapUrlA = CtfTmfTestTraceUtils.class.getResource("cyg-profile-mapping.txt");
URL mapUrlB = CtfTmfTestTraceUtils.class.getResource("dummy-mapping.txt");
String absoluteFileA = FileLocator.toFileURL(mapUrlA).getFile();
String absoluteFileB = FileLocator.toFileURL(mapUrlB).getFile();
String[] overrideFiles = { absoluteFileA, absoluteFileB };
TmfFileDialogFactory.setOverrideFiles(overrideFiles);
SWTBotShell shell = openSymbolProviderDialog();
final SWTBot symbolDialog = shell.bot();
symbolDialog.button("Add...").click();
symbolDialog.button("OK").click();
// 2- Ensure symbols are loaded and prioritized
sfBot.viewById(FlameChartView.ID).setFocus();
WaitUtils.waitForJobs();
goToTime(TIMESTAMP);
waitForSymbolNames("main", "event_loop", "draw_frame", "draw_gears", "drawB");
}
private static SWTBotShell openSymbolProviderDialog() {
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
viewBot.setFocus();
viewBot.toolbarButton(CONFIGURE_SYMBOL_PROVIDERS).click();
return sfBot.shell("Symbol mapping");
}
/**
* Test check callstack at a time with function map
*
* @throws IOException
* Missing file
*/
@Test
public void testGoToTimeAndCheckStackWithNames() throws IOException {
goToTime(TIMESTAMPS[0]);
final SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
viewBot.setFocus();
URL mapUrl = CtfTmfTestTraceUtils.class.getResource("cyg-profile-mapping.txt");
String absoluteFile = FileLocator.toFileURL(mapUrl).getFile();
TmfFileDialogFactory.setOverrideFiles(absoluteFile);
SWTBotShell shell = openSymbolProviderDialog();
SWTBot shellBot = shell.bot();
shellBot.button("Add...").click();
shellBot.button("OK").click();
shellBot.waitUntil(Conditions.shellCloses(shell));
SWTBotTimeGraph timeGraph = new SWTBotTimeGraph(viewBot.bot());
SWTBotTimeGraphEntry[] threads = timeGraph.getEntry(TRACE, PROCESS).getEntries();
assertEquals(1, threads.length);
assertEquals(THREAD, threads[0].getText(0));
waitForSymbolNames("main", "event_loop", "handle_event");
}
/**
* Test Call Stack tool bar
*/
@Test
public void testCallStackToolBar() {
SWTBotView viewBot = sfBot.viewById(FlameChartView.ID);
viewBot.setFocus();
List<String> buttons = Lists.transform(viewBot.getToolbarButtons(), SWTBotToolbarButton::getToolTipText);
assertEquals(TOOLBAR_BUTTONS_TOOLTIPS, buttons);
}
}