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