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