blob: 5dd26687da1fe1d291ce93f46eed770aa1cd0787 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* 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
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.tests.performance;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.CommandManager;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.commands.contexts.Context;
import org.eclipse.core.commands.contexts.ContextManager;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.BindingManager;
import org.eclipse.jface.bindings.Scheme;
import org.eclipse.jface.bindings.keys.IKeyLookup;
import org.eclipse.jface.bindings.keys.KeyBinding;
import org.eclipse.jface.bindings.keys.KeyLookupFactory;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.ParseException;
import org.eclipse.swt.SWT;
/**
* <p>
* Responsible for testing the commands, contexts and bindings architecture.
* This test does not rely on the existence of the workbench; it operates purely
* on JFace code and lower. See the method comments for descriptions of the
* currently supported performance tests.
* </p>
*
* @since 3.1
*/
public final class CommandsPerformanceTest extends BasicPerformanceTest {
/**
* <p>
* Constructs a branch of a context tree. This creates a branch of the given
* depth -- remembering the identifiers along the way. This method operates
* recursively.
* </p>
* <p>
* TODO This should add a bit of breadth to the tree.
* </p>
*
* @param contextManager
* The context manager in which the contexts should be defined;
* must not be <code>null</code>.
* @param parent
* The parent context identifier for the context to be created;
* may be <code>null</code>.
* @param successors
* The number of successors to create. The depth of the branch to
* be created. If this number is zero, then a context is created,
* but no recursive call is made.
* @param activeContextIds
* The list of active context identifiers; must not be
* <code>null</code>.
*/
private static final void createContext(
final ContextManager contextManager, final String parent,
final int successors, final List activeContextIds) {
final int count = activeContextIds.size();
final String contextString = "context" + count;
final Context context = contextManager.getContext(contextString);
context.define(contextString, contextString, parent);
activeContextIds.add(contextString);
if (successors == 0) {
return;
}
createContext(contextManager, contextString, successors - 1,
activeContextIds);
}
/**
* <p>
* Constructs a branch of a scheme tree. This creates a branch of the given
* depth -- remembering the schemes along the way. This method operates
* recursively.
* </p>
* <p>
* TODO This should add a bit of breadth to the tree.
* </p>
*
* @param bindingManager
* The binding manager in which the schemes should be defined;
* must not be <code>null</code>.
* @param parent
* The parent scheme identifier for the scheme to be created; may
* be <code>null</code>.
* @param successors
* The number of successors to create. The depth of the branch to
* be created. If this number is zero, then a scheme is created,
* but no recursive call is made.
* @param schemes
* The list of created schemes; must not be <code>null</code>.
*/
private static final void createScheme(final BindingManager bindingManager,
final String parent, final int successors, final List schemes) {
final int count = schemes.size();
final String schemeString = "scheme" + count;
final Scheme scheme = bindingManager.getScheme(schemeString);
scheme.define(schemeString, schemeString, parent);
schemes.add(scheme);
if (successors == 0) {
return;
}
createScheme(bindingManager, schemeString, successors - 1, schemes);
}
/**
* The binding manager for the currently running test. <code>null</code>
* if no test is running.
*/
private BindingManager bindingManager = null;
/**
* The command manager for the currently running test. <code>null</code>
* if no test is running.
*/
private CommandManager commandManager = null;
/**
* The context manager for the currently running test. <code>null</code>
* if no test is running.
*/
private ContextManager contextManager = null;
/**
* Constructs an instance of <code>CommandsPerformanceTest</code>.
*
* @param testName
* Test's name.
*/
public CommandsPerformanceTest(final String name) {
super(name);
}
/**
* <p>
* Sets up a sufficiently complex set of bindings.
* </p>
* <p>
* At the time of writing, Eclipse's key binding set contains about five
* hundred bindings. Of these, 140 specify platform information, while only
* 5 specify locale information. About 40 are deletion markers. The deepest
* point in the context tree is four levels. There are two schemes.
* </p>
* <p>
* The test binding set contains five thousand bindings. About 1400 specify
* either locale or platform information. Five hundred are deletion markers.
* The deepest point in the context tree is 40 levels. There are twenty
* schemes.
* </p>
* <p>
* The depth of the locale and platform tree is the same in both real life
* and the test case. It is difficult to imagine why the locale list would
* ever be anything but four elements, or why the platform list would ever
* be anything but three elements.
* </p>
*
* @throws NotDefinedException
* If something went wrong initializing the active scheme.
*/
protected final void doSetUp() throws NotDefinedException, Exception {
super.doSetUp();
/*
* The constants to use in creating the various objects. The platform
* locale count must be greater than or equal to the number of deletion
* markers. Deletion markers are typically created based on the platform
* or locale.
*/
final int contextTreeDepth = 40;
final int schemeDepth = 20;
final int bindingCount = 5000;
final int platformLocaleCount = 1400;
final int deletionMarkers = 500;
final String currentLocale = Locale.getDefault().toString();
final String currentPlatform = SWT.getPlatform();
// Set-up a table of modifier keys.
final IKeyLookup lookup = KeyLookupFactory.getDefault();
final int modifierKeys0 = 0;
final int modifierKeys1 = lookup.getAlt();
final int modifierKeys2 = lookup.getCommand();
final int modifierKeys3 = lookup.getCtrl();
final int modifierKeys4 = lookup.getShift();
final int modifierKeys5 = lookup.getAlt() | lookup.getCommand();
final int modifierKeys6 = lookup.getAlt() | lookup.getCtrl();
final int modifierKeys7 = lookup.getAlt() | lookup.getShift();
final int modifierKeys8 = lookup.getCommand() | lookup.getCtrl();
final int modifierKeys9 = lookup.getCommand() | lookup.getShift();
final int modifierKeys10 = lookup.getCtrl() | lookup.getShift();
final int modifierKeys11 = lookup.getAlt() | lookup.getCommand()
| lookup.getCtrl();
final int modifierKeys12 = lookup.getAlt() | lookup.getCommand()
| lookup.getShift();
final int modifierKeys13 = lookup.getAlt() | lookup.getCtrl()
| lookup.getShift();
final int modifierKeys14 = lookup.getCommand() | lookup.getCtrl()
| lookup.getShift();
final int modifierKeys15 = lookup.getAlt() | lookup.getCommand()
| lookup.getCtrl() | lookup.getShift();
final int[] modifierKeyTable = { modifierKeys0, modifierKeys1,
modifierKeys2, modifierKeys3, modifierKeys4, modifierKeys5,
modifierKeys6, modifierKeys7, modifierKeys8, modifierKeys9,
modifierKeys10, modifierKeys11, modifierKeys12, modifierKeys13,
modifierKeys14, modifierKeys15 };
// Initialize the command manager.
commandManager = new CommandManager();
// Initialize the contexts.
contextManager = new ContextManager();
final List activeContextIds = new ArrayList();
createContext(contextManager, null, contextTreeDepth, activeContextIds);
contextManager.setActiveContextIds(new HashSet(activeContextIds));
// Initialize the schemes.
bindingManager = new BindingManager(contextManager, commandManager);
final List schemes = new ArrayList();
createScheme(bindingManager, null, schemeDepth, schemes);
bindingManager
.setActiveScheme((Scheme) schemes.get(schemes.size() - 1));
// Create the deletion markers.
final Binding[] bindings = new Binding[bindingCount];
for (int i = 0; i < deletionMarkers; i++) {
/*
* Set-up the locale and platform. These are based on the numbers
* given above.
*/
String locale = null;
String platform = null;
if (i < platformLocaleCount) {
switch (i % 4) {
case 0:
locale = currentLocale;
break;
case 1:
platform = currentPlatform;
break;
case 2:
locale = "gibberish";
break;
case 3:
platform = "gibberish";
break;
}
}
// Build a key sequence.
final char character = (char) ('A' + (i % 26));
final int modifierKeys = modifierKeyTable[(i / 26)
% modifierKeyTable.length];
final KeyStroke keyStroke = KeyStroke.getInstance(modifierKeys,
character);
final KeySequence keySequence = KeySequence.getInstance(keyStroke);
// Build the other parameters.
final String schemeId = ((Scheme) schemes.get(i % schemes.size()))
.getId();
final String contextId = (String) activeContextIds.get(i
% activeContextIds.size());
final int type = (i % 2);
// Construct the binding.
final Binding binding = new KeyBinding(keySequence, null, schemeId,
contextId, locale, platform, null, type);
bindings[i] = binding;
}
/*
* Now create the regular bindings. By using the same loop structure and
* resetting the index to zero, we ensure that the deletion markers will
* actually delete something.
*/
for (int i = 0; i < bindingCount - deletionMarkers; i++) {
/*
* Set-up the locale and platform for those bindings that will not
* be used to match the above deletion markers. These are based on
* the numbers given above.
*/
String locale = null;
String platform = null;
if ((i > deletionMarkers) && (i < platformLocaleCount)) {
switch (i % 4) {
case 0:
locale = currentLocale;
break;
case 1:
platform = currentPlatform;
break;
case 2:
locale = "gibberish";
break;
case 3:
platform = "gibberish";
break;
}
}
// Build a key sequence.
final char character = (char) ('A' + (i % 26));
final int modifierKeys = modifierKeyTable[(i / 26)
% modifierKeyTable.length];
final KeyStroke keyStroke = KeyStroke.getInstance(modifierKeys,
character);
final KeySequence keySequence = KeySequence.getInstance(keyStroke);
// Build the other parameters.
final String commandId = "command" + i;
final String schemeId = ((Scheme) schemes.get(i % schemes.size()))
.getId();
final String contextId = (String) activeContextIds.get(i
% activeContextIds.size());
final int type = (i % 2);
// Construct the binding.
final Command command = commandManager.getCommand(commandId);
final ParameterizedCommand parameterizedCommand = new ParameterizedCommand(
command, null);
final Binding binding = new KeyBinding(keySequence,
parameterizedCommand, schemeId, contextId, locale,
platform, null, type);
bindings[i + deletionMarkers] = binding;
}
bindingManager.setBindings(bindings);
}
protected final void doTearDown() throws Exception {
bindingManager = null;
commandManager = null;
contextManager = null;
super.doTearDown();
}
/**
* <p>
* Tests how long it takes to access the cache if no conditions have
* changed. It measures how long it takes to look up the computation from
* the cache one million times.
* </p>
*
* @throws ParseException
* If "CTRL+F" can't be parsed for some strange reason.
*/
public final void testBindingCacheHitHard() throws ParseException {
// Constants
final int cacheHits = 1000000;
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
// Compute once.
bindingManager.getPartialMatches(keySequence);
// Time how long it takes to access the cache;
startMeasuring();
for (int i = 0; i < cacheHits; i++) {
bindingManager.getPartialMatches(keySequence);
}
stopMeasuring();
commitMeasurements();
assertPerformance();
}
/**
* <p>
* Tests how long it takes to access the cache if no conditions have
* changed. It measures how long it takes to look up the computation from
* the cache one million times. In this test, the look-up is done in reverse --
* from command identifier to trigger.
* </p>
*
* @throws ParseException
* If "CTRL+F" can't be parsed for some strange reason.
*/
public final void testBindingCacheHitHardReverse() throws ParseException {
// Constants
final int cacheHits = 1000000;
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
// Compute once.
bindingManager.getPartialMatches(keySequence);
// Time how long it takes to access the cache;
startMeasuring();
for (int i = 0; i < cacheHits; i++) {
bindingManager.getActiveBindingsFor((ParameterizedCommand) null);
}
stopMeasuring();
commitMeasurements();
assertPerformance();
}
/**
* <p>
* Tests how long it takes to access the cache if the conditions have
* changed, but the cache contains a matching entry. It measures how long it
* takes to look up the computation from the cache forty thousand times.
* </p>
*
* @throws ParseException
* If "CTRL+F" can't be parsed for some strange reason.
*/
public final void testBindingCacheHitSoft() throws ParseException {
// Constants
final int cacheHits = 10000;
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
// Compute once for each context set.
final Set contextSet1 = contextManager.getActiveContextIds();
bindingManager.getPartialMatches(keySequence);
final List contextList = new ArrayList(contextSet1);
contextList.remove(contextList.size() - 1);
final Set contextSet2 = new HashSet(contextList);
contextManager.setActiveContextIds(contextSet2);
bindingManager.getPartialMatches(keySequence);
// Time how long it takes to access the cache;
startMeasuring();
for (int i = 0; i < cacheHits; i++) {
if ((i % 2) == 0) {
contextManager.setActiveContextIds(contextSet1);
} else {
contextManager.setActiveContextIds(contextSet2);
}
bindingManager.getPartialMatches(keySequence);
}
stopMeasuring();
commitMeasurements();
assertPerformance();
}
/**
* <p>
* Tests how long it takes to do a full computation (i.e., a cache miss) on
* an exceptionally large set of bindings. The binding set tries to mimick
* some of the same properties of a "real" binding set.
* </p>
*
* @throws ParseException
* If "CTRL+F" can't be parsed for some strange reason.
*/
public final void testBindingCacheMissLarge() throws ParseException {
// Constants
final KeySequence keySequence = KeySequence.getInstance("CTRL+F");
// Time how long it takes to solve the binding set.
startMeasuring();
bindingManager.getPartialMatches(keySequence);
stopMeasuring();
commitMeasurements();
assertPerformance();
}
}