Index: leaks/org/eclipse/jdt/ui/tests/leaks/UndoManagerLeakTest.java =================================================================== RCS file: /home/eclipse/org.eclipse.jdt.ui.tests/leaks/org/eclipse/jdt/ui/tests/leaks/UndoManagerLeakTest.java,v retrieving revision 1.3 diff -u -r1.3 UndoManagerLeakTest.java --- leaks/org/eclipse/jdt/ui/tests/leaks/UndoManagerLeakTest.java 17 Jun 2005 15:49:46 -0000 1.3 +++ leaks/org/eclipse/jdt/ui/tests/leaks/UndoManagerLeakTest.java 1 Sep 2005 17:45:25 -0000 @@ -72,6 +72,7 @@ internalTestRandomAccess(); internalTestRandomAccessAsCompound(); + fUndoManager.disconnect(); fUndoManager= null; fShell.dispose(); Index: ui/org/eclipse/jdt/ui/tests/LeakTestSuite.java =================================================================== RCS file: /home/eclipse/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/LeakTestSuite.java,v retrieving revision 1.11 diff -u -r1.11 LeakTestSuite.java --- ui/org/eclipse/jdt/ui/tests/LeakTestSuite.java 3 Apr 2005 13:53:17 -0000 1.11 +++ ui/org/eclipse/jdt/ui/tests/LeakTestSuite.java 1 Sep 2005 17:45:25 -0000 @@ -14,6 +14,7 @@ import junit.framework.TestSuite; import org.eclipse.jdt.ui.tests.leaks.JavaLeakTest; +import org.eclipse.jdt.ui.tests.leaks.SharedUndoManagerLeakTest; import org.eclipse.jdt.ui.tests.leaks.UndoManagerLeakTest; @@ -38,6 +39,7 @@ public LeakTestSuite() { addTest(JavaLeakTest.suite()); addTest(UndoManagerLeakTest.suite()); + addTest(SharedUndoManagerLeakTest.suite()); } } Index: leaks/org/eclipse/jdt/ui/tests/leaks/SharedUndoManagerLeakTest.java =================================================================== RCS file: leaks/org/eclipse/jdt/ui/tests/leaks/SharedUndoManagerLeakTest.java diff -N leaks/org/eclipse/jdt/ui/tests/leaks/SharedUndoManagerLeakTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ leaks/org/eclipse/jdt/ui/tests/leaks/SharedUndoManagerLeakTest.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,267 @@ +/******************************************************************************* + * 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.jdt.ui.tests.leaks; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.DocumentUndoManager; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.IUndoManager; +import org.eclipse.jface.text.Position; +import org.eclipse.jface.text.SharedDocumentUndoManager; +import org.eclipse.jface.text.TextViewer; + +import org.eclipse.jdt.ui.leaktest.LeakTestCase; +import org.eclipse.jdt.ui.leaktest.LeakTestSetup; + +/** + * Test for leaks in SharedDocumentUndoManager. + * + * @since 3.2 + */ +public class SharedUndoManagerLeakTest extends LeakTestCase { + + /** The maximum undo level. */ + private static final int MAX_UNDO_LEVEL = 256; + + /** The shell. */ + private Shell fShell; + /** The text viewer. */ + private ITextViewer fTextViewer; + /** The undo manager. */ + private IUndoManager fUndoManager; + + public static Test suite() { + return new LeakTestSetup(new TestSuite(SharedUndoManagerLeakTest.class)); + } + + /* + * @see TestCase#TestCase(String) + */ + public SharedUndoManagerLeakTest(final String name) { + super(name); + } + + /* + * @see TestCase#setUp() + */ + protected void setUp() { + fShell= new Shell(); + fUndoManager= new SharedDocumentUndoManager(MAX_UNDO_LEVEL); + fTextViewer= new TextViewer(fShell, SWT.NONE); + fTextViewer.setUndoManager(fUndoManager); + fUndoManager.connect(fTextViewer); + } + + public void testLeak() { + internalTestConvertLineDelimiters(); + internalTestRandomAccess(); + internalTestRandomAccessAsCompound(); + + fUndoManager.disconnect(); + fUndoManager= null; + + fShell.dispose(); + fShell= null; + + fTextViewer= null; + + assertInstanceCount(TextViewer.class, 0); + assertInstanceCount(SharedDocumentUndoManager.class, 0); + assertInstanceCount(getSharedUndoManagersInnerClass("TextInputListener"), 0); + assertInstanceCount(getSharedUndoManagersInnerClass("DocumentUndoListener"), 0); + assertInstanceCount(getSharedUndoManagersInnerClass("KeyAndMouseListener"), 0); + + // This will fail until bug #108600 is fixed + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=108600 + assertInstanceCount(DocumentUndoManager.class, 0); + assertInstanceCount(getDocumentUndoManagersInnerClass("DocumentListener"), 0); + assertInstanceCount(getDocumentUndoManagersInnerClass("HistoryListener"), 0); + + } + + private Class getSharedUndoManagersInnerClass(String className) { + try { + return Class.forName("org.eclipse.jface.text.SharedDocumentUndoManager$" + className, true, getClass().getClassLoader()); + } catch (ClassNotFoundException e) { + fail(); + return null; + } + } + + private Class getDocumentUndoManagersInnerClass(String className) { + try { + return Class.forName("org.eclipse.jface.text.DocumentUndoManager$" + className, true, getClass().getClassLoader()); + } catch (ClassNotFoundException e) { + fail(); + return null; + } + } + + + /** + * Test for line delimiter conversion. + */ + private void internalTestConvertLineDelimiters() { + final String original= "a\r\nb\r\n"; + final IDocument document= new Document(original); + fTextViewer.setDocument(document); + + try { + document.replace(1, 2, "\n"); + document.replace(3, 2, "\n"); + } catch (BadLocationException e) { + assertTrue(false); + } + + assertTrue(fUndoManager.undoable()); + fUndoManager.undo(); + assertTrue(fUndoManager.undoable()); + fUndoManager.undo(); + + final String reverted= document.get(); + + assertEquals(original, reverted); + } + + /** + * Randomly applies document changes. + */ + private void internalTestRandomAccess() { + final int RANDOM_STRING_LENGTH= 50; + final int RANDOM_REPLACE_COUNT= 100; + + assertTrue(RANDOM_REPLACE_COUNT >= 1); + assertTrue(RANDOM_REPLACE_COUNT <= MAX_UNDO_LEVEL); + + String original= createRandomString(RANDOM_STRING_LENGTH); + final IDocument document= new Document(original); + fTextViewer.setDocument(document); + + doChange(document, RANDOM_REPLACE_COUNT); + + assertTrue(fUndoManager.undoable()); + while (fUndoManager.undoable()) + fUndoManager.undo(); + + final String reverted= document.get(); + + assertEquals(original, reverted); + } + + private void doChange(IDocument document, int count) { + try { + for (int i= 0; i < count; i++) { + final Position position= createRandomPositionPoisson(document.getLength()); + final String string= createRandomStringPoisson(4); + document.replace(position.getOffset(), position.getLength(), string); + } + } catch (BadLocationException e) { + assertTrue(false); + } + } + + private void internalTestRandomAccessAsCompound() { + final int RANDOM_STRING_LENGTH= 50; + final int RANDOM_REPLACE_COUNT= 100; + + assertTrue(RANDOM_REPLACE_COUNT >= 1); + assertTrue(RANDOM_REPLACE_COUNT <= MAX_UNDO_LEVEL); + + String original= createRandomString(RANDOM_STRING_LENGTH); + final IDocument document= new Document(original); + fTextViewer.setDocument(document); + + fUndoManager.beginCompoundChange(); + doChange(document, RANDOM_REPLACE_COUNT); + fUndoManager.endCompoundChange(); + + assertTrue(fUndoManager.undoable()); + while (fUndoManager.undoable()) + fUndoManager.undo(); + assertTrue(!fUndoManager.undoable()); + + final String reverted= document.get(); + + assertEquals(original, reverted); + } + + private static String createRandomString(int length) { + final StringBuffer buffer= new StringBuffer(); + + for (int i= 0; i < length; i++) + buffer.append(getRandomCharacter()); + + return buffer.toString(); + } + + private static final char getRandomCharacter() { +// return Math.random() < 0.5 +// ? '\r' +// : '\n'; + + // XXX must include \r, \n, \t + return (char) (32 + 95 * Math.random()); + } + + private static String createRandomStringPoisson(int mean) { + final int length= getRandomPoissonValue(2); + return createRandomString(length); + } + + private static Position createRandomPositionPoisson(int documentLength) { + + final float random= (float) Math.random(); + final int offset= (int) (random * (documentLength + 1)); + + int length= getRandomPoissonValue(2); + if (offset + length > documentLength) + length= documentLength - offset; + + return new Position(offset, length); + } + + private static int getRandomPoissonValue(int mean) { + final int MAX_VALUE= 10; + + final float random= (float) Math.random(); + float probability= 0; + int i= 0; + while (probability < 1 && i < MAX_VALUE) { + probability += getPoissonDistribution(mean, i); + if (random <= probability) + break; + i++; + } + return i; + } + + private static float getPoissonDistribution(float lambda, int k) { + return (float) (Math.exp(-lambda) * Math.pow(lambda, k) / faculty(k)); + } + + /** + * Returns the faculty of k. + */ + private static final int faculty(int k) { + return k == 0 + ? 1 + : k * faculty(k - 1); + } + +}