View Javadoc

1   /*
2    *  soapUI, copyright (C) 2006 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of the GNU Lesser General Public License as published by the Free Software Foundation; 
6    *  either version 2.1 of the License, or (at your option) any later version.
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 		for( LoadTestRunListener listener : listeners )
210 		{
211 			listener.loadTestStopped( this, context );
212 		}
213 		
214 		TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
215 		
216 		for( TestCaseRunner runner : r )
217 		{
218 			runner.cancel( reason, true );
219 		}
220 		
221 		String msg = "LoadTest [" + loadTest.getName()+ "] canceled";
222 		if( reason != null )
223 			msg += "; " + reason;
224 		
225 		loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg )); 
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 		for( LoadTestRunListener listener : listeners )
237 		{
238 			listener.loadTestStopped( this, context );
239 		}
240 		
241 		TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
242 		
243 		for( TestCaseRunner runner : r )
244 		{
245 			runner.cancel( reason, true );
246 		}
247 		
248 		String msg = "LoadTest [" + loadTest.getName()+ "] failed";
249 		if( reason != null )
250 			msg += "; " + reason;
251 		
252 		loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg )); 
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( loadTest.getTestLimit() < 1 ) return true;
332 		
333 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
334 			return runCount++ + runners.size() < loadTest.getTestLimit();
335 			
336 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
337 			return getTimeTaken() < loadTest.getTestLimit()*1000;
338 			
339 		return true;
340 	}
341 	
342 	public class TestCaseRunner implements Runnable
343 	{
344 		private final WsdlTestCase testCase;
345 		private boolean canceled;
346 		private long runCount;
347 		private WsdlTestCaseRunner runner;
348 		private final int threadIndex;
349 
350 		public TestCaseRunner(WsdlTestCase testCase, int threadIndex )
351 		{
352 			this.testCase = testCase;
353 			this.threadIndex = threadIndex;
354 		}
355 
356 		public void run()
357 		{
358 			runner = new WsdlTestCaseRunner( testCase, PropertiesMap.EMPTY_MAP );
359 			
360 			while( !canceled )
361 			{
362 				try
363 				{
364 					runner.getRunContext().reset();
365 					runner.getRunContext().setProperty( TestRunContext.THREAD_INDEX, threadIndex );
366 					runner.getRunContext().setProperty( TestRunContext.RUN_COUNT, runCount );
367 					runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_RUNNER, WsdlLoadTestRunner.this );
368 					runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_CONTEXT, context );
369 
370 					runner.run();
371 				}
372 				catch( Throwable e )
373 				{
374 					System.err.println( "Error running testcase: " + e );
375 					e.printStackTrace();
376 				}
377 				
378 				runCount++;
379 				
380 				if( !afterRun( this ) )
381 					break;
382 			}
383 			
384 			finishRunner( this );
385 			testCase.release();
386 		}
387 
388 		public void cancel( String reason, boolean cancelRunner )
389 		{
390 			if( runner != null && cancelRunner )
391 				runner.cancel( reason );
392 			
393 			this.canceled = true;
394 		}
395 
396 		public boolean isCanceled()
397 		{
398 			return canceled;
399 		}
400 		
401 		public long getRunCount()
402 		{
403 			return runCount;
404 		}
405 
406 		public WsdlTestCase getTestCase()
407 		{
408 			return testCase;
409 		}
410 	}
411 
412 	public LoadTest getLoadTest()
413 	{
414 		return loadTest;
415 	}
416 
417 	public class InternalPropertyChangeListener implements PropertyChangeListener
418 	{
419 		public void propertyChange(PropertyChangeEvent evt)
420 		{
421 			updateThreadCount();
422 		}
423 	}
424 
425 	public synchronized void updateThreadCount()
426 	{
427 		if( status != Status.RUNNING ) return;
428 		
429 		long newCount = loadTest.getThreadCount();
430 		
431 		// get list of active runners
432 		Iterator<TestCaseRunner> iterator = runners.iterator();
433 		List<TestCaseRunner> activeRunners = new ArrayList<TestCaseRunner>();
434 		while( iterator.hasNext() )
435 		{
436 			TestCaseRunner runner = iterator.next();
437 			if( !runner.isCanceled() )
438 				activeRunners.add( runner );
439 		}
440 		
441 		long diff = newCount-activeRunners.size();
442 		
443 		if( diff == 0 )
444 			return;	
445 		
446 		// cancel runners if thread count has been decreased
447 		if( diff < 0 )
448 		{
449 			diff = Math.abs( diff );
450 			for( int c = 0; c < diff && c < activeRunners.size(); c++ )
451 			{
452 				activeRunners.get( c ).cancel( "excessive thread", false );
453 			}
454 		}
455 		// start new runners if thread count has been increased
456 		else if( diff > 0 )
457 		{
458 			for( int c = 0; c < diff; c++ )
459 			{
460 				int startDelay = loadTest.getStartDelay();
461 				if( startDelay > 0 )
462 				{
463 					try
464 					{
465 						Thread.sleep( startDelay );
466 					}
467 					catch( InterruptedException e )
468 					{
469 						e.printStackTrace();
470 					}
471 				}
472 				
473 				if( status == Status.RUNNING ) 
474 					startTestCase( createTestCase() );
475 			}
476 		}
477 	}
478 	
479 	private WsdlTestCase createTestCase()
480 	{
481 		WsdlTestCase testCase = loadTest.getTestCase();
482 
483 		// clone config and remove and loadtests
484 		TestCaseConfig config = (TestCaseConfig) testCase.getConfig().copy();
485 		config.setLoadTestArray( new LoadTestConfig[0] );
486 		
487 		//	 clone entire testCase
488 		WsdlTestCase tc = new WsdlTestCase( testCase.getTestSuite(), config );
489 		tc.addTestRunListener( testRunListener );
490 		Settings settings = tc.getSettings();
491 		settings.setBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN ));
492 		settings.setBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN ));
493 		settings.setBoolean( HttpSettings.CLOSE_CONNECTIONS, loadTest.getSettings().getBoolean( HttpSettings.CLOSE_CONNECTIONS ));
494 		
495 		// dont discard.. the WsdlLoadTests internal listener will discard after asserting..
496 		tc.setDiscardOkResults( false );
497 		return tc;
498 	}
499 
500 	public String getReason()
501 	{
502 		return reason;
503 	}
504 
505 	public long getTimeTaken()
506 	{
507 		return System.currentTimeMillis()-startTime;
508 	}
509 	
510 	private class InternalTestRunListener implements TestRunListener
511 	{
512 		public void beforeRun(TestRunner testRunner, TestRunContext runContext)
513 		{
514 			for( LoadTestRunListener listener : listeners )
515 			{
516 				listener.beforeTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
517 			}
518 		}
519 
520 		public void beforeStep(TestRunner testRunner, TestRunContext runContext)
521 		{
522 			for( LoadTestRunListener listener : listeners )
523 			{
524 				listener.beforeTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, runContext.getCurrentStep() );
525 			}
526 		}
527 
528 		public void afterStep(TestRunner testRunner, TestRunContext runContext, TestStepResult result)
529 		{
530 			for( LoadTestRunListener listener : listeners )
531 			{
532 				listener.afterTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, result );
533 			}
534 		}
535 
536 		public void afterRun(TestRunner testRunner, TestRunContext runContext)
537 		{
538 			for( LoadTestRunListener listener : listeners )
539 			{
540 				listener.afterTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
541 			}
542 		}
543 	}
544 }