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