View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2007 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
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 							// could have been canceled..
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 		// get list of active runners
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 		// cancel runners if thread count has been decreased
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 		// start new runners if thread count has been increased
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 		// clone config and remove and loadtests
520 		TestCaseConfig config = (TestCaseConfig) testCase.getConfig().copy();
521 		config.setLoadTestArray( new LoadTestConfig[0] );
522 		
523 		//	 clone entire testCase
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 		// dont discard.. the WsdlLoadTests internal listener will discard after asserting..
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 }