blob: 7ea7f1f893b93a2ccf6befd88728a96282895d58 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 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.core.tests.runtime;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
/**
*
*/
public class SubMonitorTest extends TestCase {
private long startTime;
/**
* <p>Number of calls to worked() within each test. This was chosen to be significantly larger
* than 1000 to test how well the monitor can optimize unnecessary resolution
* in reported progress, but small enough that the test completes in a reasonable
* amount of time.</p>
*
* <p>Note: changing this constant will invalidate comparisons with old performance data.</p>
*/
public static final int PROGRESS_SIZE = SubProgressTest.PROGRESS_SIZE;
/**
* <p>Depth of the chain chain of progress monitors. In all of the tests, we create
* a nested chain of progress monitors rathar than a single monitor, to test its
* scalability under recursion. We pick a number representing a moderately deep
* recursion, but is still small enough that it could correspond to a real call stack
* without causing overflow.</p>
*
* <p>Note: changing this constant will invalidate comparisons with old performance data.</p>
*/
public static final int CHAIN_DEPTH = SubProgressTest.CHAIN_DEPTH;
public SubMonitorTest() {
super();
}
public SubMonitorTest(String name) {
super(name);
}
protected void setUp() throws Exception {
startTime = System.currentTimeMillis();
super.setUp();
}
protected void tearDown() throws Exception {
long endTime = System.currentTimeMillis();
reportPerformance(getClass().getName(), getName(), startTime, endTime);
super.tearDown();
}
/**
* Reports progress by iterating over a loop of the given size, reporting 1 progress
* at each iteration. Simulates the progress of worked(int) in loops.
*
* @param monitor progress monitor (callers are responsible for calling done() if necessary)
* @param loopSize size of the loop
*/
private static void reportWorkInLoop(IProgressMonitor monitor, int loopSize) {
monitor.beginTask("", loopSize);
for (int i = 0; i < loopSize; i++) {
monitor.worked(1);
}
}
/**
* Reports progress by iterating over a loop of the given size, reporting 1 progress
* at each iteration. Simulates the progress of internalWorked(double) in loops.
*
* @param monitor progress monitor (callers are responsible for calling done() if necessary)
* @param loopSize size of the loop
*/
private static void reportFloatingPointWorkInLoop(IProgressMonitor monitor, int loopSize) {
monitor.beginTask("", loopSize);
for (int i = 0; i < loopSize; i++) {
monitor.internalWorked(1.0d);
}
}
/**
* Ensures that we don't lose any progress when calling setWorkRemaining
*/
public void testSetWorkRemaining() {
TestProgressMonitor monitor = new TestProgressMonitor();
SubMonitor mon = SubMonitor.convert(monitor, 0);
for (int i = 1000; i >= 0; i--) {
mon.setWorkRemaining(i);
mon.internalWorked(0.5);
mon.setWorkRemaining(i);
mon.internalWorked(0.5);
}
monitor.done();
monitor.assertOptimal();
}
/**
* Tests that SubMonitor.done() will clean up after an unconsumed child
* that was created with the explicit constructor
*/
public void testCleanupConstructedChildren() {
TestProgressMonitor top = new TestProgressMonitor();
SubMonitor monitor = SubMonitor.convert(top, 1000);
monitor.beginTask("", 1000);
monitor.newChild(500);
SubMonitor child2 = monitor.newChild(100);
child2.done();
Assert.assertEquals("Ensure that done() reports unconsumed progress, even if beginTask wasn't called", 600.0, top.getTotalWork(), 0.01d);
SubMonitor child3 = monitor.newChild(100);
monitor.done();
Assert.assertEquals("Ensure that done() cleans up after unconsumed children that were created by their constructor", 1000.0, top.getTotalWork(), 0.01d);
child3.worked(100);
Assert.assertEquals("Ensure that children can't report any progress if their parent has completed", 1000.0, top.getTotalWork(), 0.01d);
}
/**
* Tests SubMonitor under typical usage. This is the same
* as the performance test as the same name, but it verifies correctness
* rather than performance.
*/
public void testTypicalUsage() {
TestProgressMonitor monitor = new TestProgressMonitor();
SubMonitorTest.runTestTypicalUsage(monitor);
monitor.assertOptimal();
}
/**
* Tests creating a tree of SubMonitors. This is the same
* as the performance test as the same name, but it verifies correctness
* rather than performance.
*/
public void testCreateTree() {
TestProgressMonitor monitor = new TestProgressMonitor();
SubMonitorTest.runTestCreateTree(monitor);
monitor.assertOptimal();
}
/**
* Ensures that SubMonitor won't report more than 100% progress
* when a child is created with more than the amount of available progress.
*/
public void testChildOverflow() {
TestProgressMonitor top = new TestProgressMonitor();
SubMonitor mon1 = SubMonitor.convert(top, 1000);
Assert.assertEquals(0.0, top.getTotalWork(), 0.1d);
SubMonitor child2 = mon1.newChild(700);
child2.done();
Assert.assertEquals(700.0, top.getTotalWork(), 0.1d);
SubMonitor child3 = mon1.newChild(700);
child3.done();
Assert.assertEquals("The reported work should not exceed 1000", 1000.0, top.getTotalWork(), 0.1d);
mon1.done();
top.done();
}
/**
* Ensures that SubMonitor doesn't propogate redundant progress to its parent.
*/
public void testRedundantWork() {
TestProgressMonitor top = new TestProgressMonitor();
SubMonitor monitor = SubMonitor.convert(top, 10000);
for (int i = 0; i < 10000; i++) {
monitor.setTaskName("Task name");
monitor.subTask("Subtask");
monitor.worked(0);
monitor.internalWorked(0.0);
// Report some real work
monitor.worked(1);
}
top.done();
top.assertOptimal();
}
public void testCancellation() {
TestProgressMonitor root = new TestProgressMonitor();
SubMonitor spm = SubMonitor.convert(root, 1000);
// Test that changes at the root propogate to the child
root.setCanceled(true);
Assert.assertTrue(spm.isCanceled());
root.setCanceled(false);
Assert.assertFalse(spm.isCanceled());
// Test that changes to the child propogate to the root
spm.setCanceled(true);
Assert.assertTrue(root.isCanceled());
spm.setCanceled(false);
Assert.assertFalse(root.isCanceled());
// Test a chain of depth 2
SubMonitor spm2 = spm.newChild(1000);
// Test that changes at the root propogate to the child
root.setCanceled(true);
Assert.assertTrue(spm2.isCanceled());
root.setCanceled(false);
Assert.assertFalse(spm2.isCanceled());
// Test that changes to the child propogate to the root
spm2.setCanceled(true);
Assert.assertTrue(root.isCanceled());
spm2.setCanceled(false);
Assert.assertFalse(root.isCanceled());
}
public void testNullParent() {
// Touch everything in the public API to ensure we don't throw an NPE
SubMonitor mon = SubMonitor.convert(null, 1000);
mon.setWorkRemaining(500);
mon.worked(250);
mon.newChild(200);
mon.internalWorked(50.0);
Assert.assertFalse(mon.isCanceled());
mon.setCanceled(true);
Assert.assertTrue(mon.isCanceled());
mon.subTask("subtask");
mon.setTaskName("taskname");
mon.done();
}
/**
* Tests the automatic cleanup when progress monitors are created via their constructor
*/
public void testNewChild() {
TestProgressMonitor top = new TestProgressMonitor();
SubMonitor mon = SubMonitor.convert(top, 1000);
Assert.assertEquals("Ensure no work has been reported yet", 0.0, top.getTotalWork(), 0.01d);
mon.newChild(100);
Assert.assertEquals("Ensure no work has been reported yet", 0.0, top.getTotalWork(), 0.01d);
mon.newChild(200);
Assert.assertEquals("Ensure monitor1 was collected", 100.0, top.getTotalWork(), 0.01d);
// The following behavior is necessary to make it possible to pass multiple progress monitors as
// arguments to the same method.
Assert.assertEquals("Monitor2 should not have been collected yet (when the public constructor is used, collection should happen when beginTask() or setWorkRemaining() is called.", 100.0, top.getTotalWork(), 0.01d);
SubMonitor monitor4 = mon.newChild(300);
Assert.assertEquals("Now monitor2 should be collected", 300.0, top.getTotalWork(), 0.01d);
monitor4.done();
Assert.assertEquals("Now monitor4 should be collected", 600.0, top.getTotalWork(), 0.01d);
mon.newChild(10);
Assert.assertEquals("Creating a child when there are no active children should not report any work", 600.0, top.getTotalWork(), 0.01d);
mon.worked(20);
// Note: the following behavior is somewhat arbitrary... but we test it to make sure we're consistent
Assert.assertEquals("Reporting work should not cause the active child to be destroyed", 620.0, top.getTotalWork(), 0.01d);
mon.newChild(10);
Assert.assertEquals("monitor5 should have been cleaned up", 630.0, top.getTotalWork(), 0.01d);
mon.internalWorked(60);
Assert.assertEquals("Calling internalWorked should not clean up active children", 690.0, top.getTotalWork(), 0.01d);
// Now create a chain of undisposed children
SubMonitor monitor7 = mon.newChild(100);
SubMonitor monitor8 = monitor7.newChild(40);
monitor8.newChild(10);
mon.done();
Assert.assertEquals("Calling done should clean up unused work", 1000.0, top.getTotalWork(), 0.01d);
}
/**
* Tests creating progress monitors under a custom progress monitor
* parent. This is the same as the performance test as the same name,
* but it verifies correctness rather than performance.
*/
public void testCreateChildrenUnderCustomParent() {
TestProgressMonitor monitor = new TestProgressMonitor();
createChildrenUnderParent(monitor, SubMonitorTest.PROGRESS_SIZE);
// We don't actually expect the progress to be optimal in this case since the progress monitor wouldn't
// know what it was rooted under and would have had to report more progress than necessary... but we
// should be able to check that there was no redundancy.
Assert.assertTrue(monitor.getRedundantWorkCalls() == 0);
Assert.assertTrue(monitor.getWorkCalls() >= 100);
}
/**
* Creates a chain of n nested progress monitors. Calls beginTask on all monitors
* except for the innermost one.
*
* @param parent
* @param depth
* @return the innermost SubMonitor
*/
public static SubMonitor createSubProgressChain(SubMonitor parent, int depth) {
depth--;
parent.beginTask("", 100);
SubMonitor current = parent;
while (depth > 0) {
current.setWorkRemaining(100);
current = current.newChild(100);
depth--;
}
return current;
}
/**
* Creates a balanced binary tree of progress monitors, without calling worked. Tests
* progress monitor creation and cleanup time, and ensures that excess progress is
* being collected when IProgressMonitor.done() is called.
*
* @param monitor progress monitor (callers are responsible for calling done() if necessary)
* @param loopSize total size of the recursion tree
*/
public static void createBalancedTree(IProgressMonitor parent, int loopSize) {
SubMonitor monitor = SubMonitor.convert(parent, 100);
int leftBranch = loopSize / 2;
int rightBranch = loopSize - leftBranch;
if (leftBranch > 1) {
createBalancedTree(monitor.newChild(50), leftBranch);
}
if (rightBranch > 1) {
createBalancedTree(monitor.newChild(50), rightBranch);
}
}
/**
* <p>The innermost loop for the create tree test. We make this a static method so
* that it can be used both in this performance test and in the correctness test.</p>
*
* <p>The performance test ensures that it is fast to create a lot of progress monitors.</p>
*
* <p>The correctness test ensures that creating and destroying SubMonitors
* is enough to report progress, even if worked(int) and worked(double) are never called</p>
*/
public static void runTestCreateTree(IProgressMonitor monitor) {
SubMonitor progress = SubMonitor.convert(monitor, 100);
SubMonitor nestedMonitor = SubMonitorTest.createSubProgressChain(progress, SubMonitorTest.CHAIN_DEPTH);
SubMonitorTest.createBalancedTree(nestedMonitor, SubMonitorTest.PROGRESS_SIZE);
progress.done();
monitor.done();
}
/**
* Reports progress by creating a balanced binary tree of progress monitors. Simulates
* mixed usage of IProgressMonitor in a typical usage. Calls isCanceled once each time work
* is reported. Half of the work is reported using internalWorked and half is reported using worked,
* to simulate mixed usage of the progress monitor.
*
* @param monitor progress monitor (callers are responsible for calling done() if necessary)
* @param loopSize total size of the recursion tree
*/
public static void reportWorkInBalancedTree(IProgressMonitor parent, int loopSize) {
SubMonitor monitor = SubMonitor.convert(parent, 100);
int leftBranch = loopSize / 2;
int rightBranch = loopSize - leftBranch;
if (leftBranch > 1) {
reportWorkInBalancedTree(monitor.newChild(50), leftBranch);
} else {
monitor.worked(25);
monitor.internalWorked(25.0);
monitor.isCanceled();
}
if (rightBranch > 1) {
reportWorkInBalancedTree(monitor.newChild(50), rightBranch);
} else {
monitor.worked(25);
monitor.internalWorked(25.0);
monitor.isCanceled();
}
}
/**
* The innermost loop for the recursion test. We make this a static method so
* that it can be used both in this performance test and in the correctness test.
*/
public static void runTestTypicalUsage(IProgressMonitor monitor) {
SubMonitor progress = SubMonitor.convert(monitor, 100);
SubMonitor nestedMonitor = SubMonitorTest.createSubProgressChain(progress, SubMonitorTest.CHAIN_DEPTH);
SubMonitorTest.reportWorkInBalancedTree(nestedMonitor, SubMonitorTest.PROGRESS_SIZE);
progress.done();
monitor.done();
}
/**
* Tests SubMonitor.worked. This is the same
* as the performance test as the same name, but it verifies correctness
* rather than performance.
*/
public void testWorked() {
TestProgressMonitor monitor = new TestProgressMonitor();
SubMonitor progress = SubMonitor.convert(monitor, 100);
SubMonitor nestedMonitor = createSubProgressChain(progress, SubProgressTest.CHAIN_DEPTH);
reportWorkInLoop(nestedMonitor, SubProgressTest.PROGRESS_SIZE);
progress.done();
monitor.done();
monitor.assertOptimal();
}
/**
* Tests SubMonitor.worked. This is the same
* as the performance test as the same name, but it verifies correctness
* rather than performance.
*/
public void testInternalWorked() {
TestProgressMonitor monitor = new TestProgressMonitor();
SubMonitor progress = SubMonitor.convert(monitor, 100);
SubMonitor nestedMonitor = createSubProgressChain(progress, SubProgressTest.CHAIN_DEPTH);
reportFloatingPointWorkInLoop(nestedMonitor, SubProgressTest.PROGRESS_SIZE);
progress.done();
monitor.done();
monitor.assertOptimal();
}
/**
* Creates and destroys the given number of child progress monitors under the given parent.
*
* @param monitor monitor to create children under. The caller must call done on this monitor
* if necessary.
* @param progressSize total number of children to create.
*/
private static void createChildrenUnderParent(IProgressMonitor parent, int progressSize) {
SubMonitor monitor = SubMonitor.convert(parent, progressSize);
for (int count = 0; count < progressSize; count++) {
SubMonitor mon = monitor.newChild(1);
mon.beginTask("", 100);
}
}
static public void reportPerformance(String className, String methodName, long startTime, long endTime) {
if (false) // enable to see performance results for the progress monitors
System.out.println(className + "#" + methodName + " elapsed time: " + (endTime - startTime) / 1000.0d + "s");
}
}