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