View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2009 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.support.TestRunListenerAdapter;
33  import com.eviware.soapui.model.testsuite.LoadTestRunListener;
34  import com.eviware.soapui.model.testsuite.LoadTestRunner;
35  import com.eviware.soapui.model.testsuite.TestCaseRunContext;
36  import com.eviware.soapui.model.testsuite.TestCaseRunner;
37  import com.eviware.soapui.model.testsuite.TestRunContext;
38  import com.eviware.soapui.model.testsuite.TestRunnable;
39  import com.eviware.soapui.model.testsuite.TestStep;
40  import com.eviware.soapui.model.testsuite.TestStepResult;
41  import com.eviware.soapui.settings.HttpSettings;
42  import com.eviware.soapui.settings.WsdlSettings;
43  import com.eviware.soapui.support.UISupport;
44  import com.eviware.soapui.support.types.StringToObjectMap;
45  import com.eviware.x.dialogs.Worker;
46  import com.eviware.x.dialogs.XProgressDialog;
47  import com.eviware.x.dialogs.XProgressMonitor;
48  
49  /***
50   * TestRunner for load-tests.
51   * 
52   * @author Ole.Matzura
53   * @todo statistics should be calculated first after all threads have been
54   *       started..
55   */
56  
57  public class WsdlLoadTestRunner implements LoadTestRunner
58  {
59  	private final WsdlLoadTest loadTest;
60  	private Set<InternalTestCaseRunner> runners = new HashSet<InternalTestCaseRunner>();
61  	private long startTime = 0;
62  	private InternalPropertyChangeListener internalPropertyChangeListener = new InternalPropertyChangeListener();
63  	private InternalTestRunListener testRunListener = new InternalTestRunListener();
64  	private long runCount;
65  	private Status status;
66  	private WsdlLoadTestContext context;
67  	private String reason;
68  	private int threadCount;
69  	private int threadsWaitingToStart;
70  	private int startedCount;
71  	private boolean hasTearedDown;
72  	private TestCaseStarter testCaseStarter;
73  
74  	public WsdlLoadTestRunner( WsdlLoadTest test )
75  	{
76  		this.loadTest = test;
77  		status = Status.INITIALIZED;
78  	}
79  
80  	public Status getStatus()
81  	{
82  		return status;
83  	}
84  
85  	void start()
86  	{
87  		loadTest.getTestCase().beforeSave();
88  		startTime = System.currentTimeMillis();
89  
90  		runners.clear();
91  		runCount = 0;
92  		threadCount = 0;
93  		threadsWaitingToStart = 0;
94  		startedCount = 0;
95  		context = new WsdlLoadTestContext( this );
96  
97  		try
98  		{
99  			loadTest.runSetupScript( context, this );
100 		}
101 		catch( Exception e1 )
102 		{
103 			SoapUI.logError( e1 );
104 		}
105 
106 		status = Status.RUNNING;
107 
108 		for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
109 		{
110 			try
111 			{
112 				listener.beforeLoadTest( this, context );
113 			}
114 			catch( Throwable e )
115 			{
116 				SoapUI.logError( e );
117 			}
118 		}
119 
120 		loadTest.addPropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener );
121 
122 		loadTest.getLoadTestLog()
123 				.addEntry( new LoadTestLogMessageEntry( "LoadTest started at " + new Date( startTime ) ) );
124 
125 		int startDelay = loadTest.getStartDelay();
126 		if( startDelay >= 0 )
127 		{
128 			XProgressDialog progressDialog = UISupport.getDialogs().createProgressDialog( "Starting threads",
129 					( int )loadTest.getThreadCount(), "", true );
130 			try
131 			{
132 				testCaseStarter = new TestCaseStarter();
133 				progressDialog.run( testCaseStarter );
134 			}
135 			catch( Exception e )
136 			{
137 				SoapUI.logError( e );
138 			}
139 		}
140 		else
141 		{
142 			List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
143 			for( int c = 0; c < loadTest.getThreadCount(); c++ )
144 				testCases.add( createTestCase() );
145 
146 			for( int c = 0; c < loadTest.getThreadCount(); c++ )
147 			{
148 				if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT && runners.size() >= loadTest.getTestLimit() )
149 					break;
150 
151 				startTestCase( testCases.get( c ) );
152 			}
153 		}
154 
155 		if( status == Status.RUNNING )
156 		{
157 			for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
158 			{
159 				listener.loadTestStarted( this, context );
160 			}
161 
162 			startStrategyThread();
163 		}
164 		else
165 		{
166 			for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
167 			{
168 				listener.afterLoadTest( this, context );
169 			}
170 
171 			tearDown();
172 		}
173 	}
174 
175 	/***
176 	 * Starts thread the calls the current strategy to recalculate
177 	 */
178 
179 	private void startStrategyThread()
180 	{
181 		new Thread( new Runnable()
182 		{
183 			public void run()
184 			{
185 				while( getStatus() == Status.RUNNING )
186 				{
187 					try
188 					{
189 						loadTest.getLoadStrategy().recalculate( WsdlLoadTestRunner.this, context );
190 
191 						long strategyInterval = loadTest.getStrategyInterval();
192 						if( strategyInterval < 1 )
193 							strategyInterval = WsdlLoadTest.DEFAULT_STRATEGY_INTERVAL;
194 
195 						Thread.sleep( strategyInterval );
196 					}
197 					catch( InterruptedException e )
198 					{
199 						e.printStackTrace();
200 					}
201 				}
202 			}
203 		} ).start();
204 	}
205 
206 	private InternalTestCaseRunner startTestCase( WsdlTestCase testCase )
207 	{
208 		InternalTestCaseRunner testCaseRunner = new InternalTestCaseRunner( testCase, threadCount++ );
209 
210 		SoapUI.getThreadPool().submit( testCaseRunner );
211 		runners.add( testCaseRunner );
212 		return testCaseRunner;
213 	}
214 
215 	public synchronized void cancel( String reason )
216 	{
217 		if( status != Status.RUNNING )
218 			return;
219 
220 		this.reason = reason;
221 		status = Status.CANCELED;
222 
223 		if( testCaseStarter != null )
224 			testCaseStarter.stop();
225 
226 		InternalTestCaseRunner[] r = runners.toArray( new InternalTestCaseRunner[runners.size()] );
227 
228 		for( InternalTestCaseRunner runner : r )
229 		{
230 			runner.cancel( reason, true );
231 		}
232 
233 		String msg = "LoadTest [" + loadTest.getName() + "] canceled";
234 		if( reason != null )
235 			msg += "; " + reason;
236 
237 		loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ) );
238 
239 		for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
240 		{
241 			listener.loadTestStopped( this, context );
242 		}
243 
244 		tearDown();
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 		if( testCaseStarter != null )
256 			testCaseStarter.stop();
257 
258 		String msg = "LoadTest [" + loadTest.getName() + "] failed";
259 		if( reason != null )
260 			msg += "; " + reason;
261 
262 		loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ) );
263 
264 		for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
265 		{
266 			listener.loadTestStopped( this, context );
267 		}
268 
269 		InternalTestCaseRunner[] r = runners.toArray( new InternalTestCaseRunner[runners.size()] );
270 
271 		for( InternalTestCaseRunner runner : r )
272 		{
273 			runner.cancel( reason, true );
274 		}
275 
276 		tearDown();
277 	}
278 
279 	private synchronized void tearDown()
280 	{
281 		if( hasTearedDown )
282 			return;
283 
284 		try
285 		{
286 			loadTest.runTearDownScript( context, this );
287 
288 		}
289 		catch( Exception e1 )
290 		{
291 			SoapUI.logError( e1 );
292 		}
293 		;
294 
295 		hasTearedDown = true;
296 	}
297 
298 	public Status waitUntilFinished()
299 	{
300 		while( runners.size() > 0 || threadsWaitingToStart > 0 )
301 		{
302 			try
303 			{
304 				Thread.sleep( 200 );
305 			}
306 			catch( InterruptedException e )
307 			{
308 				SoapUI.logError( e );
309 			}
310 		}
311 
312 		return getStatus();
313 	}
314 
315 	public void finishTestCase( String reason, WsdlTestCase testCase )
316 	{
317 		for( InternalTestCaseRunner runner : runners )
318 		{
319 			if( runner.getTestCase() == testCase )
320 			{
321 				runner.cancel( reason, false );
322 				break;
323 			}
324 		}
325 	}
326 
327 	public synchronized void finishRunner( InternalTestCaseRunner runner )
328 	{
329 		if( !runners.contains( runner ) )
330 		{
331 			throw new RuntimeException( "Trying to finish unknown runner.. " );
332 		}
333 
334 		runners.remove( runner );
335 		if( runners.size() == 0 && ( getProgress() >= 1 || status != Status.RUNNING ) )
336 		{
337 			loadTest.removePropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener );
338 
339 			if( testCaseStarter != null )
340 				testCaseStarter.stop();
341 
342 			if( status == Status.RUNNING )
343 				status = Status.FINISHED;
344 
345 			loadTest.getLoadTestLog().addEntry(
346 					new LoadTestLogMessageEntry( "LoadTest ended at " + new Date( System.currentTimeMillis() ) ) );
347 
348 			for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
349 			{
350 				listener.afterLoadTest( this, context );
351 			}
352 
353 			tearDown();
354 
355 			context.clear();
356 		}
357 	}
358 
359 	public int getRunningThreadCount()
360 	{
361 		return runners.size();
362 	}
363 
364 	public float getProgress()
365 	{
366 		long testLimit = loadTest.getTestLimit();
367 		if( testLimit == 0 )
368 			return -1;
369 
370 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
371 			return ( float )runCount / ( float )testLimit;
372 
373 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT_PER_THREAD )
374 			return ( float )runCount / ( float )testLimit;
375 
376 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
377 			return ( float )getTimeTaken() / ( float )( testLimit * 1000 );
378 
379 		return -1;
380 	}
381 
382 	private synchronized boolean afterRun( InternalTestCaseRunner runner )
383 	{
384 		if( status != Status.RUNNING )
385 			return false;
386 
387 		runCount++ ;
388 
389 		if( loadTest.getTestLimit() < 1 )
390 			return true;
391 
392 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT_PER_THREAD )
393 			return runner.getRunCount() < loadTest.getTestLimit();
394 
395 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
396 			return runCount + runners.size() + threadsWaitingToStart <= loadTest.getTestLimit();
397 
398 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
399 			return getTimeTaken() < loadTest.getTestLimit() * 1000;
400 
401 		return true;
402 	}
403 
404 	private final class TestCaseStarter extends Worker.WorkerAdapter
405 	{
406 		private List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
407 		private boolean canceled;
408 
409 		public Object construct( XProgressMonitor monitor )
410 		{
411 			int startDelay = loadTest.getStartDelay();
412 
413 			for( int c = 0; c < loadTest.getThreadCount() && !canceled; c++ )
414 			{
415 				monitor.setProgress( 1, "Creating Virtual User " + ( c + 1 ) );
416 				testCases.add( createTestCase() );
417 			}
418 
419 			threadsWaitingToStart = testCases.size();
420 			int cnt = 0;
421 			while( !testCases.isEmpty() && !canceled )
422 			{
423 				if( startDelay > 0 )
424 				{
425 					try
426 					{
427 						Thread.sleep( startDelay );
428 					}
429 					catch( InterruptedException e )
430 					{
431 						SoapUI.logError( e );
432 					}
433 				}
434 
435 				if( status != Status.RUNNING
436 						|| getProgress() >= 1 )
437 						//|| ( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT && runners.size() >= loadTest
438 						//		.getTestLimit() ) )
439 				{
440 					while( !testCases.isEmpty() )
441 						testCases.remove( 0 ).release();
442 
443 					threadsWaitingToStart = 0;
444 					break;
445 				}
446 
447 				// could have been canceled..
448 				if( !testCases.isEmpty() )
449 				{
450 					startTestCase( testCases.remove( 0 ) );
451 					monitor.setProgress( 1, "Started thread " + ( ++cnt ) );
452 					threadsWaitingToStart-- ;
453 				}
454 			}
455 
456 			return null;
457 		}
458 
459 		public boolean onCancel()
460 		{
461 			cancel( "Stopped from UI during start-up" );
462 			stop();
463 
464 			return false;
465 		}
466 
467 		public void stop()
468 		{
469 			if( !canceled )
470 			{
471 				canceled = true;
472 				while( !testCases.isEmpty() )
473 					testCases.remove( 0 ).release();
474 
475 				threadsWaitingToStart = 0;
476 			}
477 		}
478 	}
479 
480 	public class InternalTestCaseRunner implements Runnable
481 	{
482 		private final WsdlTestCase testCase;
483 		private boolean canceled;
484 		private long runCount;
485 		private WsdlTestCaseRunner runner;
486 		private final int threadIndex;
487 
488 		public InternalTestCaseRunner( WsdlTestCase testCase, int threadIndex )
489 		{
490 			this.testCase = testCase;
491 			this.threadIndex = threadIndex;
492 		}
493 
494 		public void run()
495 		{
496 			try
497 			{
498 				Thread.currentThread().setName(
499 						testCase.getName() + " " + loadTest.getName() + " ThreadIndex = " + threadIndex );
500 				runner = new WsdlTestCaseRunner( testCase, new StringToObjectMap() );
501 
502 				while( !canceled )
503 				{
504 					try
505 					{
506 						runner.getRunContext().reset();
507 						runner.getRunContext().setProperty( TestCaseRunContext.THREAD_INDEX, threadIndex );
508 						runner.getRunContext().setProperty( TestCaseRunContext.RUN_COUNT, runCount );
509 						runner.getRunContext().setProperty( TestCaseRunContext.LOAD_TEST_RUNNER, WsdlLoadTestRunner.this );
510 						runner.getRunContext().setProperty( TestCaseRunContext.LOAD_TEST_CONTEXT, context );
511 						synchronized( this )
512 						{
513 							runner.getRunContext().setProperty( TestCaseRunContext.TOTAL_RUN_COUNT, startedCount++ );
514 						}
515 
516 						runner.run();
517 					}
518 					catch( Throwable e )
519 					{
520 						System.err.println( "Error running testcase: " + e );
521 						SoapUI.logError( e );
522 					}
523 
524 					runCount++ ;
525 
526 					if( !afterRun( this ) )
527 						break;
528 				}
529 			}
530 			finally
531 			{
532 				finishRunner( this );
533 				testCase.release();
534 				testCase.removeTestRunListener( testRunListener );
535 			}
536 		}
537 
538 		public void cancel( String reason, boolean cancelRunner )
539 		{
540 			if( runner != null && cancelRunner )
541 				runner.cancel( reason );
542 
543 			this.canceled = true;
544 		}
545 
546 		public boolean isCanceled()
547 		{
548 			return canceled;
549 		}
550 
551 		public long getRunCount()
552 		{
553 			return runCount;
554 		}
555 
556 		public WsdlTestCase getTestCase()
557 		{
558 			return testCase;
559 		}
560 	}
561 
562 	public WsdlLoadTest getLoadTest()
563 	{
564 		return loadTest;
565 	}
566 
567 	public class InternalPropertyChangeListener implements PropertyChangeListener
568 	{
569 		public void propertyChange( PropertyChangeEvent evt )
570 		{
571 			updateThreadCount();
572 		}
573 	}
574 
575 	public synchronized void updateThreadCount()
576 	{
577 		if( status != Status.RUNNING )
578 			return;
579 
580 		long newCount = loadTest.getThreadCount();
581 
582 		// get list of active runners
583 		Iterator<InternalTestCaseRunner> iterator = runners.iterator();
584 		List<InternalTestCaseRunner> activeRunners = new ArrayList<InternalTestCaseRunner>();
585 		while( iterator.hasNext() )
586 		{
587 			InternalTestCaseRunner runner = iterator.next();
588 			if( !runner.isCanceled() )
589 				activeRunners.add( runner );
590 		}
591 
592 		long diff = newCount - activeRunners.size();
593 
594 		if( diff == 0 )
595 			return;
596 
597 		// cancel runners if thread count has been decreased
598 		if( diff < 0 && loadTest.getCancelExcessiveThreads() )
599 		{
600 			diff = Math.abs( diff );
601 			for( int c = 0; c < diff && c < activeRunners.size(); c++ )
602 			{
603 				activeRunners.get( c ).cancel( "excessive thread", false );
604 			}
605 		}
606 		// start new runners if thread count has been increased
607 		else if( diff > 0 )
608 		{
609 			for( int c = 0; c < diff; c++ )
610 			{
611 				int startDelay = loadTest.getStartDelay();
612 				if( startDelay > 0 )
613 				{
614 					try
615 					{
616 						Thread.sleep( startDelay );
617 					}
618 					catch( InterruptedException e )
619 					{
620 						SoapUI.logError( e );
621 					}
622 				}
623 
624 				if( status == Status.RUNNING )
625 					startTestCase( createTestCase() );
626 			}
627 		}
628 	}
629 
630 	/***
631 	 * Creates a copy of the underlying WsdlTestCase with all LoadTests removed
632 	 * and configured for LoadTesting
633 	 */
634 
635 	private WsdlTestCase createTestCase()
636 	{
637 		WsdlTestCase testCase = loadTest.getTestCase();
638 
639 		// clone config and remove and loadtests
640 		TestCaseConfig config = ( TestCaseConfig )testCase.getConfig().copy();
641 		config.setLoadTestArray( new LoadTestConfig[0] );
642 
643 		// clone entire testCase
644 		WsdlTestCase tc = testCase.getTestSuite().buildTestCase( config, true );
645 		tc.afterLoad();
646 		tc.addTestRunListener( testRunListener );
647 		Settings settings = tc.getSettings();
648 		settings.setBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN, loadTest.getSettings().getBoolean(
649 				HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN ) );
650 		settings.setBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN, loadTest.getSettings().getBoolean(
651 				HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN ) );
652 		settings.setBoolean( HttpSettings.CLOSE_CONNECTIONS, loadTest.getSettings().getBoolean(
653 				HttpSettings.CLOSE_CONNECTIONS ) );
654 
655 		// disable default pretty-printing since it takes time
656 		settings.setBoolean( WsdlSettings.PRETTY_PRINT_RESPONSE_MESSAGES, false );
657 
658 		// dont discard.. the WsdlLoadTests internal listener will discard after
659 		// asserting..
660 		tc.setDiscardOkResults( false );
661 		tc.setMaxResults( 0 );
662 		return tc;
663 	}
664 
665 	public String getReason()
666 	{
667 		return reason;
668 	}
669 
670 	public long getTimeTaken()
671 	{
672 		return System.currentTimeMillis() - startTime;
673 	}
674 
675 	private class InternalTestRunListener extends TestRunListenerAdapter
676 	{
677 		public void beforeRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
678 		{
679 			if( getProgress() > 1 && loadTest.getCancelOnReachedLimit() )
680 			{
681 				testRunner.cancel( "LoadTest Limit reached" );
682 			}
683 			else
684 				for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
685 				{
686 					listener.beforeTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
687 				}
688 		}
689 
690 		public void beforeStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStep testStep )
691 		{
692 			if( getProgress() > 1 && loadTest.getCancelOnReachedLimit() )
693 			{
694 				testRunner.cancel( "LoadTest Limit reached" );
695 			}
696 			else if( runContext.getCurrentStep() != null )
697 			{
698 				for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
699 				{
700 					listener.beforeTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, testStep );
701 				}
702 			}
703 		}
704 
705 		public void afterStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result )
706 		{
707 			for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
708 			{
709 				listener.afterTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, result );
710 			}
711 		}
712 
713 		public void afterRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
714 		{
715 			for( LoadTestRunListener listener : loadTest.getLoadTestRunListeners() )
716 			{
717 				listener.afterTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
718 			}
719 		}
720 	}
721 
722 	public TestRunContext getRunContext()
723 	{
724 		return context;
725 	}
726 
727 	public long getStartTime()
728 	{
729 		return startTime;
730 	}
731 
732 	public void start( boolean async )
733 	{
734 		start();
735 	}
736 
737 	public TestRunnable getTestRunnable()
738 	{
739 		return loadTest;
740 	}
741 }