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