blob: 2fa5d3ee2ce9c73efda09b1a9a7602564ed3fb08 [file] [log] [blame]
/*
* (C) Copyright IBM Corp. 1998-2004. All Rights Reserved.
*
* The program is provided "as is" without any warranty express or
* implied, including the warranty of non-infringement and the implied
* warranties of merchantibility and fitness for a particular purpose.
* IBM will not be liable for any damages suffered by you as a result
* of using the Program. In no event will IBM be liable for any
* special, indirect or consequential damages or lost profits even if
* IBM has been advised of the possibility of their occurrence. IBM
* will not be liable for any third party claims against you.
*/
package com.ibm.richtext.test.unit;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.richtext.textlayout.attributes.AttributeMap;
import com.ibm.richtext.textlayout.attributes.TextAttribute;
import com.ibm.richtext.styledtext.MConstText;
import com.ibm.richtext.styledtext.MText;
import com.ibm.richtext.styledtext.StyledText;
import com.ibm.richtext.styledtext.StyleModifier;
import java.text.CharacterIterator;
import java.util.Random;
import java.io.*;
public class TestMText extends TestFmwk {
static final String COPYRIGHT =
"(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
public static void main(String[] args) throws Exception {
new TestMText().run(args);
}
private static final int TEST_ITERATIONS = 5000;
private static final int STYLE_TEST_ITERATIONS = 5000;
private static final long RAND_SEED = 598436;
private static final int NOT_IN_MONKEY_TEST = -5000;
private int testIteration = NOT_IN_MONKEY_TEST;
private int theCase = NOT_IN_MONKEY_TEST;
private static StyleModifier createMinusModifier(final Object attr) {
return new StyleModifier() {
public AttributeMap modifyStyle(AttributeMap style) {
return style.removeAttribute(attr);
}
};
}
public void test() {
simpleTest();
styleTest();
monkeyTest(true);
}
public void simpleTest() {
AttributeMap boldStyle = new AttributeMap(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
AttributeMap italicStyle = new AttributeMap(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
MConstText allBold = new StyledText("bbbbb", boldStyle);
MConstText allItalic = new StyledText("iii", italicStyle);
MConstText plain = new StyledText("pppppp", AttributeMap.EMPTY_ATTRIBUTE_MAP);
{
MText buf = new StyledText();
int ts = buf.getTimeStamp();
buf.append(allBold);
buf.append(allItalic);
if (ts == buf.getTimeStamp()) {
errln("Time stamp not incremented");
}
// should be bbbbbiii now
if (buf.length() != allBold.length() + allItalic.length()) {
errln("Length is wrong.");
}
for (int i=0; i < buf.length(); i++) {
char rightChar;
AttributeMap rightStyle;
if (i < allBold.length()) {
rightChar = allBold.at(0);
rightStyle = boldStyle;
}
else {
rightChar = allItalic.at(0);
rightStyle = italicStyle;
}
if (buf.at(i) != rightChar) {
errln("Character is wrong.");
}
if (!buf.characterStyleAt(i).equals(rightStyle)) {
errln("Style is wrong.");
}
}
int pos = 0;
if (!buf.characterStyleAt(pos).equals(boldStyle)) {
errln("First style is wrong.");
}
if (buf.characterStyleLimit(pos) != allBold.length()) {
errln("Run length is wrong.");
}
pos = allBold.length();
if (!buf.characterStyleAt(pos).equals(italicStyle)) {
errln("Second style is wrong.");
}
if (buf.characterStyleLimit(pos) != buf.length()) {
errln("Run length is wrong.");
}
{
buf.resetDamagedRange();
int oldLength = buf.length();
buf.replace(buf.length(), buf.length(), allBold, 0, allBold.length());
// bbbbbiiibbbbb
if (buf.damagedRangeStart() != oldLength) {
errln("Damaged range start is incorrect");
}
if (buf.damagedRangeLimit() != buf.length()) {
errln("Damaged range limit is incorrect");
}
}
int start = allBold.length();
int limit = start + allItalic.length();
buf.remove(start, limit);
// bbbbbbbbbb
if (buf.length() != 2 * allBold.length()) {
errln("Text should be twice the length of bold text.");
}
pos = buf.length() / 2;
if (buf.characterStyleStart(pos) != 0 ||
buf.characterStyleLimit(pos) != buf.length()) {
errln("Run range is wrong.");
}
if (!buf.characterStyleAt(pos).equals(boldStyle)) {
errln("Run style is wrong.");
}
ts = buf.getTimeStamp();
CharacterIterator cIter = buf.createCharacterIterator();
for (char ch = cIter.first(); ch != CharacterIterator.DONE; ch = cIter.next()) {
if (ch != allBold.at(0)) {
errln("Character is wrong.");
}
}
if (ts != buf.getTimeStamp()) {
errln("Time stamp should not have changed");
}
buf.replace(0, 1, plain, 0, plain.length());
if (ts == buf.getTimeStamp()) {
errln("Time stamp not incremented");
}
// ppppppbbbbbbbbb
buf.replace(plain.length(), buf.length(), allItalic, 0, allItalic.length());
// ppppppiii
if (buf.length() != allItalic.length()+plain.length()) {
errln("Length is wrong.");
}
pos = 0;
if (buf.characterStyleLimit(pos) != plain.length()) {
errln("Run limit is wrong.");
}
pos = plain.length();
if (buf.characterStyleLimit(pos) != buf.length()) {
errln("Run limit is wrong.");
}
buf.replace(plain.length(), plain.length(), allBold, 0, allBold.length());
// ppppppbbbbbiii
AttributeMap st = buf.characterStyleAt(1);
if (!st.equals(AttributeMap.EMPTY_ATTRIBUTE_MAP)) {
errln("Style is wrong.");
}
if (buf.characterStyleStart(1) != 0 || buf.characterStyleLimit(1) != plain.length()) {
errln("Style start is wrong.");
}
st = buf.characterStyleAt(buf.length() - 1);
if (!st.equals(italicStyle)) {
errln("Style is wrong.");
}
if (buf.characterStyleStart(buf.length() - 1) != plain.length()+allBold.length()) {
errln("Style start is wrong.");
}
if (buf.characterStyleLimit(buf.length() - 1) != buf.length()) {
errln("Style limit is wrong.");
}
}
}
private static int randInt(Random rand, int limit) {
return randInt(rand, 0, limit);
}
private static int randInt(Random rand, int start, int limit) {
if (start > limit) {
throw new IllegalArgumentException("Range length is negative.");
}
else if (start == limit) {
return start;
}
return start + (Math.abs(rand.nextInt())%(limit-start)) ;
}
public void styleTest() {
MText text = new StyledText("0123456789", AttributeMap.EMPTY_ATTRIBUTE_MAP);
AttributeMap[] styles = new AttributeMap[text.length()];
for (int i=0; i < styles.length; i++) {
styles[i] = AttributeMap.EMPTY_ATTRIBUTE_MAP;
}
AttributeMap[] oldStyles = new AttributeMap[styles.length];
System.arraycopy(styles, 0, oldStyles, 0, styles.length);
AttributeMap bigStyle = new AttributeMap(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON).
addAttribute(TextAttribute.SIZE, new Float(23.0f));
StyleModifier[] modifiers = {
StyleModifier.createReplaceModifier(new AttributeMap(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD)),
StyleModifier.createAddModifier(new AttributeMap(TextAttribute.WEIGHT, new Float(1.0f))),
createMinusModifier(TextAttribute.WEIGHT),
StyleModifier.createAddModifier(new AttributeMap(TextAttribute.POSTURE, new Float(0.0f))),
StyleModifier.createReplaceModifier(new AttributeMap(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE)),
createMinusModifier(TextAttribute.POSTURE),
StyleModifier.createAddModifier(bigStyle),
StyleModifier.createReplaceModifier(bigStyle),
createMinusModifier(bigStyle.getKeySet())
};
Random rand = new Random(RAND_SEED);
final int stopAt = 4;
for (int testIteration=0; testIteration < STYLE_TEST_ITERATIONS + 1; testIteration++) {
System.arraycopy(styles, 0, oldStyles, 0, styles.length);
int startingAt = Integer.MAX_VALUE;
int endingAt = Integer.MIN_VALUE;
int oldTs = text.getTimeStamp();
// hack way to do an invariant check before starting...
if (testIteration != 0) {
// modify styles
text.resetDamagedRange();
startingAt = randInt(rand, styles.length+1);
endingAt = randInt(rand, startingAt, styles.length+1);
StyleModifier modifier = modifiers[randInt(rand, modifiers.length)];
if (testIteration == stopAt) {
testIteration = stopAt;
}
text.modifyCharacterStyles(startingAt, endingAt, modifier);
for (int j=startingAt; j < endingAt; j++) {
styles[j] = modifier.modifyStyle(styles[j]);
}
}
// check invariants
AttributeMap oldStyle = null;
int textLength = text.length();
for (int runStart = 0; runStart < textLength;) {
AttributeMap currentStyle = text.characterStyleAt(runStart);
int runLimit = text.characterStyleLimit(runStart);
if (runStart >= runLimit) {
errln("Run length is not positive");
}
if (currentStyle.equals(oldStyle)) {
errln("Styles didn't merge");
}
for (int pos=runStart; pos < runLimit; pos++) {
AttributeMap charStyleAtPos = text.characterStyleAt(pos);
if (currentStyle != charStyleAtPos) {
errln("Iterator style is not equal to text style at " + pos + ".");
}
AttributeMap expected = styles[pos];
if (!currentStyle.equals(expected)) {
errln("Iterator style doesn't match expected style at " + pos + ".");
}
if (!(text.characterStyleStart(pos) == runStart) ||
!(text.characterStyleLimit(pos) == runLimit)) {
errln("style run start / limit is not consistent");
}
}
runStart = runLimit;
}
if (textLength > 0) {
if (text.characterStyleAt(textLength) !=
text.characterStyleAt(textLength-1)) {
errln("Character styles at end aren't the same");
}
}
// check damaged range:
int damageStart = Integer.MAX_VALUE;
int damageLimit = Integer.MIN_VALUE;
for (int i=0; i < textLength; i++) {
if (!styles[i].equals(oldStyles[i])) {
damageStart = Math.min(i, damageStart);
damageLimit = Math.max(i+1, damageLimit);
}
}
if (damageStart != text.damagedRangeStart() ||
damageLimit != text.damagedRangeLimit()) {
logln("Test iteration: " + testIteration);
logln("startingAt: " + startingAt + "; endingAt: " + endingAt);
logln("damageStart: " + damageStart + "; damageLimit: " + damageLimit);
logln("text.rangeStart: " + text.damagedRangeStart() +
"text.rangeLimit: " + text.damagedRangeLimit());
errln("Damage range start or limit is not expected value");
}
if ((damageLimit == Integer.MIN_VALUE) != (oldTs == text.getTimeStamp())) {
errln("timeStamp is incorrect");
}
}
}
public void msg(String message, int level, boolean incCount, boolean newln) {
if (level == ERR && testIteration != NOT_IN_MONKEY_TEST) {
message = "testIteration="+testIteration+"; testCase="+theCase+message;
}
super.msg(message, level, incCount, newln);
}
/**
* Perform a random series of operations on an MText and
* check the result of each operation against a set of invariants.
*/
public void monkeyTest(boolean streaming) {
/*
You can add any operation to the switch statement provided it
preserves the following invariants:
- The String plainText contains the same text as the StyledStringBuffer.
Obviously, for the test to be meaningful plainText must be computed
independently of the buffer (ie don't write: plainText = buf.getStyledString().toString()).
- Every 'b' is bold, every 'i' is italic, every 'p' is plain, and
no other characters appear in the text.
*/
AttributeMap boldAttrs = new AttributeMap(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
AttributeMap italicAttrs = new AttributeMap(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
AttributeMap emptyAttrs = AttributeMap.EMPTY_ATTRIBUTE_MAP;
final String bold1Str_getString = "b";
MConstText bold1Str = new StyledText(bold1Str_getString, boldAttrs);
final String italic1Str_getString = "i";
MConstText italic1Str = new StyledText(italic1Str_getString, italicAttrs);
final String plain1Str_getString = "p";
MConstText plain1Str = new StyledText(plain1Str_getString, emptyAttrs);
StyledText temp = new StyledText();
temp.append(bold1Str);
temp.append(italic1Str);
final String boldItalicStr_getString = bold1Str_getString.concat(italic1Str_getString);
MConstText boldItalicStr = temp;
temp = new StyledText();
temp.append(bold1Str);
temp.append(bold1Str);
temp.append(bold1Str);
final String bold3Str_getString = "bbb";
MConstText bold3Str = temp;
MText buf = new StyledText();
String plainText = new String();
//int testIteration=0; - now instance variables so errln can report it
//int theCase=0;
final int NUM_CASES = 14;
boolean[] casesExecuted = new boolean[NUM_CASES];
final int stopAt = -1;
Random rand = new Random(RAND_SEED);
final String ALWAYS_DIFFERENT = "\uFEFF";
for (testIteration=0; testIteration < TEST_ITERATIONS; testIteration++) {
theCase = randInt(rand, NUM_CASES);
casesExecuted[theCase] = true;
if (testIteration == stopAt) {
testIteration = stopAt; // Convenient place to put breakpoint
}
int timeStamp = buf.getTimeStamp();
String oldPlainText = plainText;
if (oldPlainText == null) {
errln("oldPlainText is null!");
}
switch (theCase) {
case 0:
// create new string; replace chars at start with different style
buf = new StyledText();
buf.append(bold3Str);
buf.replace(0, 1, italic1Str, 0, italic1Str.length());
buf.replace(0, 0, italic1Str, 0, italic1Str.length());
plainText = bold3Str_getString.substring(1, bold3Str.length());
plainText = italic1Str_getString.concat(plainText);
plainText = italic1Str_getString.concat(plainText);
oldPlainText = null;
break;
case 1:
// delete the last character from the string
if (buf.length() == 0) {
buf.replace(0, 0, italic1Str, 0, italic1Str.length());
plainText = italic1Str_getString;
oldPlainText = ALWAYS_DIFFERENT;
}
buf.remove(buf.length()-1, buf.length());
plainText = plainText.substring(0, plainText.length()-1);
break;
case 2:
// replace some of the buffer with boldItalicStr
int rStart = randInt(rand, buf.length()+1);
int rStop = randInt(rand, rStart, buf.length()+1);
buf.replace(rStart, rStop, boldItalicStr);
{
String newString = (rStart>0)? plainText.substring(0, rStart) : new String();
newString = newString.concat(boldItalicStr_getString);
if (rStop < plainText.length())
newString = newString.concat(plainText.substring(rStop, plainText.length()));
oldPlainText = ALWAYS_DIFFERENT;
plainText = newString;
}
break;
case 3:
// repeatedly insert strings into the center of the buffer
{
int insPos = buf.length() / 2;
String prefix = plainText.substring(0, insPos);
String suffix = plainText.substring(insPos, plainText.length());
String middle = new String();
for (int ii=0; ii<4; ii++) {
MConstText which = (ii%2==0)? boldItalicStr : bold3Str;
String whichString = (ii%2==0)? boldItalicStr_getString : bold3Str_getString;
int tempPos = insPos+middle.length();
buf.insert(tempPos, which);
middle = middle.concat(whichString);
}
plainText = prefix.concat(middle).concat(suffix);
oldPlainText = ALWAYS_DIFFERENT;
}
break;
case 4:
// insert bold1Str at end
buf.append(bold1Str);
plainText = plainText.concat(bold1Str_getString);
break;
case 5:
// delete a character from the string
if (buf.length() > 0) {
int delPos = randInt(rand, buf.length()-1);
buf.remove(delPos, delPos+1);
plainText = plainText.substring(0, delPos).concat(plainText.substring(delPos+1));
}
else {
buf.replace(0, 0, plain1Str, 0, plain1Str.length());
plainText = plain1Str_getString;
}
break;
case 6:
// replace the contents of the buffer (except the first character) with itself
{
int start = buf.length() > 1? 1 : 0;
buf.replace(start, buf.length(), buf);
plainText = plainText.substring(0, start).concat(plainText);
if (buf.length() > 0) {
oldPlainText = ALWAYS_DIFFERENT;
}
}
break;
case 7:
// append the contents of the buffer to itself
{
MConstText content = buf;
buf.insert(buf.length(), content);
plainText = plainText.concat(plainText);
}
break;
case 8:
// replace the buffer with boldItalicStr+bold3Str
{
MText replacement = new StyledText();
replacement.append(boldItalicStr);
replacement.append(bold3Str);
buf.replace(0, buf.length(), replacement, 0, replacement.length());
plainText = boldItalicStr_getString.concat(bold3Str_getString);
oldPlainText = ALWAYS_DIFFERENT;
}
break;
case 9:
// insert bold1Str at end - same as 4 but uses different API
buf.replace(buf.length(),
buf.length(),
bold1Str_getString.toCharArray(),
0,
bold1Str_getString.length(),
boldAttrs);
plainText = plainText.concat(bold1Str_getString);
break;
case 10:
// remove all
buf.remove();
plainText = "";
oldPlainText = ALWAYS_DIFFERENT;
break;
case 11:
// remove all - different way
buf.remove(0, buf.length());
plainText = "";
break;
case 12:
// insert 'i' at 3rd character (or last, if fewer than 3 chars)
{
int insPos = Math.min(buf.length(), 3);
buf.replace(insPos, insPos, 'i', italicAttrs);
plainText = (plainText.substring(0, insPos)).
concat(italic1Str_getString).
concat(plainText.substring(insPos));
}
break;
case 13:
if (streaming) {
Throwable error = null;
try {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(bytesOut);
objOut.writeObject(buf);
ByteArrayInputStream bytesIn =
new ByteArrayInputStream(bytesOut.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(bytesIn);
buf = (MText) objIn.readObject();
oldPlainText = null;
}
catch(IOException e) {
error = e;
}
catch(ClassNotFoundException e) {
error = e;
}
if (error != null) {
error.printStackTrace();
errln("Streaming problem: " + error);
}
}
break;
default:
errln("Invalid case.");
}
// Check time stamp if oldPlainText != null.
// Time stamp should be different iff
// oldPlainText == plainText
if (oldPlainText != null) {
if ((timeStamp==buf.getTimeStamp()) !=
oldPlainText.equals(plainText)) {
logln("plainText hashCode: " + plainText.hashCode());
logln("oldPlainText hashCode: " + oldPlainText.hashCode());
errln("Time stamp is incorrect");
}
}
// now check invariants:
if (plainText.length() != buf.length()) {
errln("Lengths don't match");
}
for (int j=0; j < buf.length(); j++) {
if (buf.at(j) != plainText.charAt(j)) {
errln("Characters don't match.");
}
}
int start;
for (start = 0; start < buf.length();) {
if (start != buf.characterStyleStart(start)) {
errln("style start is wrong");
}
int limit = buf.characterStyleLimit(start);
if (start >= limit) {
errln("start >= limit");
}
char current = plainText.charAt(start);
AttributeMap comp = null;
if (current == 'p') {
comp = emptyAttrs;
}
else if (current == 'b') {
comp = boldAttrs;
}
else if (current == 'i') {
comp = italicAttrs;
}
else {
errln("An invalid character snuck in!");
}
AttributeMap startStyle = buf.characterStyleAt(start);
if (!comp.equals(startStyle)) {
errln("Style is not expected style.");
}
for (int j = start; j < limit; j++) {
if (plainText.charAt(j) != current) {
errln("Character doesn't match style.");
}
if (buf.characterStyleAt(j) != startStyle) {
errln("Incorrect style in run");
}
}
if (limit < buf.length()) {
if (plainText.charAt(limit) == current) {
errln("Style run ends too soon.");
}
}
start = limit;
}
if (start != buf.length()) {
errln("Last limit is not buffer length.");
}
// won't try to compute and check damaged range; however,
// if nonempty it should always be within text
int damageStart = buf.damagedRangeStart();
int damageLimit = buf.damagedRangeLimit();
if (damageStart == Integer.MAX_VALUE) {
if (damageLimit != Integer.MIN_VALUE) {
errln("Invalid empty interval");
}
}
else {
if (damageStart > damageLimit) {
errln("Damage range inverted");
}
if (damageStart < 0 || damageLimit > buf.length()) {
errln("Damage range endpoint out of bounds");
}
}
}
testIteration = NOT_IN_MONKEY_TEST;
boolean allCasesExecuted = true;
for (int index=0; index < NUM_CASES; index++) {
allCasesExecuted &= casesExecuted[index];
if (casesExecuted[index] == false) {
logln("Case " + index + " not executed.");
}
}
//if (allCasesExecuted) {
// logln("All cases executed.");
//}
}
}