1
2
3
4
5
6
7
8
9
10 package org.dom4j;
11
12 import com.clarkware.junitperf.LoadTest;
13 import com.clarkware.junitperf.TimedTest;
14
15 import java.text.FieldPosition;
16 import java.text.SimpleDateFormat;
17 import java.util.Date;
18
19 import junit.extensions.RepeatedTest;
20 import junit.framework.Test;
21 import junit.framework.TestSuite;
22
23
24 /*** A test harness to test the dom4j package in a threaded environment
25 *
26 * @author <a href="mailto:ddlucas@lse.com">David Lucas</a>
27 * @version $Revision: 1.4 $
28 */
29 public class TestThreading extends AbstractTestCase {
30 public TestThreading(String name) {
31 super(name);
32 }
33
34 final static boolean debug=true;
35
36 final static java.lang.ThreadLocal formatterCache = new java.lang.ThreadLocal();
37 final static String seperator=" - ";
38 final static FieldPosition FIELD_ZERO = new FieldPosition(0);
39
40 private final static void preformat(StringBuffer strBuf, String context) {
41 long now = System.currentTimeMillis();
42 Date currentTime = new Date(now);
43 SimpleDateFormat formatter = (SimpleDateFormat) formatterCache.get();
44 if (formatter == null) {
45 formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS zzz");
46 formatterCache.set(formatter);
47 }
48 strBuf.append("[");
49 formatter.format(currentTime, strBuf, FIELD_ZERO);
50 strBuf.append(" (").append(now).append(") ]");
51
52 strBuf.append(seperator);
53 strBuf.append(getThreadId());
54 strBuf.append(seperator);
55 strBuf.append(context);
56 strBuf.append(seperator);
57 }
58
59 private static final String getThreadId() {
60 String tid = Thread.currentThread().getName();
61 return tid;
62 }
63
64 public static final void debug(String context, String msg ) {
65 if (debug) {
66 StringBuffer sb = new StringBuffer();
67 preformat(sb,context);
68 sb.append(msg);
69 System.out.println(sb.toString());
70 }
71 }
72
73 /***
74 * static initializer
75 */
76 static {
77 }
78
79 /***
80 * The JUnit setup method
81 */
82 protected void setUp() { }
83
84
85 /***
86 * The teardown method for JUnit
87 */
88 protected void tearDown() { }
89
90
91 /***
92 * This test combines many different types of operations on DOM4J in a
93 * threaded environment. If a problem occurs with threading, the tests will
94 * fail. This was used to help isolate an internal threading issue.
95 * Unfortunately it may not always create the condition necessary to break
96 * un-thread-safe code. This is due to the nature of the machine, JVM, and
97 * application and if the conditions are right. Typically the problems of
98 * multithreading occur due to an unprotected HashMap or ArrayList in a class
99 * being used by more than one thread. Also, most developers think that
100 * their class or object instance will only be used by one thread. But if
101 * a factory or singleton caches a class or instance, it can quickly become
102 * an unsafe environment. Hence this test to assist in locating threading
103 * issues.
104 */
105 public void testCombo() {
106 int loop = 10;
107 try {
108 debug("testCombo", "beginning test...");
109
110 long begin = System.currentTimeMillis();
111 String value=null;
112 String expected=null;
113 String xml=null;
114 Document doc=null;
115 Element root=null;
116 Element item=null;
117 Element newItem=null;
118 QName qn = null;
119 Namespace ns = null;
120 long now=0;
121
122 xml = "<ROOT xmlns:t0=\"http://www.lse.com/t0\" >" +
123 " <ctx><type>Context</type></ctx>" +
124 " <A><B><C><D>This is a TEST</D></C></B></A>" +
125 " <t0:Signon><A>xyz</A><t0:Cust>customer</t0:Cust></t0:Signon>" +
126 "</ROOT>";
127
128
129 for (int i = 0; i < loop; i++) {
130 doc = DocumentHelper.parseText(xml);
131
132 root = doc.getRootElement();
133 ns = Namespace.get("t0","http://www.lse.com/t0");
134 qn = QName.get("Signon",ns);
135 item = root.element(qn);
136 value = item.asXML();
137 expected="<t0:Signon xmlns:t0=\"http://www.lse.com/t0\"><A>xyz</A><t0:Cust>customer</t0:Cust></t0:Signon>";
138 assertEquals("test t0:Signon ",expected,value);
139
140 qn = root.getQName("Test");
141 newItem = DocumentHelper.createElement(qn);
142 now=System.currentTimeMillis();
143 newItem.setText(String.valueOf(now));
144 root.add(newItem);
145
146 qn = root.getQName("Test2");
147 newItem = DocumentHelper.createElement(qn);
148 now=System.currentTimeMillis();
149 newItem.setText(String.valueOf(now));
150 root.add(newItem);
151
152 item=root.element(qn);
153 item.detach();
154 item.setQName(qn);
155 root.add(item);
156 value = item.asXML();
157 expected="<Test2>"+now+"</Test2>";
158 assertEquals("test Test2 ",expected,value);
159
160 qn = root.getQName("Test3");
161 newItem = DocumentHelper.createElement(qn);
162 now=System.currentTimeMillis();
163 newItem.setText(String.valueOf(now));
164 root.add(newItem);
165
166 item=root.element(qn);
167 item.detach();
168 item.setQName(qn);
169 root.add(item);
170 value = item.asXML();
171 expected="<Test3>"+now+"</Test3>";
172 assertEquals("test Test3 ",expected,value);
173
174 qn = item.getQName("Test4");
175 newItem = DocumentHelper.createElement(qn);
176 now=System.currentTimeMillis();
177 newItem.setText(String.valueOf(now));
178 root.add(newItem);
179
180 item=root.element(qn);
181 item.detach();
182 item.setQName(qn);
183 root.add(item);
184 value = item.asXML();
185 expected="<Test4>"+now+"</Test4>";
186 assertEquals("test Test4 ",expected,value);
187 }
188 double duration = System.currentTimeMillis()-begin;
189 double avg = duration / loop;
190 debug("testCombo",
191 "*** duration="+duration+" , loop="+loop+" , avg="+avg);
192 }
193 catch (Exception e) {
194 e.printStackTrace();
195 assertTrue("Exception in test: "+e.getMessage(),false);
196 }
197 }
198
199
200 /***
201 * This test isolates QNameCache in a multithreaded environment.
202 */
203 public void testQNameCache() {
204 int loop = 100;
205 try {
206 debug("testQNameCache", "beginning test...");
207
208 long begin = System.currentTimeMillis();
209 String value=null;
210 String expected=null;
211 String xml=null;
212 Document doc=null;
213 Element root=null;
214 Element item=null;
215 Element newItem=null;
216 QName qn = null;
217 Namespace ns = null;
218 long now=0;
219
220 xml = "<ROOT xmlns:t0=\"http://www.lse.com/t0\" >" +
221 " <ctx><type>Context</type></ctx>" +
222 " <A><B><C><D>This is a TEST</D></C></B></A>" +
223 " <t0:Signon><A>xyz</A><t0:Cust>customer</t0:Cust></t0:Signon>" +
224 "</ROOT>";
225
226
227 for (int i = 0; i < loop; i++) {
228 doc = DocumentHelper.parseText(xml);
229 root = doc.getRootElement();
230
231 qn=DocumentHelper.createQName("test");
232 value = fetchValue(qn);
233 expected="<test/>";
234 assertEquals("test test ",expected,value);
235
236
237 qn=DocumentHelper.createQName("test");
238 value = fetchValue(qn);
239 expected="<test/>";
240 assertEquals("test test again ",expected,value);
241
242 qn=root.getQName("t0:Signon");
243 value= fetchValue(qn);
244 expected="<t0:Signon xmlns:t0=\"http://www.lse.com/t0\"/>";
245 assertEquals("test t0:Signon ",expected,value);
246
247 }
248 double duration = System.currentTimeMillis()-begin;
249 double avg = duration / loop;
250 debug("testQNameCache",
251 "*** duration="+duration+" , loop="+loop+" , avg="+avg);
252 }
253 catch (Exception e) {
254 e.printStackTrace();
255 assertTrue("Exception in test: "+e.getMessage(),false);
256 }
257 }
258
259
260 /***
261 * This method creates a value that can be expected during a test
262 * @param qn
263 * @return
264 */
265 public String fetchValue(QName qn) {
266 String value=null;
267
268 StringBuffer sb = new StringBuffer();
269 sb.append("<");
270 String prefix=qn.getNamespacePrefix();
271 if (prefix!=null && prefix.length()>0) {
272 sb.append(prefix).append(":");
273 }
274 sb.append(qn.getName());
275 String uri=qn.getNamespaceURI();
276 if (uri!=null && uri.length()>0) {
277 sb.append(" xmlns");
278 if (prefix!=null && prefix.length()>0){
279 sb.append(":").append(prefix);
280 }
281 sb.append("=\"").append(uri).append("\"");
282 }
283 sb.append("/>");
284
285 value=sb.toString();
286 return value;
287 }
288
289 /***
290 * Assembles and returns a test suite.
291 *
292 *@return The suite
293 */
294 public static Test suite() {
295 TestSuite suite = new TestSuite();
296 suite.addTest(makeRepeatedLoadTest(5, 10, "testCombo"));
297 suite.addTest(makeRepeatedLoadTest(5, 10, "testQNameCache"));
298 return suite;
299 }
300
301
302
303 /***
304 * JUnit method to exercise test via threads and loops
305 *
306 *@param users Number of users to simulate (i.e. Threads).
307 *@param iterations Number of iterations per user ( repeat the test x times).
308 *@param method method to execute (testXXX).
309 *@return A Junit test
310 */
311 protected static Test makeRepeatedLoadTest(int users, int iterations, String testMethod) {
312 long maxElapsedTime = 120000 + (1000 * users * iterations);
313
314 Test testCase = new TestThreading(testMethod);
315
316 Test repeatedTest = new RepeatedTest(testCase, iterations);
317 Test loadTest = new LoadTest(repeatedTest, users);
318 Test timedTest = new TimedTest(loadTest, maxElapsedTime);
319
320 return timedTest;
321 }
322
323 /***
324 * The main program for the DOM4JThreadTest class
325 *
326 *@param args The command line arguments
327 */
328 public static void main(String[] args) {
329 String[] testCaseName = {TestThreading.class.getName()};
330 junit.textui.TestRunner.main(testCaseName);
331 }
332
333
334 }
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382