2 * (C) Copyright IBM Corp. 1998-2008. All Rights Reserved.
\r
4 * The program is provided "as is" without any warranty express or
\r
5 * implied, including the warranty of non-infringement and the implied
\r
6 * warranties of merchantibility and fitness for a particular purpose.
\r
7 * IBM will not be liable for any damages suffered by you as a result
\r
8 * of using the Program. In no event will IBM be liable for any
\r
9 * special, indirect or consequential damages or lost profits even if
\r
10 * IBM has been advised of the possibility of their occurrence. IBM
\r
11 * will not be liable for any third party claims against you.
\r
13 package com.ibm.richtext.test.unit;
\r
15 import com.ibm.icu.dev.test.TestFmwk;
\r
17 import com.ibm.richtext.textlayout.attributes.AttributeMap;
\r
18 import com.ibm.richtext.textlayout.attributes.TextAttribute;
\r
20 import com.ibm.richtext.styledtext.MConstText;
\r
21 import com.ibm.richtext.styledtext.MText;
\r
22 import com.ibm.richtext.styledtext.StyledText;
\r
23 import com.ibm.richtext.styledtext.StyleModifier;
\r
25 import java.text.CharacterIterator;
\r
26 import java.util.Random;
\r
30 public class TestMText extends TestFmwk {
\r
32 static final String COPYRIGHT =
\r
33 "(C) Copyright IBM Corp. 1998-1999 - All Rights Reserved";
\r
34 public static void main(String[] args) throws Exception {
\r
36 new TestMText().run(args);
\r
39 private static final int TEST_ITERATIONS = 5000;
\r
40 private static final int STYLE_TEST_ITERATIONS = 5000;
\r
41 private static final long RAND_SEED = 598436;
\r
43 private static final int NOT_IN_MONKEY_TEST = -5000;
\r
44 private int testIteration = NOT_IN_MONKEY_TEST;
\r
45 private int theCase = NOT_IN_MONKEY_TEST;
\r
47 private static StyleModifier createMinusModifier(final Object attr) {
\r
48 return new StyleModifier() {
\r
49 public AttributeMap modifyStyle(AttributeMap style) {
\r
50 return style.removeAttribute(attr);
\r
55 public void test() {
\r
62 public void simpleTest() {
\r
64 AttributeMap boldStyle = new AttributeMap(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
\r
65 AttributeMap italicStyle = new AttributeMap(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
\r
67 MConstText allBold = new StyledText("bbbbb", boldStyle);
\r
68 MConstText allItalic = new StyledText("iii", italicStyle);
\r
69 MConstText plain = new StyledText("pppppp", AttributeMap.EMPTY_ATTRIBUTE_MAP);
\r
72 MText buf = new StyledText();
\r
73 int ts = buf.getTimeStamp();
\r
74 buf.append(allBold);
\r
75 buf.append(allItalic);
\r
77 if (ts == buf.getTimeStamp()) {
\r
78 errln("Time stamp not incremented");
\r
81 // should be bbbbbiii now
\r
83 if (buf.length() != allBold.length() + allItalic.length()) {
\r
84 errln("Length is wrong.");
\r
87 for (int i=0; i < buf.length(); i++) {
\r
90 AttributeMap rightStyle;
\r
92 if (i < allBold.length()) {
\r
93 rightChar = allBold.at(0);
\r
94 rightStyle = boldStyle;
\r
97 rightChar = allItalic.at(0);
\r
98 rightStyle = italicStyle;
\r
101 if (buf.at(i) != rightChar) {
\r
102 errln("Character is wrong.");
\r
104 if (!buf.characterStyleAt(i).equals(rightStyle)) {
\r
105 errln("Style is wrong.");
\r
111 if (!buf.characterStyleAt(pos).equals(boldStyle)) {
\r
112 errln("First style is wrong.");
\r
114 if (buf.characterStyleLimit(pos) != allBold.length()) {
\r
115 errln("Run length is wrong.");
\r
118 pos = allBold.length();
\r
120 if (!buf.characterStyleAt(pos).equals(italicStyle)) {
\r
121 errln("Second style is wrong.");
\r
123 if (buf.characterStyleLimit(pos) != buf.length()) {
\r
124 errln("Run length is wrong.");
\r
128 buf.resetDamagedRange();
\r
129 int oldLength = buf.length();
\r
130 buf.replace(buf.length(), buf.length(), allBold, 0, allBold.length());
\r
133 if (buf.damagedRangeStart() != oldLength) {
\r
134 errln("Damaged range start is incorrect");
\r
136 if (buf.damagedRangeLimit() != buf.length()) {
\r
137 errln("Damaged range limit is incorrect");
\r
141 int start = allBold.length();
\r
142 int limit = start + allItalic.length();
\r
143 buf.remove(start, limit);
\r
146 if (buf.length() != 2 * allBold.length()) {
\r
147 errln("Text should be twice the length of bold text.");
\r
150 pos = buf.length() / 2;
\r
151 if (buf.characterStyleStart(pos) != 0 ||
\r
152 buf.characterStyleLimit(pos) != buf.length()) {
\r
153 errln("Run range is wrong.");
\r
155 if (!buf.characterStyleAt(pos).equals(boldStyle)) {
\r
156 errln("Run style is wrong.");
\r
159 ts = buf.getTimeStamp();
\r
160 CharacterIterator cIter = buf.createCharacterIterator();
\r
161 for (char ch = cIter.first(); ch != CharacterIterator.DONE; ch = cIter.next()) {
\r
162 if (ch != allBold.at(0)) {
\r
163 errln("Character is wrong.");
\r
167 if (ts != buf.getTimeStamp()) {
\r
168 errln("Time stamp should not have changed");
\r
171 buf.replace(0, 1, plain, 0, plain.length());
\r
173 if (ts == buf.getTimeStamp()) {
\r
174 errln("Time stamp not incremented");
\r
178 buf.replace(plain.length(), buf.length(), allItalic, 0, allItalic.length());
\r
181 if (buf.length() != allItalic.length()+plain.length()) {
\r
182 errln("Length is wrong.");
\r
186 if (buf.characterStyleLimit(pos) != plain.length()) {
\r
187 errln("Run limit is wrong.");
\r
190 pos = plain.length();
\r
191 if (buf.characterStyleLimit(pos) != buf.length()) {
\r
192 errln("Run limit is wrong.");
\r
195 buf.replace(plain.length(), plain.length(), allBold, 0, allBold.length());
\r
198 AttributeMap st = buf.characterStyleAt(1);
\r
199 if (!st.equals(AttributeMap.EMPTY_ATTRIBUTE_MAP)) {
\r
200 errln("Style is wrong.");
\r
202 if (buf.characterStyleStart(1) != 0 || buf.characterStyleLimit(1) != plain.length()) {
\r
203 errln("Style start is wrong.");
\r
206 st = buf.characterStyleAt(buf.length() - 1);
\r
207 if (!st.equals(italicStyle)) {
\r
208 errln("Style is wrong.");
\r
210 if (buf.characterStyleStart(buf.length() - 1) != plain.length()+allBold.length()) {
\r
211 errln("Style start is wrong.");
\r
214 if (buf.characterStyleLimit(buf.length() - 1) != buf.length()) {
\r
215 errln("Style limit is wrong.");
\r
220 private static int randInt(Random rand, int limit) {
\r
222 return randInt(rand, 0, limit);
\r
225 private static int randInt(Random rand, int start, int limit) {
\r
227 if (start > limit) {
\r
228 throw new IllegalArgumentException("Range length is negative.");
\r
230 else if (start == limit) {
\r
234 return start + (Math.abs(rand.nextInt())%(limit-start)) ;
\r
237 public void styleTest() {
\r
239 MText text = new StyledText("0123456789", AttributeMap.EMPTY_ATTRIBUTE_MAP);
\r
241 AttributeMap[] styles = new AttributeMap[text.length()];
\r
242 for (int i=0; i < styles.length; i++) {
\r
243 styles[i] = AttributeMap.EMPTY_ATTRIBUTE_MAP;
\r
245 AttributeMap[] oldStyles = new AttributeMap[styles.length];
\r
246 System.arraycopy(styles, 0, oldStyles, 0, styles.length);
\r
248 AttributeMap bigStyle = new AttributeMap(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON).
\r
249 addAttribute(TextAttribute.SIZE, new Float(23.0f));
\r
251 StyleModifier[] modifiers = {
\r
252 StyleModifier.createReplaceModifier(new AttributeMap(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD)),
\r
253 StyleModifier.createAddModifier(new AttributeMap(TextAttribute.WEIGHT, new Float(1.0f))),
\r
254 createMinusModifier(TextAttribute.WEIGHT),
\r
256 StyleModifier.createAddModifier(new AttributeMap(TextAttribute.POSTURE, new Float(0.0f))),
\r
257 StyleModifier.createReplaceModifier(new AttributeMap(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE)),
\r
258 createMinusModifier(TextAttribute.POSTURE),
\r
260 StyleModifier.createAddModifier(bigStyle),
\r
261 StyleModifier.createReplaceModifier(bigStyle),
\r
262 createMinusModifier(bigStyle.getKeySet())
\r
265 Random rand = new Random(RAND_SEED);
\r
266 final int stopAt = 4;
\r
268 for (int testItr=0; testItr < STYLE_TEST_ITERATIONS + 1; testItr++) {
\r
270 System.arraycopy(styles, 0, oldStyles, 0, styles.length);
\r
272 int startingAt = Integer.MAX_VALUE;
\r
273 int endingAt = Integer.MIN_VALUE;
\r
274 int oldTs = text.getTimeStamp();
\r
276 // hack way to do an invariant check before starting...
\r
277 if (testItr != 0) {
\r
279 text.resetDamagedRange();
\r
280 startingAt = randInt(rand, styles.length+1);
\r
281 endingAt = randInt(rand, startingAt, styles.length+1);
\r
282 StyleModifier modifier = modifiers[randInt(rand, modifiers.length)];
\r
284 if (testItr == stopAt) {
\r
287 text.modifyCharacterStyles(startingAt, endingAt, modifier);
\r
289 for (int j=startingAt; j < endingAt; j++) {
\r
290 styles[j] = modifier.modifyStyle(styles[j]);
\r
294 // check invariants
\r
295 AttributeMap oldStyle = null;
\r
296 int textLength = text.length();
\r
297 for (int runStart = 0; runStart < textLength;) {
\r
299 AttributeMap currentStyle = text.characterStyleAt(runStart);
\r
300 int runLimit = text.characterStyleLimit(runStart);
\r
301 if (runStart >= runLimit) {
\r
302 errln("Run length is not positive");
\r
304 if (currentStyle.equals(oldStyle)) {
\r
305 errln("Styles didn't merge");
\r
308 for (int pos=runStart; pos < runLimit; pos++) {
\r
309 AttributeMap charStyleAtPos = text.characterStyleAt(pos);
\r
310 if (currentStyle != charStyleAtPos) {
\r
311 errln("Iterator style is not equal to text style at " + pos + ".");
\r
313 AttributeMap expected = styles[pos];
\r
314 if (!currentStyle.equals(expected)) {
\r
315 errln("Iterator style doesn't match expected style at " + pos + ".");
\r
317 if (!(text.characterStyleStart(pos) == runStart) ||
\r
318 !(text.characterStyleLimit(pos) == runLimit)) {
\r
319 errln("style run start / limit is not consistent");
\r
322 runStart = runLimit;
\r
324 if (textLength > 0) {
\r
325 if (text.characterStyleAt(textLength) !=
\r
326 text.characterStyleAt(textLength-1)) {
\r
327 errln("Character styles at end aren't the same");
\r
331 // check damaged range:
\r
332 int damageStart = Integer.MAX_VALUE;
\r
333 int damageLimit = Integer.MIN_VALUE;
\r
334 for (int i=0; i < textLength; i++) {
\r
335 if (!styles[i].equals(oldStyles[i])) {
\r
336 damageStart = Math.min(i, damageStart);
\r
337 damageLimit = Math.max(i+1, damageLimit);
\r
340 if (damageStart != text.damagedRangeStart() ||
\r
341 damageLimit != text.damagedRangeLimit()) {
\r
342 logln("Test iteration: " + testItr);
\r
343 logln("startingAt: " + startingAt + "; endingAt: " + endingAt);
\r
344 logln("damageStart: " + damageStart + "; damageLimit: " + damageLimit);
\r
345 logln("text.rangeStart: " + text.damagedRangeStart() +
\r
346 "text.rangeLimit: " + text.damagedRangeLimit());
\r
347 errln("Damage range start or limit is not expected value");
\r
350 if ((damageLimit == Integer.MIN_VALUE) != (oldTs == text.getTimeStamp())) {
\r
352 errln("timeStamp is incorrect");
\r
357 public void msg(String message, int level, boolean incCount, boolean newln) {
\r
358 if (level == ERR && testIteration != NOT_IN_MONKEY_TEST) {
\r
359 message = "testIteration="+testIteration+"; testCase="+theCase+message;
\r
361 super.msg(message, level, incCount, newln);
\r
365 * Perform a random series of operations on an MText and
\r
366 * check the result of each operation against a set of invariants.
\r
368 public void monkeyTest(boolean streaming) {
\r
371 You can add any operation to the switch statement provided it
\r
372 preserves the following invariants:
\r
373 - The String plainText contains the same text as the StyledStringBuffer.
\r
374 Obviously, for the test to be meaningful plainText must be computed
\r
375 independently of the buffer (ie don't write: plainText = buf.getStyledString().toString()).
\r
376 - Every 'b' is bold, every 'i' is italic, every 'p' is plain, and
\r
377 no other characters appear in the text.
\r
380 AttributeMap boldAttrs = new AttributeMap(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD);
\r
381 AttributeMap italicAttrs = new AttributeMap(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE);
\r
382 AttributeMap emptyAttrs = AttributeMap.EMPTY_ATTRIBUTE_MAP;
\r
384 final String bold1Str_getString = "b";
\r
385 MConstText bold1Str = new StyledText(bold1Str_getString, boldAttrs);
\r
387 final String italic1Str_getString = "i";
\r
388 MConstText italic1Str = new StyledText(italic1Str_getString, italicAttrs);
\r
390 final String plain1Str_getString = "p";
\r
391 MConstText plain1Str = new StyledText(plain1Str_getString, emptyAttrs);
\r
393 StyledText temp = new StyledText();
\r
394 temp.append(bold1Str);
\r
395 temp.append(italic1Str);
\r
396 final String boldItalicStr_getString = bold1Str_getString.concat(italic1Str_getString);
\r
397 MConstText boldItalicStr = temp;
\r
399 temp = new StyledText();
\r
400 temp.append(bold1Str);
\r
401 temp.append(bold1Str);
\r
402 temp.append(bold1Str);
\r
403 final String bold3Str_getString = "bbb";
\r
404 MConstText bold3Str = temp;
\r
406 MText buf = new StyledText();
\r
407 String plainText = new String();
\r
408 //int testIteration=0; - now instance variables so errln can report it
\r
411 final int NUM_CASES = 14;
\r
412 boolean[] casesExecuted = new boolean[NUM_CASES];
\r
413 final int stopAt = -1;
\r
414 Random rand = new Random(RAND_SEED);
\r
416 final String ALWAYS_DIFFERENT = "\uFEFF";
\r
418 for (testIteration=0; testIteration < TEST_ITERATIONS; testIteration++) {
\r
420 theCase = randInt(rand, NUM_CASES);
\r
422 casesExecuted[theCase] = true;
\r
424 if (testIteration == stopAt) {
\r
425 testIteration = stopAt; // Convenient place to put breakpoint
\r
428 int timeStamp = buf.getTimeStamp();
\r
429 String oldPlainText = plainText;
\r
430 if (oldPlainText == null) {
\r
431 errln("oldPlainText is null!");
\r
437 // create new string; replace chars at start with different style
\r
438 buf = new StyledText();
\r
439 buf.append(bold3Str);
\r
440 buf.replace(0, 1, italic1Str, 0, italic1Str.length());
\r
441 buf.replace(0, 0, italic1Str, 0, italic1Str.length());
\r
443 plainText = bold3Str_getString.substring(1, bold3Str.length());
\r
444 plainText = italic1Str_getString.concat(plainText);
\r
445 plainText = italic1Str_getString.concat(plainText);
\r
446 oldPlainText = null;
\r
450 // delete the last character from the string
\r
451 if (buf.length() == 0) {
\r
452 buf.replace(0, 0, italic1Str, 0, italic1Str.length());
\r
453 plainText = italic1Str_getString;
\r
454 oldPlainText = ALWAYS_DIFFERENT;
\r
456 buf.remove(buf.length()-1, buf.length());
\r
457 plainText = plainText.substring(0, plainText.length()-1);
\r
461 // replace some of the buffer with boldItalicStr
\r
462 int rStart = randInt(rand, buf.length()+1);
\r
463 int rStop = randInt(rand, rStart, buf.length()+1);
\r
464 buf.replace(rStart, rStop, boldItalicStr);
\r
466 String newString = (rStart>0)? plainText.substring(0, rStart) : new String();
\r
467 newString = newString.concat(boldItalicStr_getString);
\r
468 if (rStop < plainText.length())
\r
469 newString = newString.concat(plainText.substring(rStop, plainText.length()));
\r
470 oldPlainText = ALWAYS_DIFFERENT;
\r
471 plainText = newString;
\r
476 // repeatedly insert strings into the center of the buffer
\r
478 int insPos = buf.length() / 2;
\r
479 String prefix = plainText.substring(0, insPos);
\r
480 String suffix = plainText.substring(insPos, plainText.length());
\r
481 String middle = new String();
\r
482 for (int ii=0; ii<4; ii++) {
\r
483 MConstText which = (ii%2==0)? boldItalicStr : bold3Str;
\r
484 String whichString = (ii%2==0)? boldItalicStr_getString : bold3Str_getString;
\r
485 int tempPos = insPos+middle.length();
\r
486 buf.insert(tempPos, which);
\r
487 middle = middle.concat(whichString);
\r
489 plainText = prefix.concat(middle).concat(suffix);
\r
490 oldPlainText = ALWAYS_DIFFERENT;
\r
495 // insert bold1Str at end
\r
496 buf.append(bold1Str);
\r
497 plainText = plainText.concat(bold1Str_getString);
\r
501 // delete a character from the string
\r
502 if (buf.length() > 0) {
\r
503 int delPos = randInt(rand, buf.length()-1);
\r
504 buf.remove(delPos, delPos+1);
\r
505 plainText = plainText.substring(0, delPos).concat(plainText.substring(delPos+1));
\r
508 buf.replace(0, 0, plain1Str, 0, plain1Str.length());
\r
509 plainText = plain1Str_getString;
\r
514 // replace the contents of the buffer (except the first character) with itself
\r
516 int start = buf.length() > 1? 1 : 0;
\r
517 buf.replace(start, buf.length(), buf);
\r
518 plainText = plainText.substring(0, start).concat(plainText);
\r
519 if (buf.length() > 0) {
\r
520 oldPlainText = ALWAYS_DIFFERENT;
\r
526 // append the contents of the buffer to itself
\r
528 MConstText content = buf;
\r
529 buf.insert(buf.length(), content);
\r
530 plainText = plainText.concat(plainText);
\r
535 // replace the buffer with boldItalicStr+bold3Str
\r
537 MText replacement = new StyledText();
\r
538 replacement.append(boldItalicStr);
\r
539 replacement.append(bold3Str);
\r
540 buf.replace(0, buf.length(), replacement, 0, replacement.length());
\r
541 plainText = boldItalicStr_getString.concat(bold3Str_getString);
\r
542 oldPlainText = ALWAYS_DIFFERENT;
\r
547 // insert bold1Str at end - same as 4 but uses different API
\r
548 buf.replace(buf.length(),
\r
550 bold1Str_getString.toCharArray(),
\r
552 bold1Str_getString.length(),
\r
554 plainText = plainText.concat(bold1Str_getString);
\r
561 oldPlainText = ALWAYS_DIFFERENT;
\r
565 // remove all - different way
\r
566 buf.remove(0, buf.length());
\r
571 // insert 'i' at 3rd character (or last, if fewer than 3 chars)
\r
573 int insPos = Math.min(buf.length(), 3);
\r
574 buf.replace(insPos, insPos, 'i', italicAttrs);
\r
575 plainText = (plainText.substring(0, insPos)).
\r
576 concat(italic1Str_getString).
\r
577 concat(plainText.substring(insPos));
\r
583 Throwable error = null;
\r
585 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
\r
586 ObjectOutputStream objOut = new ObjectOutputStream(bytesOut);
\r
587 objOut.writeObject(buf);
\r
589 ByteArrayInputStream bytesIn =
\r
590 new ByteArrayInputStream(bytesOut.toByteArray());
\r
591 ObjectInputStream objIn = new ObjectInputStream(bytesIn);
\r
592 buf = (MText) objIn.readObject();
\r
593 oldPlainText = null;
\r
595 catch(IOException e) {
\r
598 catch(ClassNotFoundException e) {
\r
601 if (error != null) {
\r
602 error.printStackTrace();
\r
603 errln("Streaming problem: " + error);
\r
609 errln("Invalid case.");
\r
612 // Check time stamp if oldPlainText != null.
\r
613 // Time stamp should be different iff
\r
614 // oldPlainText == plainText
\r
615 if (oldPlainText != null) {
\r
616 if ((timeStamp==buf.getTimeStamp()) !=
\r
617 oldPlainText.equals(plainText)) {
\r
618 logln("plainText hashCode: " + plainText.hashCode());
\r
619 logln("oldPlainText hashCode: " + oldPlainText.hashCode());
\r
620 errln("Time stamp is incorrect");
\r
624 // now check invariants:
\r
625 if (plainText.length() != buf.length()) {
\r
626 errln("Lengths don't match");
\r
629 for (int j=0; j < buf.length(); j++) {
\r
630 if (buf.at(j) != plainText.charAt(j)) {
\r
631 errln("Characters don't match.");
\r
636 for (start = 0; start < buf.length();) {
\r
638 if (start != buf.characterStyleStart(start)) {
\r
639 errln("style start is wrong");
\r
641 int limit = buf.characterStyleLimit(start);
\r
642 if (start >= limit) {
\r
643 errln("start >= limit");
\r
645 char current = plainText.charAt(start);
\r
647 AttributeMap comp = null;
\r
648 if (current == 'p') {
\r
651 else if (current == 'b') {
\r
654 else if (current == 'i') {
\r
655 comp = italicAttrs;
\r
658 errln("An invalid character snuck in!");
\r
661 AttributeMap startStyle = buf.characterStyleAt(start);
\r
662 if (!comp.equals(startStyle)) {
\r
663 errln("Style is not expected style.");
\r
666 for (int j = start; j < limit; j++) {
\r
667 if (plainText.charAt(j) != current) {
\r
668 errln("Character doesn't match style.");
\r
670 if (buf.characterStyleAt(j) != startStyle) {
\r
671 errln("Incorrect style in run");
\r
675 if (limit < buf.length()) {
\r
676 if (plainText.charAt(limit) == current) {
\r
677 errln("Style run ends too soon.");
\r
682 if (start != buf.length()) {
\r
683 errln("Last limit is not buffer length.");
\r
686 // won't try to compute and check damaged range; however,
\r
687 // if nonempty it should always be within text
\r
688 int damageStart = buf.damagedRangeStart();
\r
689 int damageLimit = buf.damagedRangeLimit();
\r
690 if (damageStart == Integer.MAX_VALUE) {
\r
691 if (damageLimit != Integer.MIN_VALUE) {
\r
692 errln("Invalid empty interval");
\r
696 if (damageStart > damageLimit) {
\r
697 errln("Damage range inverted");
\r
699 if (damageStart < 0 || damageLimit > buf.length()) {
\r
700 errln("Damage range endpoint out of bounds");
\r
705 testIteration = NOT_IN_MONKEY_TEST;
\r
707 boolean allCasesExecuted = true;
\r
708 for (int index=0; index < NUM_CASES; index++) {
\r
709 allCasesExecuted &= casesExecuted[index];
\r
710 if (casesExecuted[index] == false) {
\r
711 logln("Case " + index + " not executed.");
\r
714 //if (allCasesExecuted) {
\r
715 // logln("All cases executed.");
\r