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