/* * SoapUI, Copyright (C) 2004-2016 SmartBear Software * * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent * versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software distributed under the Licence is * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the Licence for the specific language governing permissions and limitations * under the Licence. */ package com.eviware.soapui.impl.wsdl.loadtest; import com.eviware.soapui.SoapUI; import com.eviware.soapui.config.LoadTestConfig; import com.eviware.soapui.config.LoadTestLimitTypesConfig; import com.eviware.soapui.config.SecurityTestConfig; import com.eviware.soapui.config.TestCaseConfig; import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogMessageEntry; import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase; import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner; import com.eviware.soapui.model.settings.Settings; import com.eviware.soapui.model.support.TestRunListenerAdapter; import com.eviware.soapui.model.testsuite.LoadTestRunListener; import com.eviware.soapui.model.testsuite.LoadTestRunner; import com.eviware.soapui.model.testsuite.TestCaseRunContext; import com.eviware.soapui.model.testsuite.TestCaseRunner; import com.eviware.soapui.model.testsuite.TestRunContext; import com.eviware.soapui.model.testsuite.TestRunnable; import com.eviware.soapui.model.testsuite.TestStep; import com.eviware.soapui.model.testsuite.TestStepResult; import com.eviware.soapui.settings.HttpSettings; import com.eviware.soapui.settings.WsdlSettings; import com.eviware.soapui.support.UISupport; import com.eviware.soapui.support.types.StringToObjectMap; import com.eviware.x.dialogs.Worker; import com.eviware.x.dialogs.XProgressDialog; import com.eviware.x.dialogs.XProgressMonitor; import org.apache.xmlbeans.XmlException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** * TestRunner for load-tests. * * @author Ole.Matzura * @todo statistics should be calculated first after all threads have been * started.. */ public class WsdlLoadTestRunner implements LoadTestRunner { private final WsdlLoadTest loadTest; private Set<InternalTestCaseRunner> runners = new HashSet<InternalTestCaseRunner>(); private long startTime = 0; private InternalPropertyChangeListener internalPropertyChangeListener = new InternalPropertyChangeListener(); private InternalTestRunListener testRunListener = new InternalTestRunListener(); private long runCount; private Status status; private WsdlLoadTestContext context; private String reason; private int threadCount; private int threadsWaitingToStart; private int startedCount; private boolean hasTearedDown; private TestCaseStarter testCaseStarter; private boolean stopped; private TestCaseConfig blueprintConfig; public WsdlLoadTestRunner(WsdlLoadTest test) { this.loadTest = test; status = Status.INITIALIZED; } public Status getStatus() { return status; } void start() { loadTest.getTestCase().beforeSave(); runners.clear(); runCount = 0; threadCount = 0; threadsWaitingToStart = 0; startedCount = 0; context = new WsdlLoadTestContext(this); try { loadTest.runSetupScript(context, this); } catch (Exception e1) { SoapUI.logError(e1); } for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { try { listener.beforeLoadTest(this, context); } catch (Throwable e) { SoapUI.logError(e); } } status = Status.RUNNING; loadTest.addPropertyChangeListener(WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener); XProgressDialog progressDialog = UISupport.getDialogs().createProgressDialog("Starting threads", (int) loadTest.getThreadCount(), "", true); try { testCaseStarter = new TestCaseStarter(); progressDialog.run(testCaseStarter); } catch (Exception e) { SoapUI.logError(e); } if (status == Status.RUNNING) { for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { listener.loadTestStarted(this, context); } startStrategyThread(); } else { stop(); } } /** * Starts thread the calls the current strategy to recalculate */ private void startStrategyThread() { new Thread(new Runnable() { public void run() { while (getStatus() == Status.RUNNING) { try { loadTest.getLoadStrategy().recalculate(WsdlLoadTestRunner.this, context); long strategyInterval = loadTest.getStrategyInterval(); if (strategyInterval < 1) { strategyInterval = WsdlLoadTest.DEFAULT_STRATEGY_INTERVAL; } Thread.sleep(strategyInterval); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } private InternalTestCaseRunner startTestCase(WsdlTestCase testCase) { InternalTestCaseRunner testCaseRunner = new InternalTestCaseRunner(testCase, threadCount++); SoapUI.getThreadPool().submit(testCaseRunner); runners.add(testCaseRunner); return testCaseRunner; } public synchronized void cancel(String reason) { if (status != Status.RUNNING) { return; } this.reason = reason; status = Status.CANCELED; if (testCaseStarter != null) { testCaseStarter.stop(); } InternalTestCaseRunner[] r = runners.toArray(new InternalTestCaseRunner[runners.size()]); for (InternalTestCaseRunner runner : r) { runner.cancel(reason, true); } String msg = "LoadTest [" + loadTest.getName() + "] canceled"; if (reason != null) { msg += "; " + reason; } loadTest.getLoadTestLog().addEntry(new LoadTestLogMessageEntry(msg)); for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { listener.loadTestStopped(this, context); } if (r.length == 0) { stop(); } } public synchronized void fail(String reason) { boolean wasRunning = (status == Status.RUNNING); this.reason = reason; status = Status.FAILED; if (!wasRunning) { return; } if (testCaseStarter != null) { testCaseStarter.stop(); } String msg = "LoadTest [" + loadTest.getName() + "] failed"; if (reason != null) { msg += "; " + reason; } loadTest.getLoadTestLog().addEntry(new LoadTestLogMessageEntry(msg)); for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { try { listener.loadTestStopped(this, context); } catch (Throwable e) { SoapUI.logError(e); } } InternalTestCaseRunner[] r = runners.toArray(new InternalTestCaseRunner[runners.size()]); for (InternalTestCaseRunner runner : r) { runner.cancel(reason, true); } if (r.length == 0) { stop(); } } private synchronized void tearDown() { if (hasTearedDown) { return; } try { loadTest.runTearDownScript(context, this); } catch (Exception e1) { SoapUI.logError(e1); } hasTearedDown = true; } public Status waitUntilFinished() { while (runners.size() > 0 || threadsWaitingToStart > 0 || !hasStopped()) { try { Thread.sleep(200); } catch (InterruptedException e) { SoapUI.logError(e); } } return getStatus(); } public void finishTestCase(String reason, WsdlTestCase testCase) { for (InternalTestCaseRunner runner : runners) { if (runner.getTestCase() == testCase) { runner.cancel(reason, false); break; } } } public synchronized void finishRunner(InternalTestCaseRunner runner) { if (!runners.contains(runner)) { throw new RuntimeException("Trying to finish unknown runner.. "); } runners.remove(runner); if (getProgress() >= 1 || status != Status.RUNNING) { stop(); } } private synchronized void stop() { if (stopped) { return; } loadTest.removePropertyChangeListener(WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener); if (testCaseStarter != null) { testCaseStarter.stop(); } if (status == Status.RUNNING) { status = Status.FINISHED; } loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry("LoadTest ended at " + new Date(System.currentTimeMillis()))); try { tearDown(); } catch (Throwable e) { SoapUI.logError(e); } for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { try { listener.afterLoadTest(this, context); } catch (Throwable e) { SoapUI.logError(e); } } context.clear(); stopped = true; blueprintConfig = null; } public boolean hasStopped() { return stopped; } public int getRunningThreadCount() { return runners.size(); } public float getProgress() { long testLimit = loadTest.getTestLimit(); if (testLimit == 0) { return -1; } if (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT) { return (float) runCount / (float) testLimit; } if (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT_PER_THREAD) { return (float) runCount / (float) (testLimit * loadTest.getThreadCount()); } if (loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME) { return startTime == 0 ? 0 : (float) getTimeTaken() / (float) (testLimit * 1000); } return -1; } private synchronized boolean afterRun(InternalTestCaseRunner runner) { if (status != Status.RUNNING) { return false; } runCount++; if (loadTest.getTestLimit() < 1) { return true; } if (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT_PER_THREAD) { return runner.getRunCount() < loadTest.getTestLimit(); } if (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT) { return runCount + runners.size() + threadsWaitingToStart <= loadTest.getTestLimit(); } if (loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME) { return getTimeTaken() < loadTest.getTestLimit() * 1000; } return true; } private final class TestCaseStarter extends Worker.WorkerAdapter { private List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>(); private boolean canceled; public Object construct(XProgressMonitor monitor) { int startDelay = loadTest.getStartDelay(); for (int c = 0; c < loadTest.getThreadCount() && !canceled; c++) { monitor.setProgress(1, "Creating Virtual User " + (c + 1)); testCases.add(createTestCase()); } startTime = System.currentTimeMillis(); if (canceled) { loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry("LoadTest canceled during startup at " + new Date(startTime))); return null; } loadTest.getLoadTestLog().addEntry( new LoadTestLogMessageEntry("LoadTest started at " + new Date(startTime))); threadsWaitingToStart = testCases.size(); int cnt = 0; while (!testCases.isEmpty() && !canceled) { if (startDelay > 0) { try { Thread.sleep(startDelay); } catch (InterruptedException e) { SoapUI.logError(e); } } if (status != Status.RUNNING || getProgress() >= 1) { while (!testCases.isEmpty()) { testCases.remove(0).release(); } threadsWaitingToStart = 0; break; } // could have been canceled.. if (!testCases.isEmpty()) { startTestCase(testCases.remove(0)); monitor.setProgress(1, "Started thread " + (++cnt)); threadsWaitingToStart--; } } return null; } public boolean onCancel() { cancel("Stopped from UI during start-up"); stop(); return false; } public void stop() { if (!canceled) { canceled = true; while (!testCases.isEmpty()) { testCases.remove(0).release(); } threadsWaitingToStart = 0; } } } public class InternalTestCaseRunner implements Runnable { private final WsdlTestCase testCase; private boolean canceled; private long runCount; private WsdlTestCaseRunner runner; private final int threadIndex; public InternalTestCaseRunner(WsdlTestCase testCase, int threadIndex) { this.testCase = testCase; this.threadIndex = threadIndex; } public void run() { try { if (System.getProperty("soapui.enablenamedthreads") != null) { Thread.currentThread().setName( testCase.getName() + " " + loadTest.getName() + " ThreadIndex = " + threadIndex); } runner = new WsdlTestCaseRunner(testCase, new StringToObjectMap()); while (!canceled) { try { runner.getRunContext().reset(); runner.getRunContext().setProperty(TestCaseRunContext.THREAD_INDEX, threadIndex); runner.getRunContext().setProperty(TestCaseRunContext.RUN_COUNT, runCount); runner.getRunContext().setProperty(TestCaseRunContext.LOAD_TEST_RUNNER, WsdlLoadTestRunner.this); runner.getRunContext().setProperty(TestCaseRunContext.LOAD_TEST_CONTEXT, context); synchronized (this) { runner.getRunContext().setProperty(TestCaseRunContext.TOTAL_RUN_COUNT, startedCount++); } runner.run(); } catch (Throwable e) { System.err.println("Error running testcase: " + e); SoapUI.logError(e); } runCount++; if (!afterRun(this)) { break; } } } finally { finishRunner(this); testCase.release(); testCase.removeTestRunListener(testRunListener); } } public void cancel(String reason, boolean cancelRunner) { if (runner != null && cancelRunner) { runner.cancel(reason); } this.canceled = true; } public boolean isCanceled() { return canceled; } public long getRunCount() { return runCount; } public WsdlTestCase getTestCase() { return testCase; } } public WsdlLoadTest getLoadTest() { return loadTest; } public class InternalPropertyChangeListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { updateThreadCount(); } } public synchronized void updateThreadCount() { if (status != Status.RUNNING) { return; } long newCount = loadTest.getThreadCount(); // get list of active runners Iterator<InternalTestCaseRunner> iterator = runners.iterator(); List<InternalTestCaseRunner> activeRunners = new ArrayList<InternalTestCaseRunner>(); while (iterator.hasNext()) { InternalTestCaseRunner runner = iterator.next(); if (!runner.isCanceled()) { activeRunners.add(runner); } } long diff = newCount - activeRunners.size(); if (diff == 0) { return; } // cancel runners if thread count has been decreased if (diff < 0 && loadTest.getCancelExcessiveThreads()) { diff = Math.abs(diff); for (int c = 0; c < diff && c < activeRunners.size(); c++) { activeRunners.get(c).cancel("excessive thread", false); } } // start new runners if thread count has been increased else if (diff > 0) { for (int c = 0; c < diff; c++) { int startDelay = loadTest.getStartDelay(); if (startDelay > 0) { try { Thread.sleep(startDelay); } catch (InterruptedException e) { SoapUI.logError(e); } } if (status == Status.RUNNING) { startTestCase(createTestCase()); } } } } /** * Creates a copy of the underlying WsdlTestCase with all LoadTests removed * and configured for LoadTesting */ private synchronized WsdlTestCase createTestCase() { WsdlTestCase testCase = loadTest.getTestCase(); TestCaseConfig config = null; if (blueprintConfig == null) { try { blueprintConfig = TestCaseConfig.Factory.parse(testCase.getConfig().xmlText()); blueprintConfig.setLoadTestArray(new LoadTestConfig[0]); blueprintConfig.setSecurityTestArray(new SecurityTestConfig[0]); } catch (XmlException e) { e.printStackTrace(); } } config = (TestCaseConfig) blueprintConfig.copy(); // clone entire testCase WsdlTestCase tc = testCase.getTestSuite().buildTestCase(config, true); tc.afterLoad(); tc.addTestRunListener(testRunListener); Settings settings = tc.getSettings(); settings.setBoolean(HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN, loadTest.getSettings().getBoolean(HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN)); settings.setBoolean(HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN, loadTest.getSettings().getBoolean(HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN)); settings.setBoolean(HttpSettings.CLOSE_CONNECTIONS, loadTest.getSettings().getBoolean(HttpSettings.CLOSE_CONNECTIONS)); // disable default pretty-printing since it takes time settings.setBoolean(WsdlSettings.PRETTY_PRINT_RESPONSE_MESSAGES, false); // don't discard.. the WsdlLoadTests internal listener will discard after // asserting.. tc.setDiscardOkResults(false); tc.setMaxResults(0); return tc; } public String getReason() { return reason; } public long getTimeTaken() { return System.currentTimeMillis() - startTime; } private class InternalTestRunListener extends TestRunListenerAdapter { public void beforeRun(TestCaseRunner testRunner, TestCaseRunContext runContext) { if (getProgress() > 1 && loadTest.getCancelOnReachedLimit()) { testRunner.cancel("LoadTest Limit reached"); } else { for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { listener.beforeTestCase(WsdlLoadTestRunner.this, context, testRunner, runContext); } } } public void beforeStep(TestCaseRunner testRunner, TestCaseRunContext runContext, TestStep testStep) { if (getProgress() > 1 && loadTest.getCancelOnReachedLimit()) { testRunner.cancel("LoadTest Limit reached"); } else if (runContext.getCurrentStep() != null) { for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { listener.beforeTestStep(WsdlLoadTestRunner.this, context, testRunner, runContext, testStep); } } } public void afterStep(TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result) { for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { listener.afterTestStep(WsdlLoadTestRunner.this, context, testRunner, runContext, result); } } public void afterRun(TestCaseRunner testRunner, TestCaseRunContext runContext) { for (LoadTestRunListener listener : loadTest.getLoadTestRunListeners()) { listener.afterTestCase(WsdlLoadTestRunner.this, context, testRunner, runContext); } } } public boolean isRunning() { return status == Status.RUNNING; } public TestRunContext getRunContext() { return context; } public long getStartTime() { return startTime; } public void start(boolean async) { start(); } public TestRunnable getTestRunnable() { return loadTest; } public void release() { loadTest.removePropertyChangeListener(WsdlLoadTest.THREADCOUNT_PROPERTY, internalPropertyChangeListener); } }