1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.impl.wsdl.loadtest;
14
15 import java.beans.PropertyChangeEvent;
16 import java.beans.PropertyChangeListener;
17 import java.util.ArrayList;
18 import java.util.Date;
19 import java.util.HashSet;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Set;
23
24 import com.eviware.soapui.config.LoadTestConfig;
25 import com.eviware.soapui.config.LoadTestLimitTypesConfig;
26 import com.eviware.soapui.config.TestCaseConfig;
27 import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogMessageEntry;
28 import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
29 import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
30 import com.eviware.soapui.model.settings.Settings;
31 import com.eviware.soapui.model.support.PropertiesMap;
32 import com.eviware.soapui.model.testsuite.LoadTest;
33 import com.eviware.soapui.model.testsuite.LoadTestRunListener;
34 import com.eviware.soapui.model.testsuite.LoadTestRunner;
35 import com.eviware.soapui.model.testsuite.TestRunContext;
36 import com.eviware.soapui.model.testsuite.TestRunListener;
37 import com.eviware.soapui.model.testsuite.TestRunner;
38 import com.eviware.soapui.model.testsuite.TestStepResult;
39 import com.eviware.soapui.settings.HttpSettings;
40 import com.eviware.soapui.support.UISupport;
41 import com.eviware.x.dialogs.Worker;
42 import com.eviware.x.dialogs.XProgressDialog;
43 import com.eviware.x.dialogs.XProgressMonitor;
44
45 /***
46 * TestRunner for load-tests.
47 *
48 * @todo statistics should be calculated first after all threads have been started..
49 *
50 * @author Ole.Matzura
51 */
52
53 public class WsdlLoadTestRunner implements LoadTestRunner
54 {
55 private final WsdlLoadTest loadTest;
56 private ThreadGroup threadGroup;
57 private Set<TestCaseRunner> runners = new HashSet<TestCaseRunner>();
58 private long startTime = 0;
59 private InternalPropertyChangeListener internalPropertyChangeListener = new InternalPropertyChangeListener();
60 private InternalTestRunListener testRunListener = new InternalTestRunListener();
61 private LoadTestRunListener[] listeners;
62 private long runCount;
63 private Status status;
64 private WsdlLoadTestContext context;
65 private String reason;
66 private int threadCount;
67
68 public WsdlLoadTestRunner(WsdlLoadTest test)
69 {
70 this.loadTest = test;
71 threadGroup = new ThreadGroup( loadTest.getName() );
72
73 status = Status.INITIALIZED;
74 }
75
76 public Status getStatus()
77 {
78 return status;
79 }
80
81 void start()
82 {
83 startTime = System.currentTimeMillis();
84
85 runners.clear();
86 loadTest.addPropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener );
87 runCount = 0;
88 threadCount = 0;
89 context = new WsdlLoadTestContext( this );
90
91 status = Status.RUNNING;
92
93 listeners = loadTest.getLoadTestRunListeners();
94 for( LoadTestRunListener listener : listeners )
95 {
96 listener.beforeLoadTest( this, context );
97 }
98
99 loadTest.getLoadTestLog().addEntry(
100 new LoadTestLogMessageEntry( "LoadTest started at " + new Date( startTime ) ));
101
102 int startDelay = loadTest.getStartDelay();
103 if( startDelay > 0 )
104 {
105 XProgressDialog progressDialog = UISupport.getDialogs().createProgressDialog( "Starting threads",
106 ( int ) loadTest.getThreadCount(),
107 "", true );
108 try
109 {
110 progressDialog.run( new Worker.WorkerAdapter() {
111
112 private List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
113
114 public Object construct( XProgressMonitor monitor )
115 {
116 int startDelay = loadTest.getStartDelay();
117
118 for( int c = 0; c < loadTest.getThreadCount(); c++ )
119 testCases.add( createTestCase() );
120
121 int cnt = 0;
122 while( !testCases.isEmpty() )
123 {
124 if( startDelay > 0 )
125 {
126 try
127 {
128 Thread.sleep( startDelay );
129 }
130 catch( InterruptedException e )
131 {
132 e.printStackTrace();
133 }
134 }
135
136 if( status != Status.RUNNING || getProgress() >= 1 ||
137 (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT &&
138 runners.size() >= loadTest.getTestLimit() ))
139 break;
140
141
142 if( !testCases.isEmpty() )
143 {
144 startTestCase( testCases.remove( 0 ));
145 monitor.setProgress( 1, "Started thread " + (++cnt) );
146 }
147 }
148
149 return null;
150 }
151
152 public boolean onCancel()
153 {
154 cancel( "Stopped from UI during start-up" );
155 while( !testCases.isEmpty() )
156 testCases.remove( 0 ).release();
157
158 return false;
159 }} );
160
161 }
162 catch( Exception e )
163 {
164 e.printStackTrace();
165 }
166 }
167 else
168 {
169 List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
170 for( int c = 0; c < loadTest.getThreadCount(); c++ )
171 testCases.add( createTestCase() );
172
173 for( int c = 0; c < loadTest.getThreadCount(); c++ )
174 {
175 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT &&
176 runners.size() >= loadTest.getTestLimit() )
177 break;
178
179 startTestCase( testCases.get( c ) );
180 }
181 }
182
183 if( status == Status.RUNNING )
184 {
185 for( LoadTestRunListener listener : listeners )
186 {
187 listener.loadTestStarted( this, context );
188 }
189 }
190 }
191
192 private TestCaseRunner startTestCase( WsdlTestCase testCase )
193 {
194 TestCaseRunner testCaseRunner = new TestCaseRunner( testCase, threadCount++ );
195 Thread thread = new Thread( threadGroup, testCaseRunner);
196 thread.start();
197 runners.add( testCaseRunner );
198 return testCaseRunner;
199 }
200
201 public void cancel( String reason )
202 {
203 if( status != Status.RUNNING )
204 return;
205
206 this.reason = reason;
207 status = Status.CANCELED;
208
209 for( LoadTestRunListener listener : listeners )
210 {
211 listener.loadTestStopped( this, context );
212 }
213
214 TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
215
216 for( TestCaseRunner runner : r )
217 {
218 runner.cancel( reason, true );
219 }
220
221 String msg = "LoadTest [" + loadTest.getName()+ "] canceled";
222 if( reason != null )
223 msg += "; " + reason;
224
225 loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
226 }
227
228 public void fail( String reason )
229 {
230 if( status != Status.RUNNING )
231 return;
232
233 this.reason = reason;
234 status = Status.FAILED;
235
236 for( LoadTestRunListener listener : listeners )
237 {
238 listener.loadTestStopped( this, context );
239 }
240
241 TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
242
243 for( TestCaseRunner runner : r )
244 {
245 runner.cancel( reason, true );
246 }
247
248 String msg = "LoadTest [" + loadTest.getName()+ "] failed";
249 if( reason != null )
250 msg += "; " + reason;
251
252 loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
253 }
254
255 public void waitUntilFinished()
256 {
257 synchronized( threadGroup )
258 {
259 try
260 {
261 if( threadGroup.activeCount() > 0)
262 threadGroup.wait();
263 }
264 catch (InterruptedException e)
265 {
266 e.printStackTrace();
267 }
268 }
269 }
270
271 public void finishTestCase( String reason, WsdlTestCase testCase )
272 {
273 for( TestCaseRunner runner : runners )
274 {
275 if( runner.getTestCase() == testCase )
276 {
277 runner.cancel( reason, false );
278 break;
279 }
280 }
281 }
282
283 public void finishRunner( TestCaseRunner runner )
284 {
285 if( !runners.contains( runner ))
286 {
287 throw new RuntimeException( "Trying to finish unknown runner.. " );
288 }
289
290 runners.remove( runner );
291 if( runners.size() == 0 )
292 {
293 loadTest.removePropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY,
294 internalPropertyChangeListener );
295
296 if( status == Status.RUNNING )
297 status = Status.FINISHED;
298
299 loadTest.getLoadTestLog().addEntry(
300 new LoadTestLogMessageEntry( "LoadTest ended at " + new Date( System.currentTimeMillis() ) ));
301
302 for( LoadTestRunListener listener : listeners )
303 {
304 listener.afterLoadTest( this, context );
305 }
306 }
307 }
308
309 public int getRunningThreadCount()
310 {
311 return runners.size();
312 }
313
314 public float getProgress()
315 {
316 long testLimit = loadTest.getTestLimit();
317 if( testLimit == 0 )
318 return -1;
319
320 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
321 return (float)runCount / (float)testLimit;
322
323 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
324 return (float)getTimeTaken() / (float)(testLimit*1000);
325
326 return -1;
327 }
328
329 private boolean afterRun( TestCaseRunner runner )
330 {
331 if( loadTest.getTestLimit() < 1 ) return true;
332
333 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
334 return runCount++ + runners.size() < loadTest.getTestLimit();
335
336 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
337 return getTimeTaken() < loadTest.getTestLimit()*1000;
338
339 return true;
340 }
341
342 public class TestCaseRunner implements Runnable
343 {
344 private final WsdlTestCase testCase;
345 private boolean canceled;
346 private long runCount;
347 private WsdlTestCaseRunner runner;
348 private final int threadIndex;
349
350 public TestCaseRunner(WsdlTestCase testCase, int threadIndex )
351 {
352 this.testCase = testCase;
353 this.threadIndex = threadIndex;
354 }
355
356 public void run()
357 {
358 runner = new WsdlTestCaseRunner( testCase, PropertiesMap.EMPTY_MAP );
359
360 while( !canceled )
361 {
362 try
363 {
364 runner.getRunContext().reset();
365 runner.getRunContext().setProperty( TestRunContext.THREAD_INDEX, threadIndex );
366 runner.getRunContext().setProperty( TestRunContext.RUN_COUNT, runCount );
367 runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_RUNNER, WsdlLoadTestRunner.this );
368 runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_CONTEXT, context );
369
370 runner.run();
371 }
372 catch( Throwable e )
373 {
374 System.err.println( "Error running testcase: " + e );
375 e.printStackTrace();
376 }
377
378 runCount++;
379
380 if( !afterRun( this ) )
381 break;
382 }
383
384 finishRunner( this );
385 testCase.release();
386 }
387
388 public void cancel( String reason, boolean cancelRunner )
389 {
390 if( runner != null && cancelRunner )
391 runner.cancel( reason );
392
393 this.canceled = true;
394 }
395
396 public boolean isCanceled()
397 {
398 return canceled;
399 }
400
401 public long getRunCount()
402 {
403 return runCount;
404 }
405
406 public WsdlTestCase getTestCase()
407 {
408 return testCase;
409 }
410 }
411
412 public LoadTest getLoadTest()
413 {
414 return loadTest;
415 }
416
417 public class InternalPropertyChangeListener implements PropertyChangeListener
418 {
419 public void propertyChange(PropertyChangeEvent evt)
420 {
421 updateThreadCount();
422 }
423 }
424
425 public synchronized void updateThreadCount()
426 {
427 if( status != Status.RUNNING ) return;
428
429 long newCount = loadTest.getThreadCount();
430
431
432 Iterator<TestCaseRunner> iterator = runners.iterator();
433 List<TestCaseRunner> activeRunners = new ArrayList<TestCaseRunner>();
434 while( iterator.hasNext() )
435 {
436 TestCaseRunner runner = iterator.next();
437 if( !runner.isCanceled() )
438 activeRunners.add( runner );
439 }
440
441 long diff = newCount-activeRunners.size();
442
443 if( diff == 0 )
444 return;
445
446
447 if( diff < 0 )
448 {
449 diff = Math.abs( diff );
450 for( int c = 0; c < diff && c < activeRunners.size(); c++ )
451 {
452 activeRunners.get( c ).cancel( "excessive thread", false );
453 }
454 }
455
456 else if( diff > 0 )
457 {
458 for( int c = 0; c < diff; c++ )
459 {
460 int startDelay = loadTest.getStartDelay();
461 if( startDelay > 0 )
462 {
463 try
464 {
465 Thread.sleep( startDelay );
466 }
467 catch( InterruptedException e )
468 {
469 e.printStackTrace();
470 }
471 }
472
473 if( status == Status.RUNNING )
474 startTestCase( createTestCase() );
475 }
476 }
477 }
478
479 private WsdlTestCase createTestCase()
480 {
481 WsdlTestCase testCase = loadTest.getTestCase();
482
483
484 TestCaseConfig config = (TestCaseConfig) testCase.getConfig().copy();
485 config.setLoadTestArray( new LoadTestConfig[0] );
486
487
488 WsdlTestCase tc = new WsdlTestCase( testCase.getTestSuite(), config );
489 tc.addTestRunListener( testRunListener );
490 Settings settings = tc.getSettings();
491 settings.setBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN ));
492 settings.setBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN ));
493 settings.setBoolean( HttpSettings.CLOSE_CONNECTIONS, loadTest.getSettings().getBoolean( HttpSettings.CLOSE_CONNECTIONS ));
494
495
496 tc.setDiscardOkResults( false );
497 return tc;
498 }
499
500 public String getReason()
501 {
502 return reason;
503 }
504
505 public long getTimeTaken()
506 {
507 return System.currentTimeMillis()-startTime;
508 }
509
510 private class InternalTestRunListener implements TestRunListener
511 {
512 public void beforeRun(TestRunner testRunner, TestRunContext runContext)
513 {
514 for( LoadTestRunListener listener : listeners )
515 {
516 listener.beforeTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
517 }
518 }
519
520 public void beforeStep(TestRunner testRunner, TestRunContext runContext)
521 {
522 for( LoadTestRunListener listener : listeners )
523 {
524 listener.beforeTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, runContext.getCurrentStep() );
525 }
526 }
527
528 public void afterStep(TestRunner testRunner, TestRunContext runContext, TestStepResult result)
529 {
530 for( LoadTestRunListener listener : listeners )
531 {
532 listener.afterTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, result );
533 }
534 }
535
536 public void afterRun(TestRunner testRunner, TestRunContext runContext)
537 {
538 for( LoadTestRunListener listener : listeners )
539 {
540 listener.afterTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
541 }
542 }
543 }
544 }