View Javadoc

    1   /*
    2    *  soapUI, copyright (C) 2004-2007 eviware.com
    3    *
    4    *  soapUI is free software; you can redistribute it and/or modify it under the
    5    *  terms of version 2.1 of the GNU Lesser General Public License as published by
    6    *  the Free Software Foundation.
    7    *
    8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
    9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    10   *  See the GNU Lesser General Public License for more details at gnu.org.
    11   */
    12
    13  package com.eviware.soapui.impl.wsdl.loadtest;
    14
    15  import java.beans.PropertyChangeEvent;
    16  import java.beans.PropertyChangeListener;
    17  import java.util.ArrayList;
    18  import java.util.Date;
    19  import java.util.HashSet;
    20  import java.util.Iterator;
    21  import java.util.List;
    22  import java.util.Set;
    23
    24  import com.eviware.soapui.SoapUI;
    25  import com.eviware.soapui.config.LoadTestConfig;
    26  import com.eviware.soapui.config.LoadTestLimitTypesConfig;
    27  import com.eviware.soapui.config.TestCaseConfig;
    28  import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogMessageEntry;
    29  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
    30  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
    31  import com.eviware.soapui.model.settings.Settings;
    32  import com.eviware.soapui.model.support.PropertiesMap;
    33  import com.eviware.soapui.model.testsuite.LoadTest;
    34  import com.eviware.soapui.model.testsuite.LoadTestRunListener;
    35  import com.eviware.soapui.model.testsuite.LoadTestRunner;
    36  import com.eviware.soapui.model.testsuite.TestRunContext;
    37  import com.eviware.soapui.model.testsuite.TestRunListener;
    38  import com.eviware.soapui.model.testsuite.TestRunner;
    39  import com.eviware.soapui.model.testsuite.TestStepResult;
    40  import com.eviware.soapui.settings.HttpSettings;
    41  import com.eviware.soapui.support.UISupport;
    42  import com.eviware.x.dialogs.Worker;
    43  import com.eviware.x.dialogs.XProgressDialog;
    44  import com.eviware.x.dialogs.XProgressMonitor;
    45
    46  /***
    47   * TestRunner for load-tests.
    48   *
    49   * @todo statistics should be calculated first after all threads have been started..
    50   *
    51   * @author Ole.Matzura
    52   */
    53
    54  public class WsdlLoadTestRunner implements LoadTestRunner
    55  {
    56  	private final WsdlLoadTest loadTest;
    57  	private ThreadGroup threadGroup;
    58  	private Set<TestCaseRunner> runners = new HashSet<TestCaseRunner>();
    59  	private long startTime = 0;
    60  	private InternalPropertyChangeListener internalPropertyChangeListener = new InternalPropertyChangeListener();
    61  	private InternalTestRunListener testRunListener = new InternalTestRunListener();
    62  	private LoadTestRunListener[] listeners;
    63  	private long runCount;
    64  	private Status status;
    65  	private WsdlLoadTestContext context;
    66  	private String reason;
    67  	private int threadCount;
    68  	private int threadsWaitingToStart;
    69
    70  	public WsdlLoadTestRunner(WsdlLoadTest test)
    71  	{
    72  		this.loadTest = test;
    73  		threadGroup = new ThreadGroup( loadTest.getName() );
    74
    75  		status = Status.INITIALIZED;
    76  	}
    77
    78  	public Status getStatus()
    79  	{
    80  		return status;
    81  	}
    82
    83  	void start()
    84  	{
    85  		loadTest.getTestCase().onSave();
    86  		startTime = System.currentTimeMillis();
    87
    88  		runners.clear();
    89  		loadTest.addPropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener );
    90  		runCount = 0;
    91  		threadCount = 0;
    92  		threadsWaitingToStart = 0;
    93  		context = new WsdlLoadTestContext( this );
    94
    95  		status = Status.RUNNING;
    96
    97  		listeners = loadTest.getLoadTestRunListeners();
    98  		for( LoadTestRunListener listener : listeners )
    99  		{
    100 			listener.beforeLoadTest( this, context );
    101 		}
    102
    103 		loadTest.getLoadTestLog().addEntry(
    104 				new LoadTestLogMessageEntry( "LoadTest started at " + new Date( startTime ) ));
    105
    106 		int startDelay = loadTest.getStartDelay();
    107 		if( startDelay >= 0 )
    108 		{
    109 			XProgressDialog progressDialog = UISupport.getDialogs().createProgressDialog( "Starting threads",
    110 						( int ) loadTest.getThreadCount(),
    111 						"", true );
    112 			try
    113 			{
    114 				progressDialog.run( new Worker.WorkerAdapter() {
    115
    116 					private List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
    117 					private boolean canceled;
    118
    119 					public Object construct( XProgressMonitor monitor )
    120 					{
    121 						int startDelay = loadTest.getStartDelay();
    122
    123 						for( int c = 0; c < loadTest.getThreadCount() && !canceled; c++ )
    124 						{
    125 							monitor.setProgress( 1, "Creating Virtual User " + (c+1) );
    126 							testCases.add( createTestCase() );
    127 						}
    128
    129 						threadsWaitingToStart = testCases.size();
    130 						int cnt = 0;
    131 						while( !testCases.isEmpty() && !canceled )
    132 						{
    133 							if( startDelay > 0 )
    134 							{
    135 								try
    136 								{
    137 									Thread.sleep( startDelay );
    138 								}
    139 								catch( InterruptedException e )
    140 								{
    141 									SoapUI.logError( e );
    142 								}
    143 							}
    144
    145 							if( status != Status.RUNNING || getProgress() >= 1 ||
    146 								(loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT &&
    147 											runners.size() >= loadTest.getTestLimit() ))
    148 								break;
    149
    150 							// could have been canceled..
    151 							if( !testCases.isEmpty() )
    152 							{
    153 								startTestCase( testCases.remove( 0 ));
    154 								monitor.setProgress( 1, "Started thread " + (++cnt) );
    155 								threadsWaitingToStart--;
    156 							}
    157 						}
    158
    159 						return null;
    160 					}
    161
    162 					public boolean onCancel()
    163 					{
    164 						cancel( "Stopped from UI during start-up" );
    165 						canceled = true;
    166 						while( !testCases.isEmpty() )
    167 							testCases.remove( 0 ).release();
    168
    169 						return false;
    170 					}} );
    171
    172 			}
    173 			catch( Exception e )
    174 			{
    175 				SoapUI.logError( e );
    176 			}
    177 		}
    178 		else
    179 		{
    180 			List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
    181 			for( int c = 0; c < loadTest.getThreadCount(); c++ )
    182 				testCases.add( createTestCase() );
    183
    184 			for( int c = 0; c < loadTest.getThreadCount(); c++ )
    185 			{
    186 				if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT &&
    187 							runners.size() >= loadTest.getTestLimit() )
    188 					break;
    189
    190 				startTestCase( testCases.get( c ) );
    191 			}
    192 		}
    193
    194 		if( status == Status.RUNNING )
    195 		{
    196 			for( LoadTestRunListener listener : listeners )
    197 			{
    198 				listener.loadTestStarted( this, context );
    199 			}
    200 		}
    201 		else
    202 		{
    203 			for( LoadTestRunListener listener : listeners )
    204 			{
    205 				listener.afterLoadTest( this, context );
    206 			}
    207 		}
    208 	}
    209
    210 	private TestCaseRunner startTestCase( WsdlTestCase testCase )
    211 	{
    212 		TestCaseRunner testCaseRunner = new TestCaseRunner( testCase, threadCount++ );
    213 		Thread thread = new Thread( threadGroup, testCaseRunner,
    214 					testCase.getName() + " - " + loadTest.getName() + " - ThreadIndex " + testCaseRunner.threadIndex );
    215 		thread.start();
    216 		runners.add( testCaseRunner );
    217 		return testCaseRunner;
    218 	}
    219
    220 	public synchronized void cancel( String reason )
    221 	{
    222 		if( status != Status.RUNNING )
    223 			return;
    224
    225 		this.reason = reason;
    226 		status = Status.CANCELED;
    227
    228 		TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
    229
    230 		for( TestCaseRunner runner : r )
    231 		{
    232 			runner.cancel( reason, true );
    233 		}
    234
    235 		String msg = "LoadTest [" + loadTest.getName()+ "] canceled";
    236 		if( reason != null )
    237 			msg += "; " + reason;
    238
    239 		loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
    240
    241 		for( LoadTestRunListener listener : listeners )
    242 		{
    243 			listener.loadTestStopped( this, context );
    244 		}
    245 	}
    246
    247 	public synchronized void fail( String reason )
    248 	{
    249 		if( status != Status.RUNNING )
    250 			return;
    251
    252 		this.reason = reason;
    253 		status = Status.FAILED;
    254
    255 		String msg = "LoadTest [" + loadTest.getName()+ "] failed";
    256 		if( reason != null )
    257 			msg += "; " + reason;
    258
    259 		loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry( msg ));
    260
    261 		for( LoadTestRunListener listener : listeners )
    262 		{
    263 			listener.loadTestStopped( this, context );
    264 		}
    265
    266 		TestCaseRunner[] r = runners.toArray( new TestCaseRunner[ runners.size()] );
    267
    268 		for( TestCaseRunner runner : r )
    269 		{
    270 			runner.cancel( reason, true );
    271 		}
    272 	}
    273
    274 	public void waitUntilFinished()
    275 	{
    276 		while( runners.size() > 0 || threadsWaitingToStart > 0 )
    277 		{
    278 			try
    279 			{
    280 				Thread.sleep( 200 );
    281 			}
    282 			catch (InterruptedException e)
    283 			{
    284 				SoapUI.logError( e );
    285 			}
    286 		}
    287 	}
    288
    289 	public void finishTestCase( String reason, WsdlTestCase testCase  )
    290 	{
    291 		for( TestCaseRunner runner : runners )
    292 		{
    293 			if( runner.getTestCase() == testCase )
    294 			{
    295 				runner.cancel( reason, false );
    296 				break;
    297 			}
    298 		}
    299 	}
    300
    301 	public synchronized void finishRunner( TestCaseRunner runner )
    302 	{
    303 		if( !runners.contains( runner ))
    304 		{
    305 			throw new RuntimeException( "Trying to finish unknown runner.. " );
    306 		}
    307
    308 		runners.remove( runner );
    309 		if( runners.size() == 0 && threadsWaitingToStart == 0 )
    310 		{
    311 			loadTest.removePropertyChangeListener( WsdlLoadTest.THREADCOUNT_PROPERTY,
    312 					internalPropertyChangeListener );
    313
    314 			if( status == Status.RUNNING )
    315 				status = Status.FINISHED;
    316
    317 			loadTest.getLoadTestLog().addEntry(
    318 					new LoadTestLogMessageEntry( "LoadTest ended at " + new Date( System.currentTimeMillis() ) ));
    319
    320 			for( LoadTestRunListener listener : listeners )
    321 			{
    322 				listener.afterLoadTest( this, context );
    323 			}
    324
    325 			listeners = null;
    326 			context.clear();
    327 		}
    328 	}
    329
    330 	public int getRunningThreadCount()
    331 	{
    332 		return runners.size();
    333 	}
    334
    335 	public float getProgress()
    336 	{
    337 		long testLimit = loadTest.getTestLimit();
    338 		if( testLimit == 0 )
    339 			return -1;
    340
    341 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
    342 			return (float)runCount / (float)testLimit;
    343
    344 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
    345 			return (float)getTimeTaken() / (float)(testLimit*1000);
    346
    347 		return -1;
    348 	}
    349
    350 	private synchronized boolean afterRun( TestCaseRunner runner )
    351 	{
    352 		if( status != Status.RUNNING )
    353 			return false;
    354
    355 		runCount++;
    356
    357 		if( loadTest.getTestLimit() < 1 ) return true;
    358
    359 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT )
    360 			return runCount + runners.size() + threadsWaitingToStart <= loadTest.getTestLimit();
    361
    362 		if( loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME )
    363 			return getTimeTaken() < loadTest.getTestLimit()*1000;
    364
    365 		return true;
    366 	}
    367
    368 	public class TestCaseRunner implements Runnable
    369 	{
    370 		private final WsdlTestCase testCase;
    371 		private boolean canceled;
    372 		private long runCount;
    373 		private WsdlTestCaseRunner runner;
    374 		private final int threadIndex;
    375
    376 		public TestCaseRunner(WsdlTestCase testCase, int threadIndex )
    377 		{
    378 			this.testCase = testCase;
    379 			this.threadIndex = threadIndex;
    380 		}
    381
    382 		public void run()
    383 		{
    384 			try
    385 			{
    386 				runner = new WsdlTestCaseRunner( testCase, PropertiesMap.EMPTY_MAP );
    387
    388 				while( !canceled )
    389 				{
    390 					try
    391 					{
    392 						runner.getRunContext().reset();
    393 						runner.getRunContext().setProperty( TestRunContext.THREAD_INDEX, threadIndex );
    394 						runner.getRunContext().setProperty( TestRunContext.RUN_COUNT, runCount );
    395 						runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_RUNNER, WsdlLoadTestRunner.this );
    396 						runner.getRunContext().setProperty( TestRunContext.LOAD_TEST_CONTEXT, context );
    397
    398 						runner.run();
    399 					}
    400 					catch( Throwable e )
    401 					{
    402 						System.err.println( "Error running testcase: " + e );
    403 						SoapUI.logError( e );
    404 					}
    405
    406 					runCount++;
    407
    408 					if( !afterRun( this ) )
    409 						break;
    410 				}
    411 			}
    412 			finally
    413 			{
    414 				finishRunner( this );
    415 				testCase.release();
    416 				testCase.removeTestRunListener( testRunListener );
    417 			}
    418 		}
    419
    420 		public void cancel( String reason, boolean cancelRunner )
    421 		{
    422 			if( runner != null && cancelRunner )
    423 				runner.cancel( reason );
    424
    425 			this.canceled = true;
    426 		}
    427
    428 		public boolean isCanceled()
    429 		{
    430 			return canceled;
    431 		}
    432
    433 		public long getRunCount()
    434 		{
    435 			return runCount;
    436 		}
    437
    438 		public WsdlTestCase getTestCase()
    439 		{
    440 			return testCase;
    441 		}
    442 	}
    443
    444 	public LoadTest getLoadTest()
    445 	{
    446 		return loadTest;
    447 	}
    448
    449 	public class InternalPropertyChangeListener implements PropertyChangeListener
    450 	{
    451 		public void propertyChange(PropertyChangeEvent evt)
    452 		{
    453 			updateThreadCount();
    454 		}
    455 	}
    456
    457 	public synchronized void updateThreadCount()
    458 	{
    459 		if( status != Status.RUNNING ) return;
    460
    461 		long newCount = loadTest.getThreadCount();
    462
    463 		// get list of active runners
    464 		Iterator<TestCaseRunner> iterator = runners.iterator();
    465 		List<TestCaseRunner> activeRunners = new ArrayList<TestCaseRunner>();
    466 		while( iterator.hasNext() )
    467 		{
    468 			TestCaseRunner runner = iterator.next();
    469 			if( !runner.isCanceled() )
    470 				activeRunners.add( runner );
    471 		}
    472
    473 		long diff = newCount-activeRunners.size();
    474
    475 		if( diff == 0 )
    476 			return;
    477
    478 		// cancel runners if thread count has been decreased
    479 		if( diff < 0 )
    480 		{
    481 			diff = Math.abs( diff );
    482 			for( int c = 0; c < diff && c < activeRunners.size(); c++ )
    483 			{
    484 				activeRunners.get( c ).cancel( "excessive thread", false );
    485 			}
    486 		}
    487 		// start new runners if thread count has been increased
    488 		else if( diff > 0 )
    489 		{
    490 			for( int c = 0; c < diff; c++ )
    491 			{
    492 				int startDelay = loadTest.getStartDelay();
    493 				if( startDelay > 0 )
    494 				{
    495 					try
    496 					{
    497 						Thread.sleep( startDelay );
    498 					}
    499 					catch( InterruptedException e )
    500 					{
    501 						SoapUI.logError( e );
    502 					}
    503 				}
    504
    505 				if( status == Status.RUNNING )
    506 					startTestCase( createTestCase() );
    507 			}
    508 		}
    509 	}
    510
    511 	/***
    512 	 * Creates a copy of the underlying WsdlTestCase with all LoadTests removed and configured for LoadTesting
    513 	 */
    514
    515 	private WsdlTestCase createTestCase()
    516 	{
    517 		WsdlTestCase testCase = loadTest.getTestCase();
    518
    519 		// clone config and remove and loadtests
    520 		TestCaseConfig config = (TestCaseConfig) testCase.getConfig().copy();
    521 		config.setLoadTestArray( new LoadTestConfig[0] );
    522
    523 		//	 clone entire testCase
    524 		WsdlTestCase tc = new WsdlTestCase( testCase.getTestSuite(), config, true );
    525 		tc.addTestRunListener( testRunListener );
    526 		Settings settings = tc.getSettings();
    527 		settings.setBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN ));
    528 		settings.setBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN, loadTest.getSettings().getBoolean( HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN ));
    529 		settings.setBoolean( HttpSettings.CLOSE_CONNECTIONS, loadTest.getSettings().getBoolean( HttpSettings.CLOSE_CONNECTIONS ));
    530
    531 		// dont discard.. the WsdlLoadTests internal listener will discard after asserting..
    532 		tc.setDiscardOkResults( false );
    533 		return tc;
    534 	}
    535
    536 	public String getReason()
    537 	{
    538 		return reason;
    539 	}
    540
    541 	public long getTimeTaken()
    542 	{
    543 		return System.currentTimeMillis()-startTime;
    544 	}
    545
    546 	private class InternalTestRunListener implements TestRunListener
    547 	{
    548 		public void beforeRun(TestRunner testRunner, TestRunContext runContext)
    549 		{
    550 			for( LoadTestRunListener listener : listeners )
    551 			{
    552 				listener.beforeTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
    553 			}
    554 		}
    555
    556 		public void beforeStep(TestRunner testRunner, TestRunContext runContext)
    557 		{
    558 			for( LoadTestRunListener listener : listeners )
    559 			{
    560 				listener.beforeTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, runContext.getCurrentStep() );
    561 			}
    562 		}
    563
    564 		public void afterStep(TestRunner testRunner, TestRunContext runContext, TestStepResult result)
    565 		{
    566 			for( LoadTestRunListener listener : listeners )
    567 			{
    568 				listener.afterTestStep( WsdlLoadTestRunner.this, context, testRunner, runContext, result );
    569 			}
    570 		}
    571
    572 		public void afterRun(TestRunner testRunner, TestRunContext runContext)
    573 		{
    574 			for( LoadTestRunListener listener : listeners )
    575 			{
    576 				listener.afterTestCase( WsdlLoadTestRunner.this, context, testRunner, runContext );
    577 			}
    578 		}
    579 	}
    580 }