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.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 							// could have been canceled..
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 		// get list of active runners
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 		// cancel runners if thread count has been decreased
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 		// start new runners if thread count has been increased
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 		// clone config and remove and loadtests
491 		TestCaseConfig config = (TestCaseConfig) testCase.getConfig().copy();
492 		config.setLoadTestArray( new LoadTestConfig[0] );
493 		
494 		//	 clone entire testCase
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 		// dont discard.. the WsdlLoadTests internal listener will discard after asserting..
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 }