1  /**
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16   
    17  import org.as2lib.app.exec.AbstractProcess;
    18  import org.as2lib.app.exec.Processor;
    19  import org.as2lib.app.exec.StepByStepProcess;
    20  import org.as2lib.data.holder.Queue;
    21  import org.as2lib.data.holder.queue.LinearQueue;
    22  import org.as2lib.test.unit.ExecutionInfo;
    23  import org.as2lib.test.unit.TestCase;
    24  import org.as2lib.test.unit.TestCaseMethodInfo;
    25  import org.as2lib.test.unit.TestCaseResult;
    26  import org.as2lib.test.unit.TestRunner;
    27  import org.as2lib.test.unit.TestResult;
    28  import org.as2lib.test.unit.info.InstantiationError;
    29  import org.as2lib.test.unit.info.ExecutionError;
    30  import org.as2lib.test.unit.info.SetUpError;
    31  import org.as2lib.test.unit.info.TearDownError;
    32  import org.as2lib.util.ClassUtil;
    33  import org.as2lib.util.StopWatch;
    34  import org.as2lib.util.StringUtil;
    35  
    36  /**
    37   * {@code TestCaseRunner} is the implementation for the execution of {@link TestCase}s.
    38   * 
    39   * <p>It executes and handles all operations to process the certain {@code TestCase}.
    40   * 
    41   * <p>Usually you do not get in touch with the {@code TestCaseRunner} because any
    42   * {@code TestCase} handles it automatically.
    43   * 
    44   * <p>As its a implementation of {@link TestRunner} it is possible to add any
    45   * {@link org.as2lib.app.exec.ProcessListener} as listener to the execution of
    46   * the {@code TestCaseRunner}.
    47   * 
    48   * @author HeideggerMartin
    49   * @version 1.0
    50   */
    51  class org.as2lib.test.unit.TestCaseRunner
    52  			extends AbstractProcess
    53  			implements TestRunner, StepByStepProcess {
    54  
    55  	/** State if no method has been started. */
    56  	private var STATE_NOT_STARTED:Number = 1;
    57  	
    58  	/** State if the instance has been created. */
    59  	private var STATE_TEST_CREATED:Number = 2;
    60  	
    61  	/** State if setUp has been executed. */
    62  	private var STATE_SET_UP_FINISHED:Number = 3;
    63  	
    64  	/** State if the method has been executed. */
    65  	private var STATE_EXECUTION_FINISHED:Number = 4;
    66  	
    67  	/** State if tearDown has been executed. */
    68  	private var STATE_TEAR_DOWN_FINISHED:Number = 5;
    69  
    70  	/** Result to the execution. */
    71  	private var testResult:TestCaseResult;
    72  	
    73  	/** Queue that contains the methods to execute. */
    74  	private var openTestCaseMethods:Queue;
    75  	
    76  	/**
    77  	 * Information for the current executing method.
    78  	 * Since its possible to pause/resume the process its necessary to safe it
    79  	 * in instance scope.
    80  	 */
    81  	private var methodInfo:TestCaseMethodInfo;
    82  	
    83  	/**
    84  	 * State of the execution of the method.
    85  	 * Since its possible to pause/resume the process its necessary to safe at
    86  	 * what point of execution it had paused.
    87  	 */
    88  	private var methodState:Number;
    89  	
    90  	/** Instance for the execution of the method. */
    91  	private var testCaseInstance:TestCase;
    92  	
    93  	/** StopWatch related to the test (saved in instance scope because of performance). */
    94  	private var sW:StopWatch;
    95  	
    96  	/**
    97  	 * Constructs a new {@code TestCaseRunner}.
    98  	 * 
    99  	 * @param testCase {@code TestCase} that should be executed.
   100  	 */
   101  	function TestCaseRunner(testCase:TestCase) {
   102  		testResult = new TestCaseResult(testCase);
   103  		methodState = STATE_NOT_STARTED;
   104  		openTestCaseMethods = new LinearQueue(testResult.getMethodInfos());
   105  	}
   106  	
   107  	/** 
   108  	 * Returns the {@code TestCaseResult} to the executed {@code TestCase}.
   109  	 * 
   110  	 * <p>The result contains all informations about the connected {@code TestCase}.
   111  	 * Since it is available even if the {@code TestCaseRunner} has not been started
   112  	 * or finished the execution it is possible that the result is not complete at
   113  	 * the request. But it contains all informations about the methods that will
   114  	 * be executed.
   115  	 * 
   116  	 * @return {@link TestResult} for the {@code TestCase} that contains all informations
   117  	 */
   118  	public function getTestResult(Void):TestResult {
   119  		return testResult;
   120  	}
   121  	
   122  	/**
   123  	 * Returns the current executing {@code TestCaseResult}.
   124  	 * 
   125  	 * <p>It is necessary to get the {@code TestCaseResult} for the {@code TestCase}
   126  	 * that just gets executed because there can be more than one {@code TestCase}
   127  	 * available within a {@code TestResult}. 
   128  	 * 
   129  	 * @return {@code TestCaseResult} related to the {@code TestCase}
   130  	 */
   131  	public function getCurrentTestCase(Void):TestCaseResult {
   132  		return testResult;
   133  	}
   134  
   135  	/**
   136  	 * Returns the current executing {@code TestCaseMethodInfo}.
   137  	 * 
   138  	 * <p>It is necessary to get the {@code TestCaseMethodInfo} for the method
   139  	 * that just gets executed because there can be more than one methods available
   140  	 * within a {@code TestCaseResult}.
   141  	 * 
   142  	 * @return informations about the current executing method
   143  	 * @see #getCurrentTestCase
   144  	 */
   145  	public function getCurrentTestCaseMethodInfo(Void):TestCaseMethodInfo {
   146  		return methodInfo;
   147  	}
   148  
   149  	/**
   150  	 * Adds a information about the current executing method.
   151  	 * 
   152  	 * @param info {@code ExecutionInfo} to be added
   153  	 */
   154  	public function addInfo(info:ExecutionInfo):Void {
   155  		methodInfo.addInfo(info);
   156  	}
   157  	
   158  	/**
   159  	 * Implementation of {@link AbstractProcess#run} for the start of the {@code Process}.
   160  	 */
   161  	private function run(Void):Void {
   162  		working = true;
   163  		// Processor to manage the concrete processing of the TestCase
   164  		Processor.getInstance().addStepByStepProcess(this);
   165  	}
   166  	
   167  	/**
   168  	 * Executes the next step of the {@code Process}
   169  	 * 
   170  	 * <p>Implementation of {@link StepByStepProcess#nextStep}.
   171  	 */
   172  	public function nextStep(Void):Void {
   173  		if (openTestCaseMethods.isEmpty()) {
   174  			finish();
   175  		} else {
   176  			if (methodState == STATE_NOT_STARTED) {
   177  				methodInfo = openTestCaseMethods.dequeue();
   178  				sW = methodInfo.getStopWatch();
   179  				sendUpdateEvent();
   180  			}
   181  			while (processMethod());
   182  		}
   183  	}
   184  	
   185  	/**
   186  	 * Returns the percentage of execution of the {@code TestCase}.
   187  	 */
   188  	public function getPercentage(Void):Number {
   189  		return (100-(100/testResult.getMethodInfos().length*openTestCaseMethods.size()));
   190  	}
   191  	
   192  	/**
   193  	 * Executes the current method.
   194  	 * 
   195  	 * <p>Handles all possible executions states and continues to the next
   196  	 * execution.
   197  	 * 
   198  	 * @return {@code true} if the execution has finished and {@code false} if it has to be continued.
   199  	 */
   200  	private function processMethod(Void):Boolean {
   201  		// Execution depending to the current state.
   202  		switch (methodState) {
   203  			case STATE_NOT_STARTED:
   204  				
   205  			    // create instance and set state for next loop.
   206  				methodState = STATE_TEST_CREATED;
   207  				
   208  			    try {
   209  				    testCaseInstance = ClassUtil.createInstance(
   210  							testResult.getTestCase()["__constructor__"]
   211  					);
   212  			    } catch(e) {
   213  					fatal(new InstantiationError("IMPORTANT: Testcase threw "
   214  						+ "an error by instanciation.\n"
   215  						+ StringUtil.addSpaceIndent(e.toString(), 2), this, arguments));
   216  				}
   217  				break;
   218  				
   219  			case STATE_TEST_CREATED:
   220  			
   221  				// set up the instance and set state for next loop.
   222  				methodState = STATE_SET_UP_FINISHED;
   223  				
   224  				testCaseInstance.getTestRunner();
   225  
   226  				// Prepare the execution of the method by setUp
   227  				if (!methodInfo.hasErrors()) {
   228  					try {
   229  						testCaseInstance.setUp();
   230  					} catch (e) {
   231  						fatal(new SetUpError("IMPORTANT: Error occured during"
   232  							+ " set up(Testcase wasn't executed):\n"+StringUtil.addSpaceIndent(e.toString(), 2), testCaseInstance, arguments));
   233  					}
   234  				}
   235  				break;
   236  				
   237  			case STATE_SET_UP_FINISHED:
   238  			
   239  				// execute the method and set the state for the next loop
   240  				methodState = STATE_EXECUTION_FINISHED;
   241  				
   242  				if (!methodInfo.hasErrors()) {	
   243  					// Execute the method
   244  					sW.start();
   245  					try {
   246  						methodInfo.getMethodInfo().invoke(testCaseInstance, null);
   247  					} catch (e) {
   248  						fatal(new ExecutionError("Unexpected exception thrown"
   249  							+ " during execution:\n"
   250  							+ StringUtil.addSpaceIndent(e.toString(), 2),
   251  							testCaseInstance, arguments));
   252  					}
   253  				}
   254  				break;
   255  				
   256  			case STATE_EXECUTION_FINISHED:
   257  			
   258  				// tear down the instance and set the state for the next loop
   259  				methodState = STATE_TEAR_DOWN_FINISHED;
   260  				if (sW.hasStarted()) {
   261  					sW.stop();
   262  				}
   263  				
   264  				if (!methodInfo.hasErrors()) {	
   265  					try {
   266  						testCaseInstance.tearDown();
   267  					} catch(e) {
   268  						fatal(new TearDownError("IMPORTANT: Error occured during"
   269  							+ " tear down:\n"+StringUtil.addSpaceIndent(e.toString(), 2),
   270  							testCaseInstance, arguments));
   271  					}
   272  				}
   273  				break;
   274  				
   275  			case STATE_TEAR_DOWN_FINISHED:
   276  				methodState = STATE_NOT_STARTED;
   277  				methodInfo.setExecuted(true);
   278  				return false; // next method
   279  				
   280  		}
   281  		// next state execution
   282  		return true;
   283  	}
   284  	
   285  	/**
   286  	 * Internal helper to stop the execution if a fatal error occurs.
   287  	 * 
   288  	 * <p>It will add the passed-in {@code error} to the list of informations
   289  	 * with {@code addInfo}.
   290  	 * 
   291  	 * @param error error that occured
   292  	 */
   293  	private function fatal(error:ExecutionInfo):Void {
   294  		addInfo(error);
   295  		methodState = STATE_TEAR_DOWN_FINISHED;
   296  	}
   297  }