View Javadoc

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