001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.impl.wsdl.loadtest;
014:
015: import java.beans.PropertyChangeEvent;
016: import java.beans.PropertyChangeListener;
017: import java.util.ArrayList;
018: import java.util.Date;
019: import java.util.HashSet;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Set;
023:
024: import com.eviware.soapui.SoapUI;
025: import com.eviware.soapui.config.LoadTestConfig;
026: import com.eviware.soapui.config.LoadTestLimitTypesConfig;
027: import com.eviware.soapui.config.TestCaseConfig;
028: import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogMessageEntry;
029: import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
030: import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
031: import com.eviware.soapui.model.settings.Settings;
032: import com.eviware.soapui.model.support.PropertiesMap;
033: import com.eviware.soapui.model.testsuite.LoadTest;
034: import com.eviware.soapui.model.testsuite.LoadTestRunListener;
035: import com.eviware.soapui.model.testsuite.LoadTestRunner;
036: import com.eviware.soapui.model.testsuite.TestRunContext;
037: import com.eviware.soapui.model.testsuite.TestRunListener;
038: import com.eviware.soapui.model.testsuite.TestRunner;
039: import com.eviware.soapui.model.testsuite.TestStepResult;
040: import com.eviware.soapui.settings.HttpSettings;
041: import com.eviware.soapui.support.UISupport;
042: import com.eviware.x.dialogs.Worker;
043: import com.eviware.x.dialogs.XProgressDialog;
044: import com.eviware.x.dialogs.XProgressMonitor;
045:
046: /**
047: * TestRunner for load-tests.
048: *
049: * @todo statistics should be calculated first after all threads have been started..
050: *
051: * @author Ole.Matzura
052: */
053:
054: public class WsdlLoadTestRunner implements LoadTestRunner {
055: private final WsdlLoadTest loadTest;
056: private ThreadGroup threadGroup;
057: private Set<TestCaseRunner> runners = new HashSet<TestCaseRunner>();
058: private long startTime = 0;
059: private InternalPropertyChangeListener internalPropertyChangeListener = new InternalPropertyChangeListener();
060: private InternalTestRunListener testRunListener = new InternalTestRunListener();
061: private LoadTestRunListener[] listeners;
062: private long runCount;
063: private Status status;
064: private WsdlLoadTestContext context;
065: private String reason;
066: private int threadCount;
067: private int threadsWaitingToStart;
068:
069: public WsdlLoadTestRunner(WsdlLoadTest test) {
070: this .loadTest = test;
071: threadGroup = new ThreadGroup(loadTest.getName());
072:
073: status = Status.INITIALIZED;
074: }
075:
076: public Status getStatus() {
077: return status;
078: }
079:
080: void start() {
081: loadTest.getTestCase().onSave();
082: startTime = System.currentTimeMillis();
083:
084: runners.clear();
085: loadTest.addPropertyChangeListener(
086: WsdlLoadTest.THREADCOUNT_PROPERTY,
087: internalPropertyChangeListener);
088: runCount = 0;
089: threadCount = 0;
090: threadsWaitingToStart = 0;
091: context = new WsdlLoadTestContext(this );
092:
093: status = Status.RUNNING;
094:
095: listeners = loadTest.getLoadTestRunListeners();
096: for (LoadTestRunListener listener : listeners) {
097: listener.beforeLoadTest(this , context);
098: }
099:
100: loadTest.getLoadTestLog().addEntry(
101: new LoadTestLogMessageEntry("LoadTest started at "
102: + new Date(startTime)));
103:
104: int startDelay = loadTest.getStartDelay();
105: if (startDelay >= 0) {
106: XProgressDialog progressDialog = UISupport.getDialogs()
107: .createProgressDialog("Starting threads",
108: (int) loadTest.getThreadCount(), "", true);
109: try {
110: progressDialog.run(new Worker.WorkerAdapter() {
111:
112: private List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
113: private boolean canceled;
114:
115: public Object construct(XProgressMonitor monitor) {
116: int startDelay = loadTest.getStartDelay();
117:
118: for (int c = 0; c < loadTest.getThreadCount()
119: && !canceled; c++) {
120: monitor.setProgress(1,
121: "Creating Virtual User " + (c + 1));
122: testCases.add(createTestCase());
123: }
124:
125: threadsWaitingToStart = testCases.size();
126: int cnt = 0;
127: while (!testCases.isEmpty() && !canceled) {
128: if (startDelay > 0) {
129: try {
130: Thread.sleep(startDelay);
131: } catch (InterruptedException e) {
132: SoapUI.logError(e);
133: }
134: }
135:
136: if (status != Status.RUNNING
137: || getProgress() >= 1
138: || (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT && runners
139: .size() >= loadTest
140: .getTestLimit()))
141: break;
142:
143: // could have been canceled..
144: if (!testCases.isEmpty()) {
145: startTestCase(testCases.remove(0));
146: monitor.setProgress(1,
147: "Started thread " + (++cnt));
148: threadsWaitingToStart--;
149: }
150: }
151:
152: return null;
153: }
154:
155: public boolean onCancel() {
156: cancel("Stopped from UI during start-up");
157: canceled = true;
158: while (!testCases.isEmpty())
159: testCases.remove(0).release();
160:
161: return false;
162: }
163: });
164:
165: } catch (Exception e) {
166: SoapUI.logError(e);
167: }
168: } else {
169: List<WsdlTestCase> testCases = new ArrayList<WsdlTestCase>();
170: for (int c = 0; c < loadTest.getThreadCount(); c++)
171: testCases.add(createTestCase());
172:
173: for (int c = 0; c < loadTest.getThreadCount(); c++) {
174: if (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT
175: && runners.size() >= loadTest.getTestLimit())
176: break;
177:
178: startTestCase(testCases.get(c));
179: }
180: }
181:
182: if (status == Status.RUNNING) {
183: for (LoadTestRunListener listener : listeners) {
184: listener.loadTestStarted(this , context);
185: }
186: } else {
187: for (LoadTestRunListener listener : listeners) {
188: listener.afterLoadTest(this , context);
189: }
190: }
191: }
192:
193: private TestCaseRunner startTestCase(WsdlTestCase testCase) {
194: TestCaseRunner testCaseRunner = new TestCaseRunner(testCase,
195: threadCount++);
196: Thread thread = new Thread(threadGroup, testCaseRunner,
197: testCase.getName() + " - " + loadTest.getName()
198: + " - ThreadIndex "
199: + testCaseRunner.threadIndex);
200: thread.start();
201: runners.add(testCaseRunner);
202: return testCaseRunner;
203: }
204:
205: public synchronized void cancel(String reason) {
206: if (status != Status.RUNNING)
207: return;
208:
209: this .reason = reason;
210: status = Status.CANCELED;
211:
212: TestCaseRunner[] r = runners.toArray(new TestCaseRunner[runners
213: .size()]);
214:
215: for (TestCaseRunner runner : r) {
216: runner.cancel(reason, true);
217: }
218:
219: String msg = "LoadTest [" + loadTest.getName() + "] canceled";
220: if (reason != null)
221: msg += "; " + reason;
222:
223: loadTest.getLoadTestLog().addEntry(
224: new LoadTestLogMessageEntry(msg));
225:
226: for (LoadTestRunListener listener : listeners) {
227: listener.loadTestStopped(this , context);
228: }
229: }
230:
231: public synchronized void fail(String reason) {
232: if (status != Status.RUNNING)
233: return;
234:
235: this .reason = reason;
236: status = Status.FAILED;
237:
238: String msg = "LoadTest [" + loadTest.getName() + "] failed";
239: if (reason != null)
240: msg += "; " + reason;
241:
242: loadTest.getLoadTestLog().addEntry(
243: new LoadTestLogMessageEntry(msg));
244:
245: for (LoadTestRunListener listener : listeners) {
246: listener.loadTestStopped(this , context);
247: }
248:
249: TestCaseRunner[] r = runners.toArray(new TestCaseRunner[runners
250: .size()]);
251:
252: for (TestCaseRunner runner : r) {
253: runner.cancel(reason, true);
254: }
255: }
256:
257: public void waitUntilFinished() {
258: while (runners.size() > 0 || threadsWaitingToStart > 0) {
259: try {
260: Thread.sleep(200);
261: } catch (InterruptedException e) {
262: SoapUI.logError(e);
263: }
264: }
265: }
266:
267: public void finishTestCase(String reason, WsdlTestCase testCase) {
268: for (TestCaseRunner runner : runners) {
269: if (runner.getTestCase() == testCase) {
270: runner.cancel(reason, false);
271: break;
272: }
273: }
274: }
275:
276: public synchronized void finishRunner(TestCaseRunner runner) {
277: if (!runners.contains(runner)) {
278: throw new RuntimeException(
279: "Trying to finish unknown runner.. ");
280: }
281:
282: runners.remove(runner);
283: if (runners.size() == 0 && threadsWaitingToStart == 0) {
284: loadTest.removePropertyChangeListener(
285: WsdlLoadTest.THREADCOUNT_PROPERTY,
286: internalPropertyChangeListener);
287:
288: if (status == Status.RUNNING)
289: status = Status.FINISHED;
290:
291: loadTest.getLoadTestLog().addEntry(
292: new LoadTestLogMessageEntry("LoadTest ended at "
293: + new Date(System.currentTimeMillis())));
294:
295: for (LoadTestRunListener listener : listeners) {
296: listener.afterLoadTest(this , context);
297: }
298:
299: listeners = null;
300: context.clear();
301: }
302: }
303:
304: public int getRunningThreadCount() {
305: return runners.size();
306: }
307:
308: public float getProgress() {
309: long testLimit = loadTest.getTestLimit();
310: if (testLimit == 0)
311: return -1;
312:
313: if (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT)
314: return (float) runCount / (float) testLimit;
315:
316: if (loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME)
317: return (float) getTimeTaken() / (float) (testLimit * 1000);
318:
319: return -1;
320: }
321:
322: private synchronized boolean afterRun(TestCaseRunner runner) {
323: if (status != Status.RUNNING)
324: return false;
325:
326: runCount++;
327:
328: if (loadTest.getTestLimit() < 1)
329: return true;
330:
331: if (loadTest.getLimitType() == LoadTestLimitTypesConfig.COUNT)
332: return runCount + runners.size() + threadsWaitingToStart <= loadTest
333: .getTestLimit();
334:
335: if (loadTest.getLimitType() == LoadTestLimitTypesConfig.TIME)
336: return getTimeTaken() < loadTest.getTestLimit() * 1000;
337:
338: return true;
339: }
340:
341: public class TestCaseRunner implements Runnable {
342: private final WsdlTestCase testCase;
343: private boolean canceled;
344: private long runCount;
345: private WsdlTestCaseRunner runner;
346: private final int threadIndex;
347:
348: public TestCaseRunner(WsdlTestCase testCase, int threadIndex) {
349: this .testCase = testCase;
350: this .threadIndex = threadIndex;
351: }
352:
353: public void run() {
354: try {
355: runner = new WsdlTestCaseRunner(testCase,
356: PropertiesMap.EMPTY_MAP);
357:
358: while (!canceled) {
359: try {
360: runner.getRunContext().reset();
361: runner.getRunContext().setProperty(
362: TestRunContext.THREAD_INDEX,
363: threadIndex);
364: runner.getRunContext().setProperty(
365: TestRunContext.RUN_COUNT, runCount);
366: runner.getRunContext().setProperty(
367: TestRunContext.LOAD_TEST_RUNNER,
368: WsdlLoadTestRunner.this );
369: runner.getRunContext().setProperty(
370: TestRunContext.LOAD_TEST_CONTEXT,
371: context);
372:
373: runner.run();
374: } catch (Throwable e) {
375: System.err.println("Error running testcase: "
376: + e);
377: SoapUI.logError(e);
378: }
379:
380: runCount++;
381:
382: if (!afterRun(this ))
383: break;
384: }
385: } finally {
386: finishRunner(this );
387: testCase.release();
388: testCase.removeTestRunListener(testRunListener);
389: }
390: }
391:
392: public void cancel(String reason, boolean cancelRunner) {
393: if (runner != null && cancelRunner)
394: runner.cancel(reason);
395:
396: this .canceled = true;
397: }
398:
399: public boolean isCanceled() {
400: return canceled;
401: }
402:
403: public long getRunCount() {
404: return runCount;
405: }
406:
407: public WsdlTestCase getTestCase() {
408: return testCase;
409: }
410: }
411:
412: public LoadTest getLoadTest() {
413: return loadTest;
414: }
415:
416: public class InternalPropertyChangeListener implements
417: PropertyChangeListener {
418: public void propertyChange(PropertyChangeEvent evt) {
419: updateThreadCount();
420: }
421: }
422:
423: public synchronized void updateThreadCount() {
424: if (status != Status.RUNNING)
425: return;
426:
427: long newCount = loadTest.getThreadCount();
428:
429: // get list of active runners
430: Iterator<TestCaseRunner> iterator = runners.iterator();
431: List<TestCaseRunner> activeRunners = new ArrayList<TestCaseRunner>();
432: while (iterator.hasNext()) {
433: TestCaseRunner runner = iterator.next();
434: if (!runner.isCanceled())
435: activeRunners.add(runner);
436: }
437:
438: long diff = newCount - activeRunners.size();
439:
440: if (diff == 0)
441: return;
442:
443: // cancel runners if thread count has been decreased
444: if (diff < 0) {
445: diff = Math.abs(diff);
446: for (int c = 0; c < diff && c < activeRunners.size(); c++) {
447: activeRunners.get(c).cancel("excessive thread", false);
448: }
449: }
450: // start new runners if thread count has been increased
451: else if (diff > 0) {
452: for (int c = 0; c < diff; c++) {
453: int startDelay = loadTest.getStartDelay();
454: if (startDelay > 0) {
455: try {
456: Thread.sleep(startDelay);
457: } catch (InterruptedException e) {
458: SoapUI.logError(e);
459: }
460: }
461:
462: if (status == Status.RUNNING)
463: startTestCase(createTestCase());
464: }
465: }
466: }
467:
468: /**
469: * Creates a copy of the underlying WsdlTestCase with all LoadTests removed and configured for LoadTesting
470: */
471:
472: private WsdlTestCase createTestCase() {
473: WsdlTestCase testCase = loadTest.getTestCase();
474:
475: // clone config and remove and loadtests
476: TestCaseConfig config = (TestCaseConfig) testCase.getConfig()
477: .copy();
478: config.setLoadTestArray(new LoadTestConfig[0]);
479:
480: // clone entire testCase
481: WsdlTestCase tc = new WsdlTestCase(testCase.getTestSuite(),
482: config, true);
483: tc.addTestRunListener(testRunListener);
484: Settings settings = tc.getSettings();
485: settings.setBoolean(HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN,
486: loadTest.getSettings().getBoolean(
487: HttpSettings.INCLUDE_REQUEST_IN_TIME_TAKEN));
488: settings.setBoolean(
489: HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN,
490: loadTest.getSettings().getBoolean(
491: HttpSettings.INCLUDE_RESPONSE_IN_TIME_TAKEN));
492: settings.setBoolean(HttpSettings.CLOSE_CONNECTIONS, loadTest
493: .getSettings().getBoolean(
494: HttpSettings.CLOSE_CONNECTIONS));
495:
496: // dont discard.. the WsdlLoadTests internal listener will discard after asserting..
497: tc.setDiscardOkResults(false);
498: return tc;
499: }
500:
501: public String getReason() {
502: return reason;
503: }
504:
505: public long getTimeTaken() {
506: return System.currentTimeMillis() - startTime;
507: }
508:
509: private class InternalTestRunListener implements TestRunListener {
510: public void beforeRun(TestRunner testRunner,
511: TestRunContext runContext) {
512: for (LoadTestRunListener listener : listeners) {
513: listener.beforeTestCase(WsdlLoadTestRunner.this ,
514: context, testRunner, runContext);
515: }
516: }
517:
518: public void beforeStep(TestRunner testRunner,
519: TestRunContext runContext) {
520: for (LoadTestRunListener listener : listeners) {
521: listener.beforeTestStep(WsdlLoadTestRunner.this ,
522: context, testRunner, runContext, runContext
523: .getCurrentStep());
524: }
525: }
526:
527: public void afterStep(TestRunner testRunner,
528: TestRunContext runContext, TestStepResult result) {
529: for (LoadTestRunListener listener : listeners) {
530: listener.afterTestStep(WsdlLoadTestRunner.this ,
531: context, testRunner, runContext, result);
532: }
533: }
534:
535: public void afterRun(TestRunner testRunner,
536: TestRunContext runContext) {
537: for (LoadTestRunListener listener : listeners) {
538: listener.afterTestCase(WsdlLoadTestRunner.this,
539: context, testRunner, runContext);
540: }
541: }
542: }
543: }
|