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.SoapUI;
25 import com.eviware.soapui.config.LoadTestConfig;
26 import com.eviware.soapui.config.LoadTestLimitTypesConfig;
27 import com.eviware.soapui.config.TestCaseConfig;
28 import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogMessageEntry;
29 import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
30 import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
31 import com.eviware.soapui.model.settings.Settings;
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.settings.WsdlSettings;
41 import com.eviware.soapui.support.UISupport;
42 import com.eviware.soapui.support.types.StringToObjectMap;
43 import com.eviware.x.dialogs.Worker;
44 import com.eviware.x.dialogs.XProgressDialog;
45 import com.eviware.x.dialogs.XProgressMonitor;
46
47 /***
48 * TestRunner for load-tests.
49 *
50 * @todo statistics should be calculated first after all threads have been started..
51 *
52 * @author Ole.Matzura
53 */
54
55 public class WsdlLoadTestRunner implements LoadTestRunner
56 {
57 private final WsdlLoadTest loadTest;
58 private ThreadGroup threadGroup;
59 private Set<TestCaseRunner> runners = new HashSet<TestCaseRunner>();
60 private long startTime = 0;
61 private InternalPropertyChangeListener internalPropertyChangeListener = new InternalPropertyChangeListener();
62 private InternalTestRunListener testRunListener = new InternalTestRunListener();
63 private LoadTestRunListener[] listeners;
64 private long runCount;
65 private Status status;
66 private WsdlLoadTestContext context;
67 private String reason;
68 private int threadCount;
69 private int threadsWaitingToStart;
70 private boolean hasTearedDown;
71
72 public WsdlLoadTestRunner(WsdlLoadTest test)
73 {
74 this.loadTest = test;
75 threadGroup = new ThreadGroup( loadTest.getName() );
76
77 status = Status.INITIALIZED;
78 }
79
80 public Status getStatus()
81 {
82 return status;
83 }
84
85 void start()
86 {
87 loadTest.getTestCase().beforeSave();
88 startTime = System.currentTimeMillis();
89
90 runners.clear();
91 loadTest.addPropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener );
92 runCount = 0;
93 threadCount = 0;
94 threadsWaitingToStart = 0;
95 context = new WsdlLoadTestContext( this );
96
97 try
98 {
99 loadTest.runSetupScript(context, this);
100 }
101 catch (Exception e1)
102 {
103 SoapUI.logError( e1 );
104 };
105
106 status = Status.RUNNING;
107
108 listeners = loadTest.getLoadTestRunListeners();
109 for( LoadTestRunListener listener : listeners )
110 {
111 try
112 {
113 listener.beforeLoadTest( this, context );
114 }
115 catch( Throwable e )
116 {
117 SoapUI.logError( e );
118 }
119 }
120
121 loadTest.getLoadTestLog().addEntry(
122 new LoadTestLogMessageEntry( "LoadTest started at " + new Date( startTime ) ));
123
124 int startDelay = loadTest.getStartDelay();
125 if( startDelay >= 0 )
126 {
127 XProgressDialog progressDialog = UISupport.getDialogs().createProgressDialog( "Starting threads",
128 ( int ) loadTest.getThreadCount(),
129 "", true );
130 try
131 {
132 progressDialog.run( new Worker.WorkerAdapter() {
133
134 private List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
135 private boolean canceled;
136
137 public Object construct( XProgressMonitor monitor )
138 {
139 int startDelay = loadTest.getStartDelay();
140
141 for( int c = 0; c < loadTest.getThreadCount() && !canceled; c++ )
142 {
143 monitor.setProgress( 1, "Creating Virtual User " + (c+1) );
144 testCases.add( createTestCase() );
145 }
146
147 threadsWaitingToStart = testCases.size();
148 int cnt = 0;
149 while( !testCases.isEmpty() && !canceled )
150 {
151 if( startDelay > 0 )
152 {
153 try
154 {
155 Thread.sleep( startDelay );
156 }
157 catch( InterruptedException e )
158 {
159 SoapUI.logError( e );
160 }
161 }
162
163 if( status != Status.RUNNING || getProgress() >= 1 ||
164 (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT &&
165 runners.size() >= loadTest.getTestLimit() ))
166 break;
167
168
169 if( !testCases.isEmpty() )
170 {
171 startTestCase( testCases.remove( 0 ));
172 monitor.setProgress( 1, "Started thread " + (++cnt) );
173 threadsWaitingToStart--;
174 }
175 }
176
177 return null;
178 }
179
180 public boolean onCancel()
181 {
182 cancel( "Stopped from UI during start-up" );
183 canceled = true;
184 while( !testCases.isEmpty() )
185 testCases.remove( 0 ).release();
186
187 return false;
188 }} );
189
190 }
191 catch( Exception e )
192 {
193 SoapUI.logError( e );
194 }
195 }
196 else
197 {
198 List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
199 for( int c = 0; c < loadTest.getThreadCount(); c++ )
200 testCases.add( createTestCase() );
201
202 for( int c = 0; c < loadTest.getThreadCount(); c++ )
203 {
204 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT &&
205 runners.size() >= loadTest.getTestLimit() )
206 break;
207
208 startTestCase( testCases.get( c ) );
209 }
210 }
211
212 if( listeners != null )
213 {
214 if( status == Status.RUNNING )
215 {
216 for( LoadTestRunListener listener : listeners )
217 {
218 listener.loadTestStarted( this, context );
219 }
220
221 startStrategyThread();
222 }
223 else
224 {
225 for( LoadTestRunListener listener : listeners )
226 {
227 listener.afterLoadTest( this, context );
228 }
229
230 tearDown();
231 }
232 }
233 }
234
235 private void startStrategyThread()
236 {
237 new Thread( new Runnable() {
238
239 public void run()
240 {
241 while( getStatus() == Status.RUNNING )
242 {
243 try
244 {
245 loadTest.getLoadStrategy().recalculate(WsdlLoadTestRunner.this, context);
246
247 long strategyInterval = loadTest.getStrategyInterval();
248 if( strategyInterval < 1 )
249 strategyInterval = WsdlLoadTest.DEFAULT_STRATEGY_INTERVAL;
250
251 Thread.sleep( strategyInterval );
252 }
253 catch (InterruptedException e)
254 {
255 e.printStackTrace();
256 }
257 }
258 }} ).start();
259 }
260
261 private TestCaseRunner startTestCase( WsdlTestCase testCase )
262 {
263 TestCaseRunner testCaseRunner = new TestCaseRunner( testCase, threadCount++ );
264 Thread thread = new Thread( threadGroup, testCaseRunner,
265 testCase.getName() + " - " + loadTest.getName() + " - ThreadIndex " + testCaseRunner.threadIndex );
266 thread.start();
267 runners.add( testCaseRunner );
268 return testCaseRunner;
269 }
270
271 public synchronized void cancel( String reason )
272 {
273 if( status != Status.RUNNING )
274 return;
275
276 this.reason = reason;
277 status = Status.CANCELED;
278
279 TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
280
281 for( TestCaseRunner runner : r )
282 {
283 runner.cancel( reason, true );
284 }
285
286 String msg = "LoadTest [" + loadTest.getName()+ "] canceled";
287 if( reason != null )
288 msg += "; " + reason;
289
290 loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
291
292 for( LoadTestRunListener listener : listeners )
293 {
294 listener.loadTestStopped( this, context );
295 }
296
297 tearDown();
298 }
299
300 public synchronized void fail( String reason )
301 {
302 if( status != Status.RUNNING )
303 return;
304
305 this.reason = reason;
306 status = Status.FAILED;
307
308 String msg = "LoadTest [" + loadTest.getName()+ "] failed";
309 if( reason != null )
310 msg += "; " + reason;
311
312 loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
313
314 for( LoadTestRunListener listener : listeners )
315 {
316 listener.loadTestStopped( this, context );
317 }
318
319 TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
320
321 for( TestCaseRunner runner : r )
322 {
323 runner.cancel( reason, true );
324 }
325
326 tearDown();
327 }
328
329 private synchronized void tearDown()
330 {
331 if( hasTearedDown )
332 return;
333
334 try
335 {
336 loadTest.runTearDownScript(context, this);
337
338 }
339 catch (Exception e1)
340 {
341 SoapUI.logError( e1 );
342 };
343
344 hasTearedDown = true;
345 }
346
347 public void waitUntilFinished()
348 {
349 while( runners.size() > 0 || threadsWaitingToStart > 0 )
350 {
351 try
352 {
353 Thread.sleep( 200 );
354 }
355 catch (InterruptedException e)
356 {
357 SoapUI.logError( e );
358 }
359 }
360 }
361
362 public void finishTestCase( String reason, WsdlTestCase testCase )
363 {
364 for( TestCaseRunner runner : runners )
365 {
366 if( runner.getTestCase() == testCase )
367 {
368 runner.cancel( reason, false );
369 break;
370 }
371 }
372 }
373
374 public synchronized void finishRunner( TestCaseRunner runner )
375 {
376 if( !runners.contains( runner ))
377 {
378 throw new RuntimeException( "Trying to finish unknown runner.. " );
379 }
380
381 runners.remove( runner );
382 if( runners.size() == 0 && threadsWaitingToStart == 0 )
383 {
384 loadTest.removePropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY,
385 internalPropertyChangeListener );
386
387 if( status == Status.RUNNING )
388 status = Status.FINISHED;
389
390 loadTest.getLoadTestLog().addEntry(
391 new LoadTestLogMessageEntry( "LoadTest ended at " + new Date( System.currentTimeMillis() ) ));
392
393 for( LoadTestRunListener listener : listeners )
394 {
395 listener.afterLoadTest( this, context );
396 }
397
398 tearDown();
399
400 listeners = null;
401 context.clear();
402 }
403 }
404
405 public int getRunningThreadCount()
406 {
407 return runners.size();
408 }
409
410 public float getProgress()
411 {
412 long testLimit = loadTest.getTestLimit();
413 if( testLimit == 0 )
414 return -1;
415
416 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
417 return (float)runCount / (float)testLimit;
418
419 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
420 return (float)getTimeTaken() / (float)(testLimit*1000);
421
422 return -1;
423 }
424
425 private synchronized boolean afterRun( TestCaseRunner runner )
426 {
427 if( status != Status.RUNNING )
428 return false;
429
430 runCount++;
431
432 if( loadTest.getTestLimit() < 1 ) return true;
433
434 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
435 return runCount + runners.size() + threadsWaitingToStart <= loadTest.getTestLimit();
436
437 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
438 return getTimeTaken() < loadTest.getTestLimit()*1000;
439
440 return true;
441 }
442
443 public class TestCaseRunner implements Runnable
444 {
445 private final WsdlTestCase testCase;
446 private boolean canceled;
447 private long runCount;
448 private WsdlTestCaseRunner runner;
449 private final int threadIndex;
450
451 public TestCaseRunner(WsdlTestCase testCase, int threadIndex )
452 {
453 this.testCase = testCase;
454 this.threadIndex = threadIndex;
455 }
456
457 public void run()
458 {
459 try
460 {
461 runner = new WsdlTestCaseRunner( testCase, new StringToObjectMap() );
462
463 while( !canceled )
464 {
465 try
466 {
467 runner.getRunContext().reset();
468 runner.getRunContext().setProperty( TestRunContext.THREAD_INDEX, threadIndex );
469 runner.getRunContext().setProperty( TestRunContext.RUN_COUNT, runCount );
470 runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_RUNNER, WsdlLoadTestRunner.this );
471 runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_CONTEXT, context );
472
473 runner.run();
474 }
475 catch( Throwable e )
476 {
477 System.err.println( "Error running testcase: " + e );
478 SoapUI.logError( e );
479 }
480
481 runCount++;
482
483 if( !afterRun( this ) )
484 break;
485 }
486 }
487 finally
488 {
489 finishRunner( this );
490 testCase.release();
491 testCase.removeTestRunListener( testRunListener );
492 }
493 }
494
495 public void cancel( String reason, boolean cancelRunner )
496 {
497 if( runner != null && cancelRunner )
498 runner.cancel( reason );
499
500 this.canceled = true;
501 }
502
503 public boolean isCanceled()
504 {
505 return canceled;
506 }
507
508 public long getRunCount()
509 {
510 return runCount;
511 }
512
513 public WsdlTestCase getTestCase()
514 {
515 return testCase;
516 }
517 }
518
519 public LoadTest getLoadTest()
520 {
521 return loadTest;
522 }
523
524 public class InternalPropertyChangeListener implements PropertyChangeListener
525 {
526 public void propertyChange(PropertyChangeEvent evt)
527 {
528 updateThreadCount();
529 }
530 }
531
532 public synchronized void updateThreadCount()
533 {
534 if( status != Status.RUNNING ) return;
535
536 long newCount = loadTest.getThreadCount();
537
538
539 Iterator<TestCaseRunner> iterator = runners.iterator();
540 List<TestCaseRunner> activeRunners = new ArrayList<TestCaseRunner>();
541 while( iterator.hasNext() )
542 {
543 TestCaseRunner runner = iterator.next();
544 if( !runner.isCanceled() )
545 activeRunners.add( runner );
546 }
547
548 long diff = newCount-activeRunners.size();
549
550 if( diff == 0 )
551 return;
552
553
554 if( diff < 0 && loadTest.getCancelExcessiveThreads())
555 {
556 diff = Math.abs( diff );
557 for( int c = 0; c < diff && c < activeRunners.size(); c++ )
558 {
559 activeRunners.get( c ).cancel( "excessive thread", false );
560 }
561 }
562
563 else if( diff > 0 )
564 {
565 for( int c = 0; c < diff; c++ )
566 {
567 int startDelay = loadTest.getStartDelay();
568 if( startDelay > 0 )
569 {
570 try
571 {
572 Thread.sleep( startDelay );
573 }
574 catch( InterruptedException e )
575 {
576 SoapUI.logError( e );
577 }
578 }
579
580 if( status == Status.RUNNING )
581 startTestCase( createTestCase() );
582 }
583 }
584 }
585
586 /***
587 * Creates a copy of the underlying WsdlTestCase with all LoadTests removed and configured for LoadTesting
588 */
589
590 private WsdlTestCase createTestCase()
591 {
592 WsdlTestCase testCase = loadTest.getTestCase();
593
594
595 TestCaseConfig config = (TestCaseConfig) testCase.getConfig().copy();
596 config.setLoadTestArray( new LoadTestConfig[0] );
597
598
599 WsdlTestCase tc = new WsdlTestCase( testCase.getTestSuite(), config, true );
600 tc.afterLoad();
601 tc.addTestRunListener( testRunListener );
602 Settings settings = tc.getSettings();
603 settings.setBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN ));
604 settings.setBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN ));
605 settings.setBoolean( HttpSettings.CLOSE_CONNECTIONS, loadTest.getSettings().getBoolean( HttpSettings.CLOSE_CONNECTIONS ));
606
607
608 settings.setBoolean( WsdlSettings.PRETTY_PRINT_RESPONSE_MESSAGES, false );
609
610
611 tc.setDiscardOkResults( false );
612 return tc;
613 }
614
615 public String getReason()
616 {
617 return reason;
618 }
619
620 public long getTimeTaken()
621 {
622 return System.currentTimeMillis()-startTime;
623 }
624
625 private class InternalTestRunListener implements TestRunListener
626 {
627 public void beforeRun(TestRunner testRunner, TestRunContext runContext)
628 {
629 if( getProgress() > 1 && loadTest.getCancelOnReachedLimit() )
630 {
631 testRunner.cancel( "LoadTest Limit reached" );
632 }
633 else for( LoadTestRunListener listener : listeners )
634 {
635 listener.beforeTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
636 }
637 }
638
639 public void beforeStep(TestRunner testRunner, TestRunContext runContext)
640 {
641 if( getProgress() > 1 && loadTest.getCancelOnReachedLimit() )
642 {
643 testRunner.cancel( "LoadTest Limit reached" );
644 }
645 else for( LoadTestRunListener listener : listeners )
646 {
647 listener.beforeTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, runContext.getCurrentStep() );
648 }
649 }
650
651 public void afterStep(TestRunner testRunner, TestRunContext runContext, TestStepResult result)
652 {
653 for( LoadTestRunListener listener : listeners )
654 {
655 listener.afterTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, result );
656 }
657 }
658
659 public void afterRun(TestRunner testRunner, TestRunContext runContext)
660 {
661 for( LoadTestRunListener listener : listeners )
662 {
663 listener.afterTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
664 }
665 }
666 }
667 }