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