001: /*
002: * TestMultithreadTokenizerProperties.java: JUnit test for TokenizerProperties implementations
003: *
004: * Copyright (C) 2002 Heiko Blau
005: *
006: * This file belongs to the JTopas test suite.
007: * The JTopas test suite is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as published by the
009: * Free Software Foundation; either version 2.1 of the License, or (at your option)
010: * any later version.
011: *
012: * This software is distributed in the hope that it will be useful, but WITHOUT
013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014: * FITNESS FOR A PARTICULAR PURPOSE.
015: * See the GNU Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public License along
018: * with the JTopas test suite. If not, write to the
019: *
020: * Free Software Foundation, Inc.
021: * 59 Temple Place, Suite 330,
022: * Boston, MA 02111-1307
023: * USA
024: *
025: * or check the Internet: http://www.fsf.org
026: *
027: * The JTopas test suite uses the test framework JUnit by Kent Beck and Erich Gamma.
028: * You should have received a copy of their JUnit licence agreement along with
029: * the JTopas test suite.
030: *
031: * We do NOT provide the JUnit archive junit.jar nessecary to compile and run
032: * our tests, since we assume, that You either have it already or would like
033: * to get the current release Yourself.
034: * Please visit either:
035: * http://sourceforge.net/projects/junit
036: * or
037: * http://junit.org
038: * to obtain JUnit.
039: *
040: * Contact:
041: * email: heiko@susebox.de
042: */
043:
044: package de.susebox.jtopas;
045:
046: //-----------------------------------------------------------------------------
047: // Imports
048: //
049: import java.util.Iterator;
050: import java.util.Random;
051: import java.util.Map;
052: import java.util.HashMap;
053: import java.util.Collections;
054:
055: import junit.framework.Test;
056: import junit.framework.TestCase;
057: import junit.framework.TestSuite;
058: import junit.framework.Assert;
059:
060: import de.susebox.java.lang.ExtRuntimeException;
061: import de.susebox.jtopas.spi.DataProvider;
062: import de.susebox.jtopas.spi.KeywordHandler;
063: import de.susebox.jtopas.spi.SequenceHandler;
064: import de.susebox.jtopas.spi.PatternHandler;
065: import de.susebox.jtopas.spi.WhitespaceHandler;
066: import de.susebox.jtopas.spi.SeparatorHandler;
067:
068: import de.susebox.TestUtilities;
069:
070: //-----------------------------------------------------------------------------
071: // Class TestMultithreadTokenizerProperties
072: //
073:
074: /**<p>
075: * This class tests the implementations of {@link TokenizerProperties}.
076: *</p>
077: *
078: * @see TokenizerProperties
079: * @see TokenizerPropertyListener
080: * @author Heiko Blau
081: */
082: public class TestMultithreadTokenizerProperties extends TestCase
083: implements TokenizerPropertyListener, DataProvider {
084:
085: //---------------------------------------------------------------------------
086: // main method
087: //
088:
089: /**
090: * call this method to invoke the tests.
091: */
092: public static void main(String[] args) {
093: String[] tests = { TestMultithreadTokenizerProperties.class
094: .getName() };
095:
096: TestUtilities.run(tests, args);
097: }
098:
099: //---------------------------------------------------------------------------
100: // suite method
101: //
102:
103: /**
104: * Implementation of the JUnit method <code>suite</code>. For each set of test
105: * properties one or more tests are instantiated.
106: *
107: * @return a test suite
108: */
109: public static Test suite() {
110: TestSuite suite = new TestSuite(
111: TestMultithreadTokenizerProperties.class.getName());
112:
113: suite.addTest(new TestMultithreadTokenizerProperties(
114: "testParallelModification"));
115: return suite;
116: }
117:
118: //---------------------------------------------------------------------------
119: // Constructor
120: //
121:
122: /**
123: * Default constructor. Standard input {@link java.lang.System#in} is used
124: * to construct the input stream reader.
125: */
126: public TestMultithreadTokenizerProperties(String test) {
127: super (test);
128: }
129:
130: //---------------------------------------------------------------------------
131: // Fixture setup and release
132: //
133:
134: /**
135: * Sets up the fixture, for example, open a network connection.
136: * This method is called before a test is executed.
137: */
138: protected void setUp() throws Exception {
139: _properties = new StandardTokenizerProperties();
140: _properties.addTokenizerPropertyListener(this );
141: _eventRecord = new HashMap(101);
142: // _eventRecord = new HashMap(101);
143: }
144:
145: /**
146: * Tears down the fixture, for example, close a network connection.
147: * This method is called after a test is executed.
148: */
149: protected void tearDown() throws Exception {
150: }
151:
152: //---------------------------------------------------------------------------
153: // test cases
154: //
155:
156: /**
157: * Testing generic methods.
158: */
159: public void testParallelModification() throws Throwable {
160: System.out.println("Number of threads: " + _numberOfThreads);
161: System.out.println("Test duration (s): " + _duration);
162:
163: Random random = new Random();
164: StringBuffer[] active = new StringBuffer[_numberOfThreads];
165: String[] last = new String[_numberOfThreads];
166: Runner[] runner = new Runner[_numberOfThreads];
167: Thread[] thread = new Thread[_numberOfThreads];
168: long start = System.currentTimeMillis();
169:
170: // Create resources
171: for (int index = 0; index < _numberOfThreads; ++index) {
172: active[index] = new StringBuffer("0");
173: runner[index] = new Runner(this , random
174: .nextInt(_testProperties.length - 1), 2,
175: active[index]);
176: thread[index] = new Thread(runner[index]);
177: }
178:
179: // start threads and check actions
180: try {
181: for (int index = 0; index < _numberOfThreads; ++index) {
182: thread[index].start();
183: }
184:
185: while (System.currentTimeMillis() - start < _duration * 1000) {
186: synchronized (this ) {
187: try {
188: wait(3000);
189: } catch (InterruptedException ex) {
190: }
191: }
192:
193: // print activity
194: for (int index = 0; index < _numberOfThreads; ++index) {
195: System.out.println(System.currentTimeMillis()
196: + ": Activity at runner " + index + ": "
197: + active[index]);
198: last[index] = active[index].toString();
199: }
200: }
201:
202: // stop the threads
203: for (int index = 0; index < _numberOfThreads; ++index) {
204: runner[index].stop();
205: }
206: Thread.sleep(5000);
207:
208: // check activity
209: for (int index = 0; index < _numberOfThreads; ++index) {
210: String activity = active[index].toString();
211:
212: assertTrue(
213: "No good activity at runner " + index + ": "
214: + activity,
215: new Integer(activity).intValue() > (600 / _numberOfThreads)
216: * _duration);
217: }
218:
219: } finally {
220: for (int index = 0; index < _numberOfThreads; ++index) {
221: thread[index] = null;
222: }
223: }
224: }
225:
226: //---------------------------------------------------------------------------
227: // TokenizerPropertyListener implementation
228: //
229:
230: /**
231: * Event handler method. The given {@link TokenizerPropertyEvent} parameter
232: * contains the nessecary information about the property change.
233: *
234: * @param event the {@link TokenizerPropertyEvent} that describes the change
235: */
236: public void propertyChanged(TokenizerPropertyEvent event) {
237: TokenizerProperty prop = event.getProperty();
238: String name = Thread.currentThread().getName();
239:
240: switch (event.getType()) {
241: case TokenizerPropertyEvent.PROPERTY_MODIFIED:
242: assertTrue("Modified non-existing property " + prop,
243: _eventRecord.containsKey(prop));
244: assertTrue("Property does not exist: " + prop, _properties
245: .propertyExists(prop));
246: break;
247:
248: case TokenizerPropertyEvent.PROPERTY_ADDED:
249: assertTrue("Added existing property " + prop, _eventRecord
250: .put(prop, prop) == null);
251: assertTrue("Property does not exist: " + prop, _properties
252: .propertyExists(prop));
253: break;
254:
255: case TokenizerPropertyEvent.PROPERTY_REMOVED:
256: assertTrue("Removed not added property " + prop,
257: _eventRecord.remove(prop) != null);
258: assertTrue("Property still exists: " + prop, !_properties
259: .propertyExists(prop));
260: break;
261: }
262: }
263:
264: /**
265: * See {@link de.susebox.jtopas.spi.DataProvider} for details.
266: *
267: * @return the character buffer to read data from
268: */
269: public char[] getData() {
270: return _currData;
271: }
272:
273: /**
274: * See {@link de.susebox.jtopas.spi.DataProvider} for details.
275: *
276: * @return a copy of the valid data of this {@link DataProvider}
277: * @see #getData
278: */
279: public char[] getDataCopy() {
280: char[] dst = new char[_currDataLength];
281:
282: System.arraycopy(_currData, _currStartPos, dst, 0,
283: _currDataLength);
284: return dst;
285: }
286:
287: /**
288: * See {@link de.susebox.jtopas.spi.DataProvider} for details.
289: *
290: * @param testChar check this character
291: * @return <code>true</code> if the given character is a separator,
292: * <code>false</code> otherwise
293: */
294: public int getLength() {
295: return _currDataLength;
296: }
297:
298: /**
299: * See {@link de.susebox.jtopas.spi.DataProvider} for details.
300: *
301: * @return index in the character array returned by {@link #getData}, where data starts
302: */
303: public int getStartPosition() {
304: return _currStartPos;
305: }
306:
307: /**
308: * See {@link de.susebox.jtopas.spi.DataProvider#getCharAt} for details.
309: *
310: * @param index an index between 0 and {@link #getLength}
311: * @return the character at the given index;
312: */
313: public char getCharAt(int index) {
314: return _currData[_currStartPos + index];
315: }
316:
317: //---------------------------------------------------------------------------
318: // class members
319: //
320: protected static int _numberOfThreads = 20;
321: protected static int _duration = 60;
322:
323: protected static final TokenizerProperty[] _testProperties = {
324: new TokenizerProperty(Token.KEYWORD, new String[] { "k1" }),
325: new TokenizerProperty(Token.STRING, new String[] { "\"",
326: "\"", "\\" }),
327: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
328: new String[] { ">>>" }, new Object()),
329: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
330: new String[] { ">>" }),
331: new TokenizerProperty(Token.KEYWORD, new String[] { "k2" },
332: new Object(), Flags.F_NO_CASE),
333: new TokenizerProperty(Token.KEYWORD, new String[] { "k3" },
334: new Object(), Flags.F_NO_CASE),
335: new TokenizerProperty(Token.LINE_COMMENT,
336: new String[] { "//" }),
337: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
338: new String[] { "<<" }, new Object()),
339: new TokenizerProperty(Token.BLOCK_COMMENT, new String[] {
340: "/**", "*/" }, new Object()),
341: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
342: new String[] { "+=" }),
343: new TokenizerProperty(Token.KEYWORD, new String[] { "H1" },
344: null, Flags.F_NO_CASE),
345: new TokenizerProperty(Token.KEYWORD, new String[] { "H2" },
346: null, Flags.F_NO_CASE),
347: new TokenizerProperty(Token.KEYWORD, new String[] { "H3" },
348: null, Flags.F_NO_CASE),
349: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
350: new String[] { "-=" }, new Object()),
351: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
352: new String[] { "*=" }),
353: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
354: new String[] { "**=" }),
355: new TokenizerProperty(Token.LINE_COMMENT,
356: new String[] { "#" }, new Object()),
357: new TokenizerProperty(Token.LINE_COMMENT,
358: new String[] { "rem" }, null, Flags.F_NO_CASE),
359: new TokenizerProperty(Token.BLOCK_COMMENT, new String[] {
360: "/*", "*/" }),
361: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
362: new String[] { "++" }),
363: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
364: new String[] { "+++" }, new Object()),
365: new TokenizerProperty(Token.BLOCK_COMMENT, new String[] {
366: "{{", "}}" }, new Object()),
367: new TokenizerProperty(Token.BLOCK_COMMENT, new String[] {
368: "[startBlockComment]", "[endBlockComment]" }, null,
369: Flags.F_NO_CASE),
370: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
371: new String[] { "**" }),
372: new TokenizerProperty(Token.BLOCK_COMMENT, new String[] {
373: "<!--", "-->" }),
374: new TokenizerProperty(Token.STRING, new String[] { "'",
375: "'", "\\" }),
376: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
377: new String[] { ":=" }),
378: new TokenizerProperty(Token.PATTERN,
379: new String[] { "[+\\-]?[0-9]+\\.?[0-9]*" },
380: new Object()),
381: new TokenizerProperty(Token.KEYWORD, new String[] { "if" },
382: new Object()),
383: new TokenizerProperty(Token.KEYWORD,
384: new String[] { "then" }, new Object()),
385: new TokenizerProperty(Token.PATTERN,
386: new String[] { "[A-Z_][A-Z0-9_]*" }, new Object(),
387: Flags.F_NO_CASE),
388: new TokenizerProperty(Token.KEYWORD,
389: new String[] { "while" }, new Object()),
390: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
391: new String[] { "!=" }, new Object()),
392: new TokenizerProperty(Token.KEYWORD,
393: new String[] { "loop" }, new Object()),
394: new TokenizerProperty(Token.KEYWORD,
395: new String[] { "class" }, new Object()),
396: new TokenizerProperty(Token.KEYWORD,
397: new String[] { "interface" }, new Object()),
398: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
399: new String[] { "<>" }, new Object()),
400: new TokenizerProperty(Token.LINE_COMMENT,
401: new String[] { "--" }, new Object()),
402: new TokenizerProperty(Token.KEYWORD,
403: new String[] { "synchronized" }, new Object()),
404: new TokenizerProperty(Token.KEYWORD,
405: new String[] { "final" }),
406: new TokenizerProperty(Token.KEYWORD,
407: new String[] { "implements" }),
408: new TokenizerProperty(Token.KEYWORD, new String[] { "int" }),
409: new TokenizerProperty(Token.STRING, new String[] { "''",
410: "''", "''" }, new Object()),
411: new TokenizerProperty(Token.KEYWORD,
412: new String[] { "boolean" }),
413: new TokenizerProperty(Token.KEYWORD,
414: new String[] { "void" }),
415: new TokenizerProperty(Token.KEYWORD, new String[] { "do" }),
416: new TokenizerProperty(Token.KEYWORD,
417: new String[] { "HREF" }, null, Flags.F_NO_CASE),
418: new TokenizerProperty(Token.KEYWORD,
419: new String[] { "FONT" }, null, Flags.F_NO_CASE),
420: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
421: new String[] { "." }),
422: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
423: new String[] { "{" }),
424: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
425: new String[] { "}" }),
426: new TokenizerProperty(Token.KEYWORD,
427: new String[] { "import" }),
428: new TokenizerProperty(Token.KEYWORD,
429: new String[] { "package" }),
430: new TokenizerProperty(Token.KEYWORD,
431: new String[] { "static" }),
432: new TokenizerProperty(Token.KEYWORD,
433: new String[] { "try" }, new Object()),
434: new TokenizerProperty(Token.KEYWORD,
435: new String[] { "catch" }, new Object()),
436: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
437: new String[] { "/" }),
438: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
439: new String[] { "/=" }),
440: new TokenizerProperty(Token.KEYWORD,
441: new String[] { "table" }, null, Flags.F_NO_CASE),
442: new TokenizerProperty(Token.KEYWORD,
443: new String[] { "span" }, null, Flags.F_NO_CASE),
444: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
445: new String[] { "[" }),
446: new TokenizerProperty(Token.KEYWORD,
447: new String[] { "layer" }, null, Flags.F_NO_CASE),
448: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
449: new String[] { "]" }),
450: new TokenizerProperty(Token.KEYWORD,
451: new String[] { "STYLE" }, null, Flags.F_NO_CASE),
452: new TokenizerProperty(Token.KEYWORD,
453: new String[] { "image" }, null, Flags.F_NO_CASE),
454: new TokenizerProperty(Token.SPECIAL_SEQUENCE,
455: new String[] { "+" }) };
456:
457: //---------------------------------------------------------------------------
458: // Members
459: //
460: protected Map _eventRecord = null;
461: protected TokenizerProperties _properties = null;
462: private char[] _currData = new char[8192];
463: private int _currStartPos = 0;
464: private int _currDataLength = 0;
465:
466: //---------------------------------------------------------------------------
467: // inner classes
468: //
469:
470: /**
471: * Thread for TokenizerProperties manipulation
472: */
473: class Runner implements Runnable {
474:
475: /**
476: * Constructor taking the {@link TokenizerProperties} instance to work, an array
477: * of {@link TokenizerProperty} objects to add and remove from the <code>TokenizerProperties</code>
478: * object and a step interval for the entries in the property array.
479: */
480: public Runner(TestMultithreadTokenizerProperties parent,
481: int start, int step, StringBuffer activity) {
482: _parent = parent;
483: _start = start;
484: _activity = activity;
485: if ((_step = step) < 1) {
486: _step = 1;
487: }
488: }
489:
490: /**
491: * When an object implementing interface <code>Runnable</code> is used
492: * to create a thread, starting the thread causes the object's
493: * <code>run</code> method to be called in that separately executing
494: * thread.
495: * <p>
496: * The general contract of the method <code>run</code> is that it may
497: * take any action whatsoever.
498: *
499: * @see java.lang.Thread#run()
500: */
501: public void run() {
502: Thread thread = Thread.currentThread();
503: String name = thread.getName();
504: int counter = _start;
505:
506: try {
507: while (Thread.currentThread() == thread && !_stop) {
508: synchronized (this ) {
509: long start = System.currentTimeMillis();
510: int index = counter
511: % _parent._testProperties.length;
512: TokenizerProperty prop = _parent._testProperties[index];
513:
514: // add or remove a property
515: // System.out.println(name + ": checking " + prop);
516: if (_parent._properties.propertyExists(prop)) {
517: _parent._properties.removeProperty(prop);
518: } else {
519: _parent._properties.addProperty(prop);
520: }
521:
522: // check duration
523: long duration = System.currentTimeMillis()
524: - start;
525: if (duration > 5000) {
526: _parent.assertTrue(name
527: + ": waited too long: " + duration,
528: false);
529: break;
530: }
531:
532: // increase counter
533: counter += _step;
534:
535: // signal activity
536: long value = Long.parseLong(_activity
537: .toString());
538: _activity.setLength(0);
539: _activity.append(value + 1);
540:
541: // pause a little bit
542: try {
543: wait(1);
544: } catch (InterruptedException ex) {
545: }
546: }
547: }
548: } catch (Throwable t) {
549: t.printStackTrace();
550: }
551: System.out.println(name + ": exiting. Activity: "
552: + _activity);
553: }
554:
555: /**
556: * Signal the thread to stop
557: */
558: public void stop() {
559: synchronized (this ) {
560: _stop = true;
561: }
562: }
563:
564: //---------------------------------------------------------------------------
565: // Members
566: //
567: private TestMultithreadTokenizerProperties _parent = null;
568: private int _start = 0;
569: private int _step = 0;
570: private boolean _stop = false;
571: private StringBuffer _activity = null;
572: }
573: }
|