blob: 361e701d4ac29b388ddbe4d7caf69d083792f08b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 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.jface.text.tests;
import junit.framework.TestCase;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.IUndoManager;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.wst.sse.core.internal.ltk.modelhandler.IModelHandler;
import org.eclipse.wst.sse.core.internal.modelhandler.ModelHandlerRegistry;
import org.eclipse.wst.sse.core.internal.text.BasicStructuredDocument;
/**
* Tests for DefaultUndoManager. Changed from the JFace original to creates
* and test against a structured document.
*/
public abstract class AbstractUndoManagerTest extends TestCase {
/** 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;
private static final int LOOP_COUNT= 20;
//--- Static data sets for comparing scenarios - obtained from capturing random data ---
/** Original document */
private static final String INITIAL_DOCUMENT_CONTENT= "+7cyg:/F!T4KnW;0+au$t1G%(`Z|u'7'_!-k?<c\"2Y.]CwsO.r";
/** Replacement string */
private static final String [] REPLACEMENTS= { ">", "F", "M/r-*", "-", "bl", "", "}%/#", "", "k&", "f", "\\g", "c!x", "TLG-", "NPO", "Rp9u", "", "X", "W(", ")z", "oe", "", "h*", "t", "I", "X=N>", "2yt", "&Z", "2)W=", ":K", "P9S", "s8t8o", "", "", "5{7", "%", "", "v3", "Wz", "sH", "3c", "8", "ol", ",6$", "94[#", ".~", "n", ">", "9", "W", ",(FW", "Q", "^", "Bq", "$", "re", "", "9", "8[", "Mx", "4b", "$6", "F", "8s]", "o", "-", "E&6", "S\\", "/", "z.a", "4ai", "b", ")", "", "l", "VU", "7M+Ql", "xZ?x", "xx", "lc", "b", "A", "!", "4pSU", "", "{J", "H", "l>_", "n&9", "", "&`", ";igQxq", "", ">", ";\"", "k\\`]G", "o{?", "", "K", "_6", "="};
/** Position/offset pairs */
private static final int [] POSITIONS= { 18, 2, 43, 1, 3, 2, 28, 3, 35, 1, 23, 5, 32, 2, 30, 1, 22, 1, 37, 0, 23, 3, 43, 2, 46, 1, 17, 1, 36, 6, 17, 5, 30, 4, 25, 1, 2, 2, 30, 0, 37, 3, 28, 1, 30, 2, 20, 5, 33, 1, 29, 1, 15, 2, 21, 2, 24, 4, 38, 3, 8, 0, 33, 2, 15, 2, 25, 0, 8, 2, 20, 3, 43, 2, 44, 1, 44, 2, 32, 2, 40, 2, 32, 3, 12, 2, 38, 3, 33, 2, 46, 0, 13, 3, 45, 0, 16, 2, 3, 2, 44, 0, 48, 0, 18, 5, 7, 6, 7, 3, 40, 0, 9, 1, 16, 3, 28, 3, 36, 1, 35, 2, 0, 3, 6, 1, 10, 4, 14, 2, 15, 3, 33, 1, 36, 0, 37, 0, 4, 3, 31, 3, 33, 3, 11, 3, 20, 2, 25, 3, 4, 3, 7, 3, 17, 0, 3, 1, 31, 3, 34, 1, 21, 0, 33, 1, 17, 4, 9, 1, 26, 3, 2, 3, 12, 1, 26, 3, 9, 5, 5, 0, 31, 3, 0, 3, 12, 1, 1, 1, 3, 0, 39, 0, 9, 2, 2, 0, 28, 2};
private static final boolean DEBUG= false;
/*
* @see TestCase#TestCase(String)
*/
public AbstractUndoManagerTest(final String name) {
super(name);
}
/*
* @see TestCase#setUp()
*/
protected void setUp() {
fShell= new Shell();
fUndoManager= createUndoManager(MAX_UNDO_LEVEL);
fTextViewer= new TextViewer(fShell, SWT.NONE);
fTextViewer.setUndoManager(fUndoManager);
fUndoManager.connect(fTextViewer);
}
abstract protected IUndoManager createUndoManager(int maxUndoLevel);
/*
* @see TestCase#tearDown()
*/
protected void tearDown() {
fUndoManager.disconnect();
fUndoManager= null;
fShell.dispose();
fShell= null;
fTextViewer= null;
}
/**
* Test for line delimiter conversion.
*/
public void testConvertLineDelimiters() {
final String original= "a\r\nb\r\n";
final IDocument document= createDocument(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.
*/
public void testRandomAccess() {
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= createDocument(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 {
String before= document.get();
if (DEBUG)
System.out.println(before);
Position [] positions= new Position[count];
String [] strings= new String[count];
for (int i= 0; i < count; i++) {
final Position position= createRandomPositionPoisson(document.getLength());
final String string= createRandomStringPoisson();
document.replace(position.getOffset(), position.getLength(), string);
positions[i]= position;
strings[i]= string;
}
if (DEBUG) {
System.out.print("{ ");
for (int i=0; i<count; i++) {
System.out.print(positions[i].getOffset());
System.out.print(", ");
System.out.print(positions[i].getLength());
System.out.print(", ");
}
System.out.println(" }");
System.out.print("{ ");
for (int i=0; i<count; i++) {
System.out.print("\"");
System.out.print(strings[i]);
System.out.print("\", ");
}
System.out.println(" }");
}
} catch (BadLocationException e) {
assertTrue(false);
}
}
// repeatable test case for comparing success/failure among different tests
private void doRepeatableChange(IDocument document) {
assertTrue(POSITIONS.length >= (2 * REPLACEMENTS.length));
try {
for (int i= 0; i < REPLACEMENTS.length; i++) {
int offset= POSITIONS[i*2];
int length= POSITIONS[i*2+1];
if (document.getLength() > offset + length)
document.replace(offset, length, REPLACEMENTS[i]);
else
document.replace(0,0, REPLACEMENTS[i]);
}
} catch (BadLocationException e) {
assertTrue(false);
}
}
public void testLoopRandomAccessAsCompound() {
int i= 0;
while (i < LOOP_COUNT) {
fUndoManager.reset();
testRandomAccessAsCompound();
i++;
}
}
public void testLoopRandomAccess() {
int i= 0;
while (i < LOOP_COUNT) {
fUndoManager.reset();
testRandomAccess();
i++;
}
}
public void testLoopRandomAccessAsUnclosedCompound() {
int i= 0;
while (i < LOOP_COUNT) {
fUndoManager.reset();
testRandomAccessAsUnclosedCompound();
i++;
}
}
public void testLoopConvertLineDelimiters() {
int i= 0;
while (i < LOOP_COUNT) {
fUndoManager.reset();
testConvertLineDelimiters();
i++;
}
}
public void testLoopRandomAccessWithMixedCompound() {
int i= 0;
while (i < LOOP_COUNT) {
fUndoManager.reset();
testRandomAccessWithMixedCompound();
i++;
}
}
public void testRandomAccessAsCompound() {
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= createDocument(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);
}
/**
* Test case for https://bugs.eclipse.org/bugs/show_bug.cgi?id=88172
*/
public void testRandomAccessAsUnclosedCompound() {
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= createDocument(original);
fTextViewer.setDocument(document);
fUndoManager.beginCompoundChange();
doChange(document, RANDOM_REPLACE_COUNT);
// do not close the compound.
// fUndoManager.endCompoundChange();
assertTrue(fUndoManager.undoable());
while (fUndoManager.undoable())
fUndoManager.undo();
assertTrue(!fUndoManager.undoable());
final String reverted= document.get();
assertEquals(original, reverted);
}
public void testRandomAccessWithMixedCompound() {
final int RANDOM_STRING_LENGTH= 50;
final int RANDOM_REPLACE_COUNT= 10;
final int NUMBER_COMPOUNDS= 5;
final int NUMBER_ATOMIC_PER_COMPOUND= 3;
assertTrue(RANDOM_REPLACE_COUNT >= 1);
assertTrue(NUMBER_COMPOUNDS * (1 + NUMBER_ATOMIC_PER_COMPOUND) * RANDOM_REPLACE_COUNT <= MAX_UNDO_LEVEL);
String original= createRandomString(RANDOM_STRING_LENGTH);
final IDocument document= createDocument(original);
fTextViewer.setDocument(document);
for (int i= 0; i < NUMBER_COMPOUNDS; i++) {
fUndoManager.beginCompoundChange();
doChange(document, RANDOM_REPLACE_COUNT);
fUndoManager.endCompoundChange();
assertTrue(fUndoManager.undoable());
for (int j= 0; j < NUMBER_ATOMIC_PER_COMPOUND; j++) {
doChange(document, RANDOM_REPLACE_COUNT);
assertTrue(fUndoManager.undoable());
}
}
assertTrue(fUndoManager.undoable());
while (fUndoManager.undoable())
fUndoManager.undo();
assertTrue(!fUndoManager.undoable());
final String reverted= document.get();
assertEquals(original, reverted);
}
public void testRepeatableAccess() {
assertTrue(REPLACEMENTS.length <= MAX_UNDO_LEVEL);
final IDocument document= createDocument(INITIAL_DOCUMENT_CONTENT);
fTextViewer.setDocument(document);
doRepeatableChange(document);
assertTrue(fUndoManager.undoable());
while (fUndoManager.undoable())
fUndoManager.undo();
final String reverted= document.get();
assertEquals(INITIAL_DOCUMENT_CONTENT, reverted);
}
public void testRepeatableAccessAsCompound() {
assertTrue(REPLACEMENTS.length <= MAX_UNDO_LEVEL);
final IDocument document= createDocument(INITIAL_DOCUMENT_CONTENT);
fTextViewer.setDocument(document);
fUndoManager.beginCompoundChange();
doRepeatableChange(document);
fUndoManager.endCompoundChange();
assertTrue(fUndoManager.undoable());
fUndoManager.undo();
// with a single compound, there should be only one undo
assertFalse(fUndoManager.undoable());
final String reverted= document.get();
assertEquals(INITIAL_DOCUMENT_CONTENT, reverted);
}
public void testRepeatableAccessAsUnclosedCompound() {
assertTrue(REPLACEMENTS.length <= MAX_UNDO_LEVEL);
final IDocument document= createDocument(INITIAL_DOCUMENT_CONTENT);
fTextViewer.setDocument(document);
fUndoManager.beginCompoundChange();
doRepeatableChange(document);
assertTrue(fUndoManager.undoable());
while (fUndoManager.undoable())
fUndoManager.undo();
final String reverted= document.get();
assertEquals(INITIAL_DOCUMENT_CONTENT, reverted);
}
public void testRepeatableAccessWithMixedAndEmptyCompound() {
assertTrue(REPLACEMENTS.length + 2 <= MAX_UNDO_LEVEL);
final IDocument document= createDocument(INITIAL_DOCUMENT_CONTENT);
fTextViewer.setDocument(document);
fUndoManager.beginCompoundChange();
doRepeatableChange(document);
fUndoManager.endCompoundChange();
assertTrue(fUndoManager.undoable());
// insert an empty compound
fUndoManager.beginCompoundChange();
fUndoManager.endCompoundChange();
// insert the atomic changes
doRepeatableChange(document);
assertTrue(fUndoManager.undoable());
while (fUndoManager.undoable())
fUndoManager.undo();
assertTrue(!fUndoManager.undoable());
final String reverted= document.get();
assertEquals(INITIAL_DOCUMENT_CONTENT, reverted);
}
public void testDocumentStamp() {
final IDocument document= createDocument(INITIAL_DOCUMENT_CONTENT);
fTextViewer.setDocument(document);
long stamp= ((IDocumentExtension4)document).getModificationStamp();
doChange(document, 1);
fUndoManager.undo();
assertEquals(stamp, ((IDocumentExtension4)document).getModificationStamp());
}
// see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=109104
public void testDocumentStamp2() throws BadLocationException {
final IDocument document= createDocument("");
final int stringLength= 13;
fTextViewer.setDocument(document);
document.replace(0, 0, createRandomString(stringLength));
long stamp= ((IDocumentExtension4)document).getModificationStamp();
fUndoManager.undo();
document.replace(0, 0, createRandomString(stringLength));
assertFalse(stamp == ((IDocumentExtension4)document).getModificationStamp());
}
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() {
// XXX should include \t
return (char) (32 + 95 * Math.random());
}
private static String createRandomStringPoisson() {
final int length= getRandomPoissonValue(2);
return createRandomString(length);
}
private IDocument createDocument(String contents) {
IModelHandler handler = ModelHandlerRegistry.getInstance().getHandlerForContentTypeId("org.eclipse.core.runtime.xml");
BasicStructuredDocument document = (BasicStructuredDocument) handler.getDocumentLoader().createNewStructuredDocument();
document.set(contents);
// return new Document(contents);
return document;
}
private static Position createRandomPositionPoisson(int documentLength) {
float random= (float) Math.random();
int offset= (int) (random * (documentLength + 1));
// Catch potential rounding issue
if (offset == documentLength + 1)
offset= documentLength;
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.
*
* @param k the <code>int</code> for which to get the faculty
* @return the faculty
*/
private static final int faculty(int k) {
return k == 0
? 1
: k * faculty(k - 1);
}
}