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.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 		if( runner != null )
414 			runner.release();
415 
416 		assertionErrors.clear();
417 		runner = new WsdlLoadTestRunner( this );
418 		runner.start();
419 		return runner;
420 	}
421 
422 	private class InternalTestRunListener extends LoadTestRunListenerAdapter
423 	{
424 		@Override
425 		public void afterLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
426 		{
427 			statisticsLogger.finish();
428 		}
429 
430 		@Override
431 		public void beforeLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
432 		{
433 			statisticsLogger.init( context );
434 
435 			if( getStatisticsLogInterval() > 0 )
436 				statisticsLogger.start();
437 		}
438 
439 		@Override
440 		public void afterTestCase( LoadTestRunner loadTestRunner, LoadTestRunContext context, TestCaseRunner testRunner,
441 				TestCaseRunContext runContext )
442 		{
443 			if( !assertions.isEmpty() )
444 			{
445 				for( LoadTestAssertion assertion : assertions )
446 				{
447 					String error = assertion.assertResults( loadTestRunner, context, testRunner, runContext );
448 					if( error != null )
449 					{
450 						int threadIndex = 0;
451 
452 						try
453 						{
454 							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString() );
455 						}
456 						catch( Throwable t )
457 						{
458 						}
459 
460 						loadTestLog.addEntry( new LoadTestLogErrorEntry( assertion.getName(), error, assertion.getIcon(),
461 								threadIndex ) );
462 						statisticsModel.addError( LoadTestStatistics.TOTAL );
463 					}
464 				}
465 			}
466 		}
467 
468 		@Override
469 		public void afterTestStep( LoadTestRunner loadTestRunner, LoadTestRunContext context, TestCaseRunner testRunner,
470 				TestCaseRunContext runContext, TestStepResult result )
471 		{
472 			if( !assertions.isEmpty() )
473 			{
474 				boolean added = false;
475 
476 				for( LoadTestAssertion assertion : assertions )
477 				{
478 					String error = assertion.assertResult( loadTestRunner, context, result, testRunner, runContext );
479 					if( error != null )
480 					{
481 						int indexOfTestStep = testRunner.getTestCase().getIndexOfTestStep( result.getTestStep() );
482 						int threadIndex = 0;
483 
484 						try
485 						{
486 							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString() );
487 						}
488 						catch( Throwable t )
489 						{
490 						}
491 
492 						LoadTestLogErrorEntry errorEntry = new LoadTestLogErrorEntry( assertion.getName(), error, result,
493 								assertion.getIcon(), threadIndex );
494 
495 						loadTestLog.addEntry( errorEntry );
496 						statisticsModel.addError( indexOfTestStep );
497 
498 						long maxAssertionErrors = getMaxAssertionErrors();
499 						if( maxAssertionErrors > 0 )
500 						{
501 							synchronized( assertionErrors )
502 							{
503 								assertionErrors.add( errorEntry );
504 								while( assertionErrors.size() > maxAssertionErrors )
505 								{
506 									assertionErrors.remove( 0 ).discard();
507 								}
508 							}
509 						}
510 
511 						added = true;
512 					}
513 				}
514 
515 				// discard if set to discard and there were no errors
516 				if( !added )
517 				{
518 					if( getTestCase().getDiscardOkResults() || getTestCase().getMaxResults() == 0 )
519 					{
520 						result.discard();
521 					}
522 					else if( getTestCase().getMaxResults() > 0 && testRunner instanceof WsdlTestCaseRunner )
523 					{
524 						( ( WsdlTestCaseRunner )testRunner ).enforceMaxResults( getTestCase().getMaxResults() );
525 					}
526 				}
527 			}
528 			else
529 				result.discard();
530 		}
531 	}
532 
533 	public LoadStrategy getLoadStrategy()
534 	{
535 		return loadStrategy;
536 	}
537 
538 	public void setLoadStrategy( LoadStrategy loadStrategy )
539 	{
540 		this.loadStrategy.removeConfigurationChangeListener( loadStrategyListener );
541 		removeLoadTestRunListener( this.loadStrategy );
542 
543 		this.loadStrategy = loadStrategy;
544 		this.loadStrategy.addConfigurationChangeListener( loadStrategyListener );
545 		addLoadTestRunListener( this.loadStrategy );
546 
547 		getConfig().getLoadStrategy().setType( loadStrategy.getType() );
548 		getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
549 	}
550 
551 	private class LoadStrategyConfigurationChangeListener implements PropertyChangeListener
552 	{
553 		public void propertyChange( PropertyChangeEvent evt )
554 		{
555 			getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
556 		}
557 	}
558 
559 	public LoadTestAssertion addAssertion( String type, String targetStep, boolean showConfig )
560 	{
561 		LoadTestAssertion assertion = LoadTestAssertionRegistry.createAssertion( type, this );
562 		assertion.setTargetStep( targetStep );
563 
564 		if( assertion instanceof Configurable && showConfig )
565 		{
566 			if( !( ( Configurable )assertion ).configure() )
567 				return null;
568 		}
569 
570 		assertions.add( assertion );
571 
572 		getConfig().addNewAssertion().set( assertion.getConfiguration() );
573 		assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
574 		fireAssertionAdded( assertion );
575 
576 		return assertion;
577 	}
578 
579 	public void removeAssertion( LoadTestAssertion assertion )
580 	{
581 		int ix = assertions.indexOf( assertion );
582 		if( ix >= 0 )
583 		{
584 			try
585 			{
586 				assertions.remove( ix );
587 				fireAssertionRemoved( assertion );
588 			}
589 			finally
590 			{
591 				assertion.removePropertyChangeListener( configurationChangeListener );
592 				assertion.release();
593 				getConfig().removeAssertion( ix );
594 			}
595 		}
596 	}
597 
598 	private void fireAssertionRemoved( LoadTestAssertion assertion )
599 	{
600 		if( !loadTestListeners.isEmpty() )
601 		{
602 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
603 			for( LoadTestListener listener : l )
604 			{
605 				listener.assertionRemoved( assertion );
606 			}
607 		}
608 	}
609 
610 	private void fireAssertionAdded( LoadTestAssertion assertion )
611 	{
612 		if( !loadTestListeners.isEmpty() )
613 		{
614 			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
615 			for( LoadTestListener listener : l )
616 			{
617 				listener.assertionAdded( assertion );
618 			}
619 		}
620 	}
621 
622 	public int getAssertionCount()
623 	{
624 		return assertions.size();
625 	}
626 
627 	public LoadTestAssertion getAssertionAt( int index )
628 	{
629 		return index < 0 || index >= assertions.size() ? null : assertions.get( index );
630 	}
631 
632 	private class ConfigurationChangePropertyListener implements PropertyChangeListener
633 	{
634 		public void propertyChange( PropertyChangeEvent evt )
635 		{
636 			int ix = assertions.indexOf( evt.getSource() );
637 			if( ix >= 0 )
638 			{
639 				getConfig().getAssertionArray( ix ).set( assertions.get( ix ).getConfiguration() );
640 			}
641 		}
642 	}
643 
644 	public LoadTestLog getLoadTestLog()
645 	{
646 		return loadTestLog;
647 	}
648 
649 	public List<LoadTestAssertion> getAssertionList()
650 	{
651 		return assertions;
652 	}
653 
654 	public void addLoadTestListener( LoadTestListener listener )
655 	{
656 		loadTestListeners.add( listener );
657 	}
658 
659 	public void removeLoadTestListener( LoadTestListener listener )
660 	{
661 		loadTestListeners.remove( listener );
662 	}
663 
664 	public void addLoadTestRunListener( LoadTestRunListener listener )
665 	{
666 		loadTestRunListeners.add( listener );
667 		loadTestRunListenersArray = null;
668 	}
669 
670 	public void removeLoadTestRunListener( LoadTestRunListener listener )
671 	{
672 		loadTestRunListeners.remove( listener );
673 		loadTestRunListenersArray = null;
674 	}
675 
676 	public LoadTestRunListener[] getLoadTestRunListeners()
677 	{
678 		if( loadTestRunListenersArray == null )
679 		{
680 			loadTestRunListenersArray = loadTestRunListeners
681 					.toArray( new LoadTestRunListener[loadTestRunListeners.size()] );
682 		}
683 
684 		return loadTestRunListenersArray;
685 	}
686 
687 	/***
688 	 * Release internal objects so they can remove listeners
689 	 */
690 
691 	@Override
692 	public void release()
693 	{
694 		super.release();
695 
696 		statisticsModel.release();
697 		loadTestLog.release();
698 
699 		for( LoadTestAssertion assertion : assertions )
700 			assertion.release();
701 
702 		loadTestRunListeners.clear();
703 		loadTestListeners.clear();
704 	}
705 
706 	public boolean isRunning()
707 	{
708 		return runner != null && runner.getStatus() == LoadTestRunner.Status.RUNNING;
709 	}
710 
711 	public WsdlLoadTestRunner getRunner()
712 	{
713 		return runner;
714 	}
715 
716 	public void resetConfigOnMove( LoadTestConfig config )
717 	{
718 		setConfig( config );
719 
720 		loadStrategy.updateConfig( config.getLoadStrategy().getConfig() );
721 
722 		List<LoadTestAssertionConfig> assertionList = config.getAssertionList();
723 		for( int c = 0; c < assertionList.size(); c++ )
724 		{
725 			assertions.get( c ).updateConfiguration( assertionList.get( c ) );
726 		}
727 	}
728 
729 	public class StatisticsLogger implements Runnable
730 	{
731 		private boolean stopped;
732 		private List<PrintWriter> writers = new ArrayList<PrintWriter>();
733 		private long startTime;
734 
735 		public void run()
736 		{
737 			stopped = false;
738 
739 			while( !stopped && getStatisticsLogInterval() > 0 )
740 			{
741 				try
742 				{
743 					long statisticsInterval = getStatisticsLogInterval();
744 					Thread.sleep( statisticsInterval );
745 					if( !stopped )
746 					{
747 						logStatistics( "Interval" );
748 					}
749 				}
750 				catch( InterruptedException e )
751 				{
752 					e.printStackTrace();
753 				}
754 			}
755 		}
756 
757 		public void start()
758 		{
759 			new Thread( this, "Statistics Logger for LoadTest [" + getName() + "]" ).start();
760 		}
761 
762 		public void init( LoadTestRunContext context )
763 		{
764 			writers.clear();
765 
766 			String statisticsLogFolder = context.expand( getStatisticsLogFolder() );
767 			if( StringUtils.isNullOrEmpty( statisticsLogFolder ) )
768 				return;
769 
770 			File folder = new File( statisticsLogFolder );
771 			if( !folder.exists() )
772 			{
773 				if( !folder.mkdirs() )
774 				{
775 					SoapUI
776 							.logError( new Exception( "Failed to create statistics log folder [" + statisticsLogFolder + "]" ) );
777 					return;
778 				}
779 			}
780 
781 			for( int c = 0; c < testCase.getTestStepCount(); c++ )
782 			{
783 				try
784 				{
785 					WsdlTestStep testStep = testCase.getTestStepAt( c );
786 					String fileName = StringUtils.createFileName( testStep.getName(), '_' ) + ".log";
787 					PrintWriter writer = new PrintWriter( new File( folder, fileName ) );
788 					writers.add( writer );
789 					addHeaders( writer );
790 				}
791 				catch( FileNotFoundException e )
792 				{
793 					e.printStackTrace();
794 					writers.add( null );
795 				}
796 			}
797 
798 			// and one writer for the testcase..
799 			try
800 			{
801 				String fileName = StringUtils.createFileName( testCase.getName(), '_' ) + ".log";
802 				writers.add( new PrintWriter( new File( folder, fileName ) ) );
803 			}
804 			catch( FileNotFoundException e )
805 			{
806 				e.printStackTrace();
807 			}
808 
809 			startTime = System.nanoTime();
810 		}
811 
812 		private void addHeaders( PrintWriter writer )
813 		{
814 			writer.print( "date,threads,elapsed,min,max,avg,last,cnt,tps,bytes,bps,err,reason\n" );
815 		}
816 
817 		public void finish()
818 		{
819 			stopped = true;
820 
821 			logStatistics( "Finished" );
822 			for( PrintWriter writer : writers )
823 			{
824 				if( writer != null )
825 					writer.close();
826 			}
827 		}
828 
829 		private synchronized void logStatistics( String trigger )
830 		{
831 			if( writers.isEmpty() )
832 				return;
833 
834 			long timestamp = System.nanoTime();
835 			String elapsedString = String.valueOf( ( timestamp - startTime ) / 1000000 );
836 			String dateString = new Date().toString();
837 			String threadCountString = String.valueOf( getThreadCount() );
838 
839 			StringList[] snapshot = statisticsModel.getSnapshot();
840 			for( int c = 0; c < snapshot.length; c++ )
841 			{
842 				PrintWriter writer = writers.get( c );
843 				if( writer == null )
844 					continue;
845 
846 				StringList values = snapshot[c];
847 				writer.append( dateString ).append( ',' );
848 				writer.append( threadCountString ).append( ',' );
849 				writer.append( elapsedString );
850 
851 				for( String value : values )
852 				{
853 					writer.append( ',' ).append( value );
854 				}
855 
856 				writer.append( ',' ).append( trigger ).append( '\n' );
857 				writer.flush();
858 			}
859 		}
860 	}
861 
862 	public void setSetupScript( String script )
863 	{
864 		String oldScript = getSetupScript();
865 
866 		if( !getConfig().isSetSetupScript() )
867 			getConfig().addNewSetupScript();
868 
869 		getConfig().getSetupScript().setStringValue( script );
870 		if( setupScriptEngine != null )
871 			setupScriptEngine.setScript( script );
872 
873 		notifyPropertyChanged( SETUP_SCRIPT_PROPERTY, oldScript, script );
874 	}
875 
876 	public String getSetupScript()
877 	{
878 		return getConfig().isSetSetupScript() ? getConfig().getSetupScript().getStringValue() : null;
879 	}
880 
881 	public void setTearDownScript( String script )
882 	{
883 		String oldScript = getTearDownScript();
884 
885 		if( !getConfig().isSetTearDownScript() )
886 			getConfig().addNewTearDownScript();
887 
888 		getConfig().getTearDownScript().setStringValue( script );
889 		if( tearDownScriptEngine != null )
890 			tearDownScriptEngine.setScript( script );
891 
892 		notifyPropertyChanged( TEARDOWN_SCRIPT_PROPERTY, oldScript, script );
893 	}
894 
895 	public String getTearDownScript()
896 	{
897 		return getConfig().isSetTearDownScript() ? getConfig().getTearDownScript().getStringValue() : null;
898 	}
899 
900 	public Object runSetupScript( LoadTestRunContext runContext, LoadTestRunner runner ) throws Exception
901 	{
902 		String script = getSetupScript();
903 		if( StringUtils.isNullOrEmpty( script ) )
904 			return null;
905 
906 		if( setupScriptEngine == null )
907 		{
908 			setupScriptEngine = SoapUIScriptEngineRegistry.create( this );
909 			setupScriptEngine.setScript( script );
910 		}
911 
912 		setupScriptEngine.setVariable( "context", runContext );
913 		setupScriptEngine.setVariable( "loadTestRunner", runner );
914 		setupScriptEngine.setVariable( "log", SoapUI.ensureGroovyLog() );
915 		return setupScriptEngine.run();
916 	}
917 
918 	public Object runTearDownScript( LoadTestRunContext runContext, LoadTestRunner runner ) throws Exception
919 	{
920 		String script = getTearDownScript();
921 		if( StringUtils.isNullOrEmpty( script ) )
922 			return null;
923 
924 		if( tearDownScriptEngine == null )
925 		{
926 			tearDownScriptEngine = SoapUIScriptEngineRegistry.create( this );
927 			tearDownScriptEngine.setScript( script );
928 		}
929 
930 		tearDownScriptEngine.setVariable( "context", runContext );
931 		tearDownScriptEngine.setVariable( "loadTestRunner", runner );
932 		tearDownScriptEngine.setVariable( "log", SoapUI.ensureGroovyLog() );
933 		return tearDownScriptEngine.run();
934 	}
935 
936 	public int getStrategyInterval()
937 	{
938 		return getConfig().getStrategyInterval();
939 	}
940 
941 	public void setStrategyInterval( int interval )
942 	{
943 		getConfig().setStrategyInterval( interval );
944 	}
945 
946 	public boolean getUpdateStatisticsPerTestStep()
947 	{
948 		return getConfig().getUpdateStatisticsPerTestStep();
949 	}
950 
951 	public void setUpdateStatisticsPerTestStep( boolean updateStatisticsPerTestStep )
952 	{
953 		getConfig().setUpdateStatisticsPerTestStep( updateStatisticsPerTestStep );
954 	}
955 
956 	public TestRunner run( StringToObjectMap context, boolean async )
957 	{
958 		// TODO Auto-generated method stub
959 		return null;
960 	}
961 }