View Javadoc

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