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( status == Status.RUNNING )
195 {
196 for( LoadTestRunListener listener : listeners )
197 {
198 listener.loadTestStarted( this, context );
199 }
200 }
201 else
202 {
203 for( LoadTestRunListener listener : listeners )
204 {
205 listener.afterLoadTest( this, context );
206 }
207 }
208 }
209
210 private TestCaseRunner startTestCase( WsdlTestCase testCase )
211 {
212 TestCaseRunner testCaseRunner = new TestCaseRunner( testCase, threadCount++ );
213 Thread thread = new Thread( threadGroup, testCaseRunner,
214 testCase.getName() + " - " + loadTest.getName() + " - ThreadIndex " + testCaseRunner.threadIndex );
215 thread.start();
216 runners.add( testCaseRunner );
217 return testCaseRunner;
218 }
219
220 public synchronized void cancel( String reason )
221 {
222 if( status != Status.RUNNING )
223 return;
224
225 this.reason = reason;
226 status = Status.CANCELED;
227
228 TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
229
230 for( TestCaseRunner runner : r )
231 {
232 runner.cancel( reason, true );
233 }
234
235 String msg = "LoadTest [" + loadTest.getName()+ "] canceled";
236 if( reason != null )
237 msg += "; " + reason;
238
239 loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
240
241 for( LoadTestRunListener listener : listeners )
242 {
243 listener.loadTestStopped( this, context );
244 }
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 String msg = "LoadTest [" + loadTest.getName()+ "] failed";
256 if( reason != null )
257 msg += "; " + reason;
258
259 loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
260
261 for( LoadTestRunListener listener : listeners )
262 {
263 listener.loadTestStopped( this, context );
264 }
265
266 TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
267
268 for( TestCaseRunner runner : r )
269 {
270 runner.cancel( reason, true );
271 }
272 }
273
274 public void waitUntilFinished()
275 {
276 while( runners.size() > 0 || threadsWaitingToStart > 0 )
277 {
278 try
279 {
280 Thread.sleep( 200 );
281 }
282 catch (InterruptedException e)
283 {
284 SoapUI.logError( e );
285 }
286 }
287 }
288
289 public void finishTestCase( String reason, WsdlTestCase testCase )
290 {
291 for( TestCaseRunner runner : runners )
292 {
293 if( runner.getTestCase() == testCase )
294 {
295 runner.cancel( reason, false );
296 break;
297 }
298 }
299 }
300
301 public synchronized void finishRunner( TestCaseRunner runner )
302 {
303 if( !runners.contains( runner ))
304 {
305 throw new RuntimeException( "Trying to finish unknown runner.. " );
306 }
307
308 runners.remove( runner );
309 if( runners.size() == 0 && threadsWaitingToStart == 0 )
310 {
311 loadTest.removePropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY,
312 internalPropertyChangeListener );
313
314 if( status == Status.RUNNING )
315 status = Status.FINISHED;
316
317 loadTest.getLoadTestLog().addEntry(
318 new LoadTestLogMessageEntry( "LoadTest ended at " + new Date( System.currentTimeMillis() ) ));
319
320 for( LoadTestRunListener listener : listeners )
321 {
322 listener.afterLoadTest( this, context );
323 }
324
325 listeners = null;
326 context.clear();
327 }
328 }
329
330 public int getRunningThreadCount()
331 {
332 return runners.size();
333 }
334
335 public float getProgress()
336 {
337 long testLimit = loadTest.getTestLimit();
338 if( testLimit == 0 )
339 return -1;
340
341 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
342 return (float)runCount / (float)testLimit;
343
344 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
345 return (float)getTimeTaken() / (float)(testLimit*1000);
346
347 return -1;
348 }
349
350 private synchronized boolean afterRun( TestCaseRunner runner )
351 {
352 if( status != Status.RUNNING )
353 return false;
354
355 runCount++;
356
357 if( loadTest.getTestLimit() < 1 ) return true;
358
359 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
360 return runCount + runners.size() + threadsWaitingToStart <= loadTest.getTestLimit();
361
362 if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
363 return getTimeTaken() < loadTest.getTestLimit()*1000;
364
365 return true;
366 }
367
368 public class TestCaseRunner implements Runnable
369 {
370 private final WsdlTestCase testCase;
371 private boolean canceled;
372 private long runCount;
373 private WsdlTestCaseRunner runner;
374 private final int threadIndex;
375
376 public TestCaseRunner(WsdlTestCase testCase, int threadIndex )
377 {
378 this.testCase = testCase;
379 this.threadIndex = threadIndex;
380 }
381
382 public void run()
383 {
384 try
385 {
386 runner = new WsdlTestCaseRunner( testCase, new StringToObjectMap() );
387
388 while( !canceled )
389 {
390 try
391 {
392 runner.getRunContext().reset();
393 runner.getRunContext().setProperty( TestRunContext.THREAD_INDEX, threadIndex );
394 runner.getRunContext().setProperty( TestRunContext.RUN_COUNT, runCount );
395 runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_RUNNER, WsdlLoadTestRunner.this );
396 runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_CONTEXT, context );
397
398 runner.run();
399 }
400 catch( Throwable e )
401 {
402 System.err.println( "Error running testcase: " + e );
403 SoapUI.logError( e );
404 }
405
406 runCount++;
407
408 if( !afterRun( this ) )
409 break;
410 }
411 }
412 finally
413 {
414 finishRunner( this );
415 testCase.release();
416 testCase.removeTestRunListener( testRunListener );
417 }
418 }
419
420 public void cancel( String reason, boolean cancelRunner )
421 {
422 if( runner != null && cancelRunner )
423 runner.cancel( reason );
424
425 this.canceled = true;
426 }
427
428 public boolean isCanceled()
429 {
430 return canceled;
431 }
432
433 public long getRunCount()
434 {
435 return runCount;
436 }
437
438 public WsdlTestCase getTestCase()
439 {
440 return testCase;
441 }
442 }
443
444 public LoadTest getLoadTest()
445 {
446 return loadTest;
447 }
448
449 public class InternalPropertyChangeListener implements PropertyChangeListener
450 {
451 public void propertyChange(PropertyChangeEvent evt)
452 {
453 updateThreadCount();
454 }
455 }
456
457 public synchronized void updateThreadCount()
458 {
459 if( status != Status.RUNNING ) return;
460
461 long newCount = loadTest.getThreadCount();
462
463
464 Iterator<TestCaseRunner> iterator = runners.iterator();
465 List<TestCaseRunner> activeRunners = new ArrayList<TestCaseRunner>();
466 while( iterator.hasNext() )
467 {
468 TestCaseRunner runner = iterator.next();
469 if( !runner.isCanceled() )
470 activeRunners.add( runner );
471 }
472
473 long diff = newCount-activeRunners.size();
474
475 if( diff == 0 )
476 return;
477
478
479 if( diff < 0 )
480 {
481 diff = Math.abs( diff );
482 for( int c = 0; c < diff && c < activeRunners.size(); c++ )
483 {
484 activeRunners.get( c ).cancel( "excessive thread", false );
485 }
486 }
487
488 else if( diff > 0 )
489 {
490 for( int c = 0; c < diff; c++ )
491 {
492 int startDelay = loadTest.getStartDelay();
493 if( startDelay > 0 )
494 {
495 try
496 {
497 Thread.sleep( startDelay );
498 }
499 catch( InterruptedException e )
500 {
501 SoapUI.logError( e );
502 }
503 }
504
505 if( status == Status.RUNNING )
506 startTestCase( createTestCase() );
507 }
508 }
509 }
510
511 /***
512 * Creates a copy of the underlying WsdlTestCase with all LoadTests removed and configured for LoadTesting
513 */
514
515 private WsdlTestCase createTestCase()
516 {
517 WsdlTestCase testCase = loadTest.getTestCase();
518
519
520 TestCaseConfig config = (TestCaseConfig) testCase.getConfig().copy();
521 config.setLoadTestArray( new LoadTestConfig[0] );
522
523
524 WsdlTestCase tc = new WsdlTestCase( testCase.getTestSuite(), config, true );
525 tc.addTestRunListener( testRunListener );
526 Settings settings = tc.getSettings();
527 settings.setBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN ));
528 settings.setBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN ));
529 settings.setBoolean( HttpSettings.CLOSE_CONNECTIONS, loadTest.getSettings().getBoolean( HttpSettings.CLOSE_CONNECTIONS ));
530
531
532 tc.setDiscardOkResults( false );
533 return tc;
534 }
535
536 public String getReason()
537 {
538 return reason;
539 }
540
541 public long getTimeTaken()
542 {
543 return System.currentTimeMillis()-startTime;
544 }
545
546 private class InternalTestRunListener implements TestRunListener
547 {
548 public void beforeRun(TestRunner testRunner, TestRunContext runContext)
549 {
550 if( getProgress() > 1 && loadTest.getCancelOnReachedLimit() )
551 {
552 testRunner.cancel( "LoadTest Limit reached" );
553 }
554 else for( LoadTestRunListener listener : listeners )
555 {
556 listener.beforeTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
557 }
558 }
559
560 public void beforeStep(TestRunner testRunner, TestRunContext runContext)
561 {
562 if( getProgress() > 1 && loadTest.getCancelOnReachedLimit() )
563 {
564 testRunner.cancel( "LoadTest Limit reached" );
565 }
566 else for( LoadTestRunListener listener : listeners )
567 {
568 listener.beforeTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, runContext.getCurrentStep() );
569 }
570 }
571
572 public void afterStep(TestRunner testRunner, TestRunContext runContext, TestStepResult result)
573 {
574 for( LoadTestRunListener listener : listeners )
575 {
576 listener.afterTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, result );
577 }
578 }
579
580 public void afterRun(TestRunner testRunner, TestRunContext runContext)
581 {
582 for( LoadTestRunListener listener : listeners )
583 {
584 listener.afterTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
585 }
586 }
587 }
588 }