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