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.io.File;
18  import java.io.FileNotFoundException;
19  import java.io.PrintWriter;
20  import java.util.ArrayList;
21  import java.util.Date;
22  import java.util.HashSet;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Set;
26  
27  import org.apache.log4j.Logger;
28  
29  import com.eviware.soapui.SoapUI;
30  import com.eviware.soapui.config.LoadStrategyConfig;
31  import com.eviware.soapui.config.LoadTestAssertionConfig;
32  import com.eviware.soapui.config.LoadTestConfig;
33  import com.eviware.soapui.config.LoadTestLimitTypesConfig;
34  import com.eviware.soapui.config.LoadTestLimitTypesConfig.Enum;
35  import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
36  import com.eviware.soapui.impl.wsdl.loadtest.assertions.AbstractLoadTestAssertion;
37  import com.eviware.soapui.impl.wsdl.loadtest.assertions.LoadTestAssertionRegistry;
38  import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics;
39  import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLog;
40  import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogErrorEntry;
41  import com.eviware.soapui.impl.wsdl.loadtest.strategy.BurstLoadStrategy;
42  import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategy;
43  import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategyFactory;
44  import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategyRegistry;
45  import com.eviware.soapui.impl.wsdl.loadtest.strategy.SimpleLoadStrategy;
46  import com.eviware.soapui.impl.wsdl.support.Configurable;
47  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
48  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
49  import com.eviware.soapui.impl.wsdl.teststeps.SimplePathPropertySupport;
50  import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
51  import com.eviware.soapui.model.support.LoadTestRunListenerAdapter;
52  import com.eviware.soapui.model.testsuite.LoadTest;
53  import com.eviware.soapui.model.testsuite.LoadTestRunContext;
54  import com.eviware.soapui.model.testsuite.LoadTestRunListener;
55  import com.eviware.soapui.model.testsuite.LoadTestRunner;
56  import com.eviware.soapui.model.testsuite.TestCaseRunContext;
57  import com.eviware.soapui.model.testsuite.TestCaseRunner;
58  import com.eviware.soapui.model.testsuite.TestRunnable;
59  import com.eviware.soapui.model.testsuite.TestRunner;
60  import com.eviware.soapui.model.testsuite.TestStepResult;
61  import com.eviware.soapui.model.testsuite.TestRunner.Status;
62  import com.eviware.soapui.settings.HttpSettings;
63  import com.eviware.soapui.support.StringUtils;
64  import com.eviware.soapui.support.scripting.SoapUIScriptEngine;
65  import com.eviware.soapui.support.scripting.SoapUIScriptEngineRegistry;
66  import com.eviware.soapui.support.types.StringList;
67  import com.eviware.soapui.support.types.StringToObjectMap;
68  
69  /***
70   * TestCase implementation for LoadTests
71   * 
72   * @author Ole.Matzura
73   * @todo add assertionFailed event to LoadTestListener
74   * @todo create and return LoadTestAssertionResult from load-test assertions
75   */
76  
77  public class WsdlLoadTest extends AbstractWsdlModelItem<LoadTestConfig> implements LoadTest, TestRunnable
78  {
79  	public final static String THREADCOUNT_PROPERTY = WsdlLoadTest.class.getName() + "@threadcount";
80  	public final static String STARTDELAY_PROPERTY = WsdlLoadTest.class.getName() + "@startdelay";
81  	public final static String TESTLIMIT_PROPERTY = WsdlLoadTest.class.getName() + "@testlimit";
82  	public final static String HISTORYLIMIT_PROPERTY = WsdlLoadTest.class.getName() + "@historylimit";
83  	public final static String LIMITTYPE_PROPERRY = WsdlLoadTest.class.getName() + "@limittype";
84  	public final static String SAMPLEINTERVAL_PROPERRY = WsdlLoadTest.class.getName() + "@sample-interval";
85  	public static final String MAXASSERTIONERRORS_PROPERTY = WsdlLoadTest.class.getName() + "@max-assertion-errors";
86  	public final static String SETUP_SCRIPT_PROPERTY = WsdlTestCase.class.getName() + "@setupScript";
87  	public final static String TEARDOWN_SCRIPT_PROPERTY = WsdlTestCase.class.getName() + "@tearDownScript";
88  
89  	private final static Logger logger = Logger.getLogger( WsdlLoadTest.class );
90  	public static final int DEFAULT_STRATEGY_INTERVAL = 500;
91  
92  	private InternalTestRunListener internalTestRunListener = new InternalTestRunListener();
93  
94  	private WsdlTestCase testCase;
95  	private LoadTestStatistics statisticsModel;
96  	private LoadStrategy loadStrategy = new BurstLoadStrategy( this );
97  	private LoadTestLog loadTestLog;
98  
99  	private LoadStrategyConfigurationChangeListener loadStrategyListener = new LoadStrategyConfigurationChangeListener();
100 	private List<LoadTestAssertion> assertions = new ArrayList<LoadTestAssertion>();
101 	private ConfigurationChangePropertyListener configurationChangeListener = new ConfigurationChangePropertyListener();
102 	private Set<LoadTestListener> loadTestListeners = new HashSet<LoadTestListener>();
103 	private Set<LoadTestRunListener> loadTestRunListeners = new HashSet<LoadTestRunListener>();
104 	private List<LoadTestLogErrorEntry> assertionErrors = new LinkedList<LoadTestLogErrorEntry>();
105 	private WsdlLoadTestRunner runner;
106 	private StatisticsLogger statisticsLogger = new StatisticsLogger();
107 	private SoapUIScriptEngine setupScriptEngine;
108 	private SoapUIScriptEngine tearDownScriptEngine;
109 	@SuppressWarnings("unused")
110 	private SimplePathPropertySupport logFolder;
111 	private LoadTestRunListener[] loadTestRunListenersArray;
112 
113 	public WsdlLoadTest( WsdlTestCase testCase, LoadTestConfig config )
114 	{
115 		super( config, testCase, "/loadTest.gif" );
116 
117 		this.testCase = testCase;
118 
119 		if( getConfig().getThreadCount() < 1 )
120 			getConfig().setThreadCount( 5 );
121 
122 		if( getConfig().getLimitType() == null )
123 		{
124 			getConfig().setLimitType( LoadTestLimitTypesConfig.TIME );
125 			getConfig().setTestLimit( 60 );
126 		}
127 
128 		if( !getConfig().isSetHistoryLimit() )
129 		{
130 			getConfig().setHistoryLimit( -1 );
131 		}
132 
133 		addLoadTestRunListener( internalTestRunListener );
134 
135 		LoadStrategyConfig ls = getConfig().getLoadStrategy();
136 		if( ls == null )
137 		{
138 			ls = getConfig().addNewLoadStrategy();
139 			ls.setType( SimpleLoadStrategy.STRATEGY_TYPE );
140 		}
141 
142 		LoadStrategyFactory factory = LoadStrategyRegistry.getInstance().getFactory( ls.getType() );
143 		if( factory == null )
144 		{
145 			ls.setType( SimpleLoadStrategy.STRATEGY_TYPE );
146 			factory = LoadStrategyRegistry.getInstance().getFactory( ls.getType() );
147 		}
148 
149 		loadStrategy = factory.build( ls.getConfig(), this );
150 		loadStrategy.addConfigurationChangeListener( loadStrategyListener );
151 
152 		addLoadTestRunListener( loadStrategy );
153 
154 		statisticsModel = new LoadTestStatistics( this );
155 
156 		if( getConfig().xgetSampleInterval() == null )
157 			setSampleInterval( LoadTestStatistics.DEFAULT_SAMPLE_INTERVAL );
158 
159 		statisticsModel.setUpdateFrequency( getSampleInterval() );
160 
161 		List<LoadTestAssertionConfig> assertionList = getConfig().getAssertionList();
162 		for( LoadTestAssertionConfig assertionConfig : assertionList )
163 		{
164 			AbstractLoadTestAssertion assertion = LoadTestAssertionRegistry.buildAssertion( assertionConfig, this );
165 			if( assertion != null )
166 			{
167 				assertions.add( assertion );
168 				assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
169 			}
170 			else
171 			{
172 				logger.warn( "Failed to build LoadTestAssertion from getConfig() [" + assertionConfig + "]" );
173 			}
174 		}
175 
176 		if( getConfig().xgetResetStatisticsOnThreadCountChange() == null )
177 			getConfig().setResetStatisticsOnThreadCountChange( true );
178 
179 		if( getConfig().xgetCalculateTPSOnTimePassed() == null )
180 			getConfig().setCalculateTPSOnTimePassed( true );
181 
182 		if( !getConfig().isSetMaxAssertionErrors() )
183 			getConfig().setMaxAssertionErrors( 100 );
184 
185 		if( getConfig().xgetCancelExcessiveThreads() == null )
186 			getConfig().setCancelExcessiveThreads( true );
187 
188 		if( getConfig().xgetStrategyInterval() == null )
189 			getConfig().setStrategyInterval( DEFAULT_STRATEGY_INTERVAL );
190 
191 		loadTestLog = new LoadTestLog( this );
192 
193 		for( LoadTestRunListener listener : SoapUI.getListenerRegistry().getListeners( LoadTestRunListener.class ) )
194 		{
195 			addLoadTestRunListener( listener );
196 		}
197 
198 		// set close-connections to same as global so override works ok
199 		if( !getSettings().isSet( HttpSettings.CLOSE_CONNECTIONS ) )
200 			getSettings().setBoolean( HttpSettings.CLOSE_CONNECTIONS,
201 					SoapUI.getSettings().getBoolean( HttpSettings.CLOSE_CONNECTIONS ) );
202 	}
203 
204 	public LoadTestStatistics getStatisticsModel()
205 	{
206 		return statisticsModel;
207 	}
208 
209 	public StatisticsLogger getStatisticsLogger()
210 	{
211 		return statisticsLogger;
212 	}
213 
214 	public long getThreadCount()
215 	{
216 		return getConfig().getThreadCount();
217 	}
218 
219 	public void setThreadCount( long threadCount )
220 	{
221 		long oldCount = getThreadCount();
222 		if( threadCount == oldCount )
223 			return;
224 
225 		if( getLogStatisticsOnThreadChange() && isRunning() )
226 			statisticsLogger.logStatistics( "ThreadCount change from " + oldCount + " to " + threadCount );
227 
228 		getConfig().setThreadCount( ( int )threadCount );
229 		notifyPropertyChanged( THREADCOUNT_PROPERTY, oldCount, threadCount );
230 	}
231 
232 	public boolean getResetStatisticsOnThreadCountChange()
233 	{
234 		return getConfig().getResetStatisticsOnThreadCountChange();
235 	}
236 
237 	public void setResetStatisticsOnThreadCountChange( boolean value )
238 	{
239 		getConfig().setResetStatisticsOnThreadCountChange( value );
240 	}
241 
242 	public boolean getCancelOnReachedLimit()
243 	{
244 		return getConfig().getCancelOnReachedLimit();
245 	}
246 
247 	public void setCancelOnReachedLimit( boolean value )
248 	{
249 		getConfig().setCancelOnReachedLimit( value );
250 	}
251 
252 	public boolean getCancelExcessiveThreads()
253 	{
254 		return getConfig().getCancelExcessiveThreads();
255 	}
256 
257 	public void setCancelExcessiveThreads( boolean value )
258 	{
259 		getConfig().setCancelExcessiveThreads( value );
260 	}
261 
262 	public boolean getLogStatisticsOnThreadChange()
263 	{
264 		return getConfig().getLogStatisticsOnThreadChange();
265 	}
266 
267 	public void setLogStatisticsOnThreadChange( boolean value )
268 	{
269 		getConfig().setLogStatisticsOnThreadChange( value );
270 	}
271 
272 	public String getStatisticsLogFolder()
273 	{
274 		return getConfig().getStatisticsLogFolder();
275 	}
276 
277 	public void setStatisticsLogFolder( String value )
278 	{
279 		getConfig().setStatisticsLogFolder( value );
280 	}
281 
282 	public boolean getCalculateTPSOnTimePassed()
283 	{
284 		return getConfig().getCalculateTPSOnTimePassed();
285 	}
286 
287 	public void setCalculateTPSOnTimePassed( boolean value )
288 	{
289 		getConfig().setCalculateTPSOnTimePassed( value );
290 	}
291 
292 	public int getStartDelay()
293 	{
294 		return getConfig().getStartDelay();
295 	}
296 
297 	public void setStartDelay( int startDelay )
298 	{
299 		if( startDelay < 0 )
300 			return;
301 
302 		int oldDelay = getStartDelay();
303 		getConfig().setStartDelay( startDelay );
304 		notifyPropertyChanged( STARTDELAY_PROPERTY, oldDelay, startDelay );
305 	}
306 
307 	public long getHistoryLimit()
308 	{
309 		return getConfig().getHistoryLimit();
310 	}
311 
312 	public void setHistoryLimit( long historyLimit )
313 	{
314 		long oldLimit = getHistoryLimit();
315 		getConfig().setHistoryLimit( historyLimit );
316 		if( historyLimit == 0 )
317 
318 			notifyPropertyChanged( HISTORYLIMIT_PROPERTY, oldLimit, historyLimit );
319 	}
320 
321 	public long getTestLimit()
322 	{
323 		return getConfig().getTestLimit();
324 	}
325 
326 	public void setTestLimit( long testLimit )
327 	{
328 		if( testLimit < 0 )
329 			return;
330 
331 		long oldLimit = getTestLimit();
332 		getConfig().setTestLimit( testLimit );
333 		notifyPropertyChanged( TESTLIMIT_PROPERTY, oldLimit, testLimit );
334 	}
335 
336 	public long getMaxAssertionErrors()
337 	{
338 		return getConfig().getMaxAssertionErrors();
339 	}
340 
341 	public void setMaxAssertionErrors( long testLimit )
342 	{
343 		if( testLimit < 0 )
344 			return;
345 
346 		long oldLimit = getMaxAssertionErrors();
347 		getConfig().setMaxAssertionErrors( testLimit );
348 		notifyPropertyChanged( MAXASSERTIONERRORS_PROPERTY, oldLimit, testLimit );
349 	}
350 
351 	public long getStatisticsLogInterval()
352 	{
353 		return getConfig().getStatisticsLogInterval();
354 	}
355 
356 	public void setStatisticsLogInterval( int sampleInterval )
357 	{
358 		if( sampleInterval < 0 )
359 			return;
360 
361 		long oldInterval = getStatisticsLogInterval();
362 		getConfig().setStatisticsLogInterval( sampleInterval );
363 
364 		notifyPropertyChanged( SAMPLEINTERVAL_PROPERRY, oldInterval, sampleInterval );
365 
366 		if( oldInterval == 0 && sampleInterval > 0 && isRunning() )
367 			statisticsLogger.start();
368 	}
369 
370 	public long getSampleInterval()
371 	{
372 		return getConfig().getSampleInterval();
373 	}
374 
375 	public void setSampleInterval( int sampleInterval )
376 	{
377 		if( sampleInterval < 0 )
378 			return;
379 
380 		long oldInterval = getSampleInterval();
381 		getConfig().setSampleInterval( sampleInterval );
382 
383 		statisticsModel.setUpdateFrequency( sampleInterval );
384 		notifyPropertyChanged( SAMPLEINTERVAL_PROPERRY, oldInterval, sampleInterval );
385 	}
386 
387 	public Enum getLimitType()
388 	{
389 		return getConfig().getLimitType();
390 	}
391 
392 	public void setLimitType( Enum limitType )
393 	{
394 		if( limitType == null )
395 			return;
396 
397 		Enum oldType = getLimitType();
398 		getConfig().setLimitType( limitType );
399 		notifyPropertyChanged( LIMITTYPE_PROPERRY, oldType, limitType );
400 	}
401 
402 	public WsdlTestCase getTestCase()
403 	{
404 		return testCase;
405 	}
406 
407 	public synchronized WsdlLoadTestRunner run()
408 	{
409 		getStatisticsModel().reset();
410 		if( runner != null && runner.getStatus() == Status.RUNNING )
411 			return null;
412 
413 		assertionErrors.clear();
414 		runner = new WsdlLoadTestRunner( this );
415 		runner.start();
416 		return runner;
417 	}
418 
419 	private class InternalTestRunListener extends LoadTestRunListenerAdapter
420 	{
421 		@Override
422 		public void afterLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
423 		{
424 			statisticsLogger.finish();
425 		}
426 
427 		@Override
428 		public void beforeLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
429 		{
430 			statisticsLogger.init( context );
431 
432 			if( getStatisticsLogInterval() > 0 )
433 				statisticsLogger.start();
434 		}
435 
436 		@Override
437 		public void afterTestCase( LoadTestRunner loadTestRunner, LoadTestRunContext context, TestCaseRunner testRunner,
438 				TestCaseRunContext runContext )
439 		{
440 			if( !assertions.isEmpty() )
441 			{
442 				for( LoadTestAssertion assertion : assertions )
443 				{
444 					String error = assertion.assertResults( loadTestRunner, context, testRunner, runContext );
445 					if( error != null )
446 					{
447 						int threadIndex = 0;
448 
449 						try
450 						{
451 							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString() );
452 						}
453 						catch( Throwable t )
454 						{
455 						}
456 
457 						loadTestLog.addEntry( new LoadTestLogErrorEntry( assertion.getName(), error, assertion.getIcon(),
458 								threadIndex ) );
459 						statisticsModel.addError( LoadTestStatistics.TOTAL );
460 					}
461 				}
462 			}
463 		}
464 
465 		@Override
466 		public void afterTestStep( LoadTestRunner loadTestRunner, LoadTestRunContext context, TestCaseRunner testRunner,
467 				TestCaseRunContext runContext, TestStepResult result )
468 		{
469 			if( !assertions.isEmpty() )
470 			{
471 				boolean added = false;
472 
473 				for( LoadTestAssertion assertion : assertions )
474 				{
475 					String error = assertion.assertResult( loadTestRunner, context, result, testRunner, runContext );
476 					if( error != null )
477 					{
478 						int indexOfTestStep = testRunner.getTestCase().getIndexOfTestStep( result.getTestStep() );
479 						int threadIndex = 0;
480 
481 						try
482 						{
483 							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString() );
484 						}
485 						catch( Throwable t )
486 						{
487 						}
488 
489 						LoadTestLogErrorEntry errorEntry = new LoadTestLogErrorEntry( assertion.getName(), error, result,
490 								assertion.getIcon(), threadIndex );
491 
492 						loadTestLog.addEntry( errorEntry );
493 						statisticsModel.addError( indexOfTestStep );
494 
495 						long maxAssertionErrors = getMaxAssertionErrors();
496 						if( maxAssertionErrors > 0 )
497 						{
498 							synchronized( assertionErrors )
499 							{
500 								assertionErrors.add( errorEntry );
501 								while( assertionErrors.size() > maxAssertionErrors )
502 								{
503 									assertionErrors.remove( 0 ).discard();
504 								}
505 							}
506 						}
507 
508 						added = true;
509 					}
510 				}
511 
512 				// discard if set to discard and there were no errors
513 				if( !added )
514 				{
515 					if( getTestCase().getDiscardOkResults() || getTestCase().getMaxResults() == 0 )
516 					{
517 						result.discard();
518 					}
519 					else if( getTestCase().getMaxResults() > 0 && testRunner instanceof WsdlTestCaseRunner )
520 					{
521 						( ( WsdlTestCaseRunner )testRunner ).enforceMaxResults( getTestCase().getMaxResults() );
522 					}
523 				}
524 			}
525 			else
526 				result.discard();
527 		}
528 	}
529 
530 	public LoadStrategy getLoadStrategy()
531 	{
532 		return loadStrategy;
533 	}
534 
535 	public void setLoadStrategy( LoadStrategy loadStrategy )
536 	{
537 		this.loadStrategy.removeConfigurationChangeListener( loadStrategyListener );
538 		removeLoadTestRunListener( this.loadStrategy );
539 
540 		this.loadStrategy = loadStrategy;
541 		this.loadStrategy.addConfigurationChangeListener( loadStrategyListener );
542 		addLoadTestRunListener( this.loadStrategy );
543 
544 		getConfig().getLoadStrategy().setType( loadStrategy.getType() );
545 		getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
546 	}
547 
548 	private class LoadStrategyConfigurationChangeListener implements PropertyChangeListener
549 	{
550 		public void propertyChange( PropertyChangeEvent evt )
551 		{
552 			getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
553 		}
554 	}
555 
556 	public LoadTestAssertion addAssertion( String type, String targetStep, boolean showConfig )
557 	{
558 		LoadTestAssertion assertion = LoadTestAssertionRegistry.createAssertion( type, this );
559 		assertion.setTargetStep( targetStep );
560 
561 		if( assertion instanceof Configurable && showConfig )
562 		{
563 			if( !( ( Configurable )assertion ).configure() )
564 				return null;
565 		}
566 
567 		assertions.add( assertion );
568 
569 		getConfig().addNewAssertion().set( assertion.getConfiguration() );
570 		assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
571 		fireAssertionAdded( assertion );
572 
573 		return assertion;
574 	}
575 
576 	public void removeAssertion( LoadTestAssertion assertion )
577 	{
578 		int ix = assertions.indexOf( assertion );
579 		if( ix >= 0 )
580 		{
581 			try
582 			{
583 				assertions.remove( ix );
584 				fireAssertionRemoved( assertion );
585 			}
586 			finally
587 			{
588 				assertion.removePropertyChangeListener( configurationChangeListener );
589 				assertion.release();
590 				getConfig().removeAssertion( ix );
591 			}
592 		}
593 	}
594 
595 	private void fireAssertionRemoved( LoadTestAssertion assertion )
596 	{
597 		if( !loadTestListeners.isEmpty() )
598 		{
599 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
600 			for( LoadTestListener listener : l )
601 			{
602 				listener.assertionRemoved( assertion );
603 			}
604 		}
605 	}
606 
607 	private void fireAssertionAdded( LoadTestAssertion assertion )
608 	{
609 		if( !loadTestListeners.isEmpty() )
610 		{
611 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
612 			for( LoadTestListener listener : l )
613 			{
614 				listener.assertionAdded( assertion );
615 			}
616 		}
617 	}
618 
619 	public int getAssertionCount()
620 	{
621 		return assertions.size();
622 	}
623 
624 	public LoadTestAssertion getAssertionAt( int index )
625 	{
626 		return index < 0 || index >= assertions.size() ? null : assertions.get( index );
627 	}
628 
629 	private class ConfigurationChangePropertyListener implements PropertyChangeListener
630 	{
631 		public void propertyChange( PropertyChangeEvent evt )
632 		{
633 			int ix = assertions.indexOf( evt.getSource() );
634 			if( ix >= 0 )
635 			{
636 				getConfig().getAssertionArray( ix ).set( assertions.get( ix ).getConfiguration() );
637 			}
638 		}
639 	}
640 
641 	public LoadTestLog getLoadTestLog()
642 	{
643 		return loadTestLog;
644 	}
645 
646 	public List<LoadTestAssertion> getAssertionList()
647 	{
648 		return assertions;
649 	}
650 
651 	public void addLoadTestListener( LoadTestListener listener )
652 	{
653 		loadTestListeners.add( listener );
654 	}
655 
656 	public void removeLoadTestListener( LoadTestListener listener )
657 	{
658 		loadTestListeners.remove( listener );
659 	}
660 
661 	public void addLoadTestRunListener( LoadTestRunListener listener )
662 	{
663 		loadTestRunListeners.add( listener );
664 		loadTestRunListenersArray = null;
665 	}
666 
667 	public void removeLoadTestRunListener( LoadTestRunListener listener )
668 	{
669 		loadTestRunListeners.remove( listener );
670 		loadTestRunListenersArray = null;
671 	}
672 
673 	public LoadTestRunListener[] getLoadTestRunListeners()
674 	{
675 		if( loadTestRunListenersArray == null )
676 		{
677 			loadTestRunListenersArray = loadTestRunListeners
678 					.toArray( new LoadTestRunListener[loadTestRunListeners.size()] );
679 		}
680 
681 		return loadTestRunListenersArray;
682 	}
683 
684 	/***
685 	 * Release internal objects so they can remove listeners
686 	 */
687 
688 	@Override
689 	public void release()
690 	{
691 		super.release();
692 
693 		statisticsModel.release();
694 		loadTestLog.release();
695 
696 		for( LoadTestAssertion assertion : assertions )
697 			assertion.release();
698 
699 		loadTestRunListeners.clear();
700 		loadTestListeners.clear();
701 	}
702 
703 	public boolean isRunning()
704 	{
705 		return runner != null && runner.getStatus() == LoadTestRunner.Status.RUNNING;
706 	}
707 
708 	public WsdlLoadTestRunner getRunner()
709 	{
710 		return runner;
711 	}
712 
713 	public void resetConfigOnMove( LoadTestConfig config )
714 	{
715 		setConfig( config );
716 
717 		loadStrategy.updateConfig( config.getLoadStrategy().getConfig() );
718 
719 		List<LoadTestAssertionConfig> assertionList = config.getAssertionList();
720 		for( int c = 0; c < assertionList.size(); c++ )
721 		{
722 			assertions.get( c ).updateConfiguration( assertionList.get( c ) );
723 		}
724 	}
725 
726 	public class StatisticsLogger implements Runnable
727 	{
728 		private boolean stopped;
729 		private List<PrintWriter> writers = new ArrayList<PrintWriter>();
730 		private long startTime;
731 
732 		public void run()
733 		{
734 			stopped = false;
735 
736 			while( !stopped && getStatisticsLogInterval() > 0 )
737 			{
738 				try
739 				{
740 					long statisticsInterval = getStatisticsLogInterval();
741 					Thread.sleep( statisticsInterval );
742 					if( !stopped )
743 					{
744 						logStatistics( "Interval" );
745 					}
746 				}
747 				catch( InterruptedException e )
748 				{
749 					e.printStackTrace();
750 				}
751 			}
752 		}
753 
754 		public void start()
755 		{
756 			new Thread( this, "Statistics Logger for LoadTest [" + getName() + "]" ).start();
757 		}
758 
759 		public void init( LoadTestRunContext context )
760 		{
761 			writers.clear();
762 
763 			String statisticsLogFolder = context.expand( getStatisticsLogFolder() );
764 			if( StringUtils.isNullOrEmpty( statisticsLogFolder ) )
765 				return;
766 
767 			File folder = new File( statisticsLogFolder );
768 			if( !folder.exists() )
769 			{
770 				if( !folder.mkdirs() )
771 				{
772 					SoapUI
773 							.logError( new Exception( "Failed to create statistics log folder [" + statisticsLogFolder + "]" ) );
774 					return;
775 				}
776 			}
777 
778 			for( int c = 0; c < testCase.getTestStepCount(); c++ )
779 			{
780 				try
781 				{
782 					WsdlTestStep testStep = testCase.getTestStepAt( c );
783 					String fileName = StringUtils.createFileName( testStep.getName(), '_' ) + ".log";
784 					PrintWriter writer = new PrintWriter( new File( folder, fileName ) );
785 					writers.add( writer );
786 					addHeaders( writer );
787 				}
788 				catch( FileNotFoundException e )
789 				{
790 					e.printStackTrace();
791 					writers.add( null );
792 				}
793 			}
794 
795 			// and one writer for the testcase..
796 			try
797 			{
798 				String fileName = StringUtils.createFileName( testCase.getName(), '_' ) + ".log";
799 				writers.add( new PrintWriter( new File( folder, fileName ) ) );
800 			}
801 			catch( FileNotFoundException e )
802 			{
803 				e.printStackTrace();
804 			}
805 
806 			startTime = System.nanoTime();
807 		}
808 
809 		private void addHeaders( PrintWriter writer )
810 		{
811 			writer.print( "date,threads,elapsed,min,max,avg,last,cnt,tps,bytes,bps,err,reason\n" );
812 		}
813 
814 		public void finish()
815 		{
816 			stopped = true;
817 
818 			logStatistics( "Finished" );
819 			for( PrintWriter writer : writers )
820 			{
821 				if( writer != null )
822 					writer.close();
823 			}
824 		}
825 
826 		private synchronized void logStatistics( String trigger )
827 		{
828 			if( writers.isEmpty() )
829 				return;
830 
831 			long timestamp = System.nanoTime();
832 			String elapsedString = String.valueOf( ( timestamp - startTime ) / 100000 );
833 			String dateString = new Date().toString();
834 			String threadCountString = String.valueOf( getThreadCount() );
835 
836 			StringList[] snapshot = statisticsModel.getSnapshot();
837 			for( int c = 0; c < snapshot.length; c++ )
838 			{
839 				PrintWriter writer = writers.get( c );
840 				if( writer == null )
841 					continue;
842 
843 				StringList values = snapshot[c];
844 				writer.append( dateString ).append( ',' );
845 				writer.append( threadCountString ).append( ',' );
846 				writer.append( elapsedString );
847 
848 				for( String value : values )
849 				{
850 					writer.append( ',' ).append( value );
851 				}
852 
853 				writer.append( ',' ).append( trigger ).append( '\n' );
854 				writer.flush();
855 			}
856 		}
857 	}
858 
859 	public void setSetupScript( String script )
860 	{
861 		String oldScript = getSetupScript();
862 
863 		if( !getConfig().isSetSetupScript() )
864 			getConfig().addNewSetupScript();
865 
866 		getConfig().getSetupScript().setStringValue( script );
867 		if( setupScriptEngine != null )
868 			setupScriptEngine.setScript( script );
869 
870 		notifyPropertyChanged( SETUP_SCRIPT_PROPERTY, oldScript, script );
871 	}
872 
873 	public String getSetupScript()
874 	{
875 		return getConfig().isSetSetupScript() ? getConfig().getSetupScript().getStringValue() : null;
876 	}
877 
878 	public void setTearDownScript( String script )
879 	{
880 		String oldScript = getTearDownScript();
881 
882 		if( !getConfig().isSetTearDownScript() )
883 			getConfig().addNewTearDownScript();
884 
885 		getConfig().getTearDownScript().setStringValue( script );
886 		if( tearDownScriptEngine != null )
887 			tearDownScriptEngine.setScript( script );
888 
889 		notifyPropertyChanged( TEARDOWN_SCRIPT_PROPERTY, oldScript, script );
890 	}
891 
892 	public String getTearDownScript()
893 	{
894 		return getConfig().isSetTearDownScript() ? getConfig().getTearDownScript().getStringValue() : null;
895 	}
896 
897 	public Object runSetupScript( LoadTestRunContext runContext, LoadTestRunner runner ) throws Exception
898 	{
899 		String script = getSetupScript();
900 		if( StringUtils.isNullOrEmpty( script ) )
901 			return null;
902 
903 		if( setupScriptEngine == null )
904 		{
905 			setupScriptEngine = SoapUIScriptEngineRegistry.create( this );
906 			setupScriptEngine.setScript( script );
907 		}
908 
909 		setupScriptEngine.setVariable( "context", runContext );
910 		setupScriptEngine.setVariable( "loadTestRunner", runner );
911 		setupScriptEngine.setVariable( "log", SoapUI.ensureGroovyLog() );
912 		return setupScriptEngine.run();
913 	}
914 
915 	public Object runTearDownScript( LoadTestRunContext runContext, LoadTestRunner runner ) throws Exception
916 	{
917 		String script = getTearDownScript();
918 		if( StringUtils.isNullOrEmpty( script ) )
919 			return null;
920 
921 		if( tearDownScriptEngine == null )
922 		{
923 			tearDownScriptEngine = SoapUIScriptEngineRegistry.create( this );
924 			tearDownScriptEngine.setScript( script );
925 		}
926 
927 		tearDownScriptEngine.setVariable( "context", runContext );
928 		tearDownScriptEngine.setVariable( "loadTestRunner", runner );
929 		tearDownScriptEngine.setVariable( "log", SoapUI.ensureGroovyLog() );
930 		return tearDownScriptEngine.run();
931 	}
932 
933 	public int getStrategyInterval()
934 	{
935 		return getConfig().getStrategyInterval();
936 	}
937 
938 	public void setStrategyInterval( int interval )
939 	{
940 		getConfig().setStrategyInterval( interval );
941 	}
942 
943 	public boolean getUpdateStatisticsPerTestStep()
944 	{
945 		return getConfig().getUpdateStatisticsPerTestStep();
946 	}
947 
948 	public void setUpdateStatisticsPerTestStep( boolean updateStatisticsPerTestStep )
949 	{
950 		getConfig().setUpdateStatisticsPerTestStep( updateStatisticsPerTestStep );
951 	}
952 
953 	public TestRunner run( StringToObjectMap context, boolean async )
954 	{
955 		// TODO Auto-generated method stub
956 		return null;
957 	}
958 }