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.test.speed.AbstractTestResult;
    18  import org.as2lib.test.speed.TestResult;
    19  import org.as2lib.test.speed.TestSuiteResult;
    20  import org.as2lib.test.speed.MethodInvocation;
    21  import org.as2lib.test.speed.MethodInvocationHolder;
    22  
    23  /**
    24   * {@code AbstractTestSuiteResult} provides implementations of methods needed by
    25   * implementations of the {@link TestSuiteResult} interface.
    26   * 
    27   * @author Simon Wacker
    28   */
    29  class org.as2lib.test.speed.AbstractTestSuiteResult extends AbstractTestResult {
    30  	
    31  	/** Sort by name. */
    32  	public static var NAME:Number = 0;
    33  	
    34  	/** Sort by time. */
    35  	public static var TIME:Number = 1;
    36  	
    37  	/** Sort by average time. */
    38  	public static var AVERAGE_TIME:Number = 2;
    39  	
    40  	/** Sort by time percentage. */
    41  	public static var TIME_PERCENTAGE:Number = 3;
    42  	
    43  	/** Sort by method invocation count. */
    44  	public static var METHOD_INVOCATION_COUNT:Number = 4;
    45  	
    46  	/** Sort by method invocation percentage. */
    47  	public static var METHOD_INVOCATION_PERCENTAGE:Number = 5;
    48  	
    49  	/** Sort by the succession of method invocations. */
    50  	public static var METHOD_INVOCATION_SUCCESSION:Number = 6;
    51  	
    52  	/** Test results of all sub-tests. */
    53  	private var testResults:Array;
    54  	
    55  	/**
    56  	 * Constructs a new {@code AbstractTestSuiteResult} instance.
    57  	 */
    58  	private function AbstractTestSuiteResult(Void) {
    59  		this.testResults = new Array();
    60  	}
    61  	
    62  	/**
    63  	 * Returns this instance with correct type. This is needed for proper compile-time
    64  	 * checks.
    65  	 * 
    66  	 * @return this instance with its correct type
    67  	 */
    68  	private function getThis(Void):TestSuiteResult {
    69  		return TestSuiteResult(this);
    70  	}
    71  	
    72  	/**
    73  	 * Returns the time needed per method invocation.
    74  	 * 
    75  	 * @return the time needed per method invocation
    76  	 */
    77  	public function getAverageTime(Void):Number {
    78  		return (Math.round((getThis().getTime() / getThis().getMethodInvocationCount()) * 100) / 100);
    79  	}
    80  	
    81  	/**
    82  	 * Returns all profiled method invocations as {@link MethodInvocation} instances.
    83  	 * 
    84  	 * @return all profiled method invocations as {@code MethodInvocation} instances
    85  	 */
    86  	public function getAllMethodInvocations(Void):Array {
    87  		var result:Array = new Array();
    88  		for (var i:Number = 0; i < this.testResults.length; i++) {
    89  			var testResult:TestResult = this.testResults[i];
    90  			if (testResult instanceof MethodInvocation) {
    91  				result.push(testResult);
    92  			} else if (testResult instanceof MethodInvocationHolder) {
    93  				result.push(MethodInvocationHolder(testResult).getMethodInvocation());
    94  			}
    95  			if (testResult instanceof TestSuiteResult) {
    96  				result = result.concat(TestSuiteResult(testResult).getAllMethodInvocations());
    97  			}
    98  		}
    99  		return result;
   100  	}
   101  	
   102  	/**
   103  	 * Returns whether this test suite result has any method invocations.
   104  	 * 
   105  	 * @return {@code true} if this test suite result has method invocations else
   106  	 * {@code false}
   107  	 */
   108  	public function hasMethodInvocations(Void):Boolean {
   109  		return (getThis().getAllMethodInvocations().length > 0);
   110  	}
   111  	
   112  	/**
   113  	 * Returns the total number of method invocations.
   114  	 * 
   115  	 * @return the total number of method invocations
   116  	 */
   117  	public function getMethodInvocationCount(Void):Number {
   118  		return getThis().getAllMethodInvocations().length;
   119  	}
   120  	
   121  	/**
   122  	 * Returns the percentage of method invocations in relation to the passed-in
   123  	 * {@code totalMethodInvocationCount}.
   124  	 * 
   125  	 * @param totalMethodInvocationCount the total number of method invocations to
   126  	 * calculate the percentage with
   127  	 * @return the percentage of method invocations of this result
   128  	 */
   129  	public function getMethodInvocationPercentage(totalMethodInvocationCount:Number):Number {
   130  		return (Math.round((getThis().getMethodInvocationCount() / totalMethodInvocationCount) * 10000) / 100);
   131  	}
   132  	
   133  	/**
   134  	 * Returns all test results as {@link TestResult} instances directly of this test
   135  	 * suite.
   136  	 * 
   137  	 * @return all test results of this test suite
   138  	 */
   139  	public function getTestResults(Void):Array {
   140  		return this.testResults.concat();
   141  	}
   142  	
   143  	/**
   144  	 * Returns whether this test suite result has sub-test results.
   145  	 * 
   146  	 * @return {@code true} if this test suite has sub-test results else {@code false}
   147  	 */
   148  	public function hasTestResults(Void):Boolean {
   149  		return (this.testResults.length > 0);
   150  	}
   151  	
   152  	/**
   153  	 * Returns the number of direct sub-test results.
   154  	 * 
   155  	 * @return the number of direct sub-test results
   156  	 */
   157  	public function getTestResultCount(Void):Number {
   158  		return this.testResults.length;
   159  	}
   160  	
   161  	/**
   162  	 * Adds a new sub-test result.
   163  	 * 
   164  	 * <p>If {@code testResult} is {@code null} or {@code undefined} it wll be
   165  	 * ignored, this means not added.
   166  	 * 
   167  	 * @param testResult the new test result to add
   168  	 */
   169  	public function addTestResult(testResult:TestResult):Void {
   170  		if (testResult) {
   171  			this.testResults.push(testResult);
   172  		}
   173  	}
   174  	
   175  	/**
   176  	 * Sorts this test suite result and its sub-test results.
   177  	 * 
   178  	 * <p>Supported sort properties are:
   179  	 * <ul>
   180  	 *   <li>{@link #NAME}</li>
   181  	 *   <li>{@link #TIME}</li>
   182  	 *   <li>{@link #AVERAGE_TIME}</li>
   183  	 *   <li>{@link #TIME_PERCENTAGE}</li>
   184  	 *   <li>{@link #METHOD_INVOCATION_COUNT}</li>
   185  	 *   <li>{@link #METHOD_INVOCATION_PERCENTAGE}</li>
   186  	 *   <li>{@link #METHOD_INVOCATION_SUCCESSION}</li>
   187  	 * </ul>
   188  	 * 
   189  	 * @param property the property to sort by
   190  	 * @param descending determines whether to sort descending {@code true} or
   191  	 * ascending {@code false}
   192  	 */
   193  	public function sort(property:Number, descending:Boolean):Void {
   194  		if (property == null) return;
   195  		var comparator:Function = getComparator(property);
   196  		if (comparator) {
   197  			if (descending) {
   198  				this.testResults.sort(comparator, Array.DESCENDING);
   199  			} else {
   200  				this.testResults.sort(comparator);
   201  			}
   202  		}
   203  		for (var i:Number = 0; i < this.testResults.length; i++) {
   204  			var testSuiteResult:TestSuiteResult = TestSuiteResult(this.testResults[i]);
   205  			if (testSuiteResult) {
   206  				testSuiteResult.sort(property, descending);
   207  			}
   208  		}
   209  	}
   210  	
   211  	/**
   212  	 * Returns the comparator for the passed-in {@code property}.
   213  	 * 
   214  	 * @param property the property to return the comparator for
   215  	 * @return the comparator for the passed-in {@code property}
   216  	 */
   217  	private function getComparator(property:Number):Function {
   218  		switch (property) {
   219  			case NAME:
   220  				return getNameComparator();
   221  				break;
   222  			case TIME:
   223  				return getTimeComparator();
   224  				break;
   225  			case AVERAGE_TIME:
   226  				return getAverageTimeComparator();
   227  				break;
   228  			case TIME_PERCENTAGE:
   229  				return getTimePercentageComparator();
   230  				break;
   231  			case METHOD_INVOCATION_COUNT:
   232  				return getMethodInvocationCountComparator();
   233  				break;
   234  			case METHOD_INVOCATION_PERCENTAGE:
   235  				return getMethodInvocationPercentageComparator();
   236  				break;
   237  			case METHOD_INVOCATION_SUCCESSION:
   238  				return getMethodInvocationSuccessionComparator();
   239  				break;
   240  			default:
   241  				return null;
   242  				break;
   243  		}
   244  	}
   245  	
   246  	/**
   247  	 * Returns the comparator that compares test results by their names.
   248  	 * 
   249  	 * @return the comparator that compares test results by their names
   250  	 */
   251  	private function getNameComparator(Void):Function {
   252  		// returning function directly is not flex compatible
   253  		// flex compiler would not recognize return statement
   254  		// seems to be a flex compiler bug
   255  		var r:Function = function(a:TestResult, b:TestResult):Number {
   256  			var m:String = a.getName();
   257  			var n:String = b.getName();
   258  			if (m == n) return 0;
   259  			if (m > n) return 1;
   260  			return -1;
   261  		};
   262  		return r;
   263  	}
   264  	
   265  	/**
   266  	 * Returns the comparator that compares the results by their needed time.
   267  	 * 
   268  	 * @return the comparator that compares the results by their needed time
   269  	 */
   270  	private function getTimeComparator(Void):Function {
   271  		// returning function directly is not flex compatible
   272  		// flex compiler would not recognize return statement
   273  		// seems to be a flex compiler bug
   274  		var r:Function = function(a:TestResult, b:TestResult):Number {
   275  			var m:Number = a.getTime();
   276  			var n:Number = b.getTime();
   277  			if (m == n) return 0;
   278  			if (m > n) return 1;
   279  			return -1;
   280  		};
   281  		return r;
   282  	}
   283  	
   284  	/**
   285  	 * Returns the comparator that compares the results by their average time.
   286  	 * 
   287  	 * @return the comparator that compares the results by their average time
   288  	 */
   289  	private function getAverageTimeComparator(Void):Function {
   290  		// returning function directly is not flex compatible
   291  		// flex compiler would not recognize return statement
   292  		// seems to be a flex compiler bug
   293  		var r:Function = function(a:TestResult, b:TestResult):Number {
   294  			if (a instanceof TestSuiteResult
   295  					&& b instanceof TestSuiteResult) {
   296  				var m:Number = TestSuiteResult(a).getAverageTime();
   297  				var n:Number = TestSuiteResult(b).getAverageTime();
   298  				if (m == n) return 0;
   299  				if (m > n) return 1;
   300  				return -1;
   301  			}
   302  			var m:Number = a.getTime();
   303  			var n:Number = b.getTime();
   304  			if (m == n) return 0;
   305  			if (m > n) return 1;
   306  			return -1;
   307  		};
   308  		return r;
   309  	}
   310  	
   311  	/**
   312  	 * Returns the comparator that compares the results by their needed time in
   313  	 * percentage.
   314  	 * 
   315  	 * @return the comparator that compares the results by their needed time in
   316  	 * percentage.
   317  	 */
   318  	private function getTimePercentageComparator(Void):Function {
   319  		var scope:TestResult = getThis();
   320  		// returning function directly is not flex compatible
   321  		// flex compiler would not recognize return statement
   322  		// seems to be a flex compiler bug
   323  		var r:Function = function(a:TestResult, b:TestResult):Number {
   324  			var m:Number = a.getTimePercentage(scope.getTime());
   325  			var n:Number = b.getTimePercentage(scope.getTime());
   326  			if (m == n) return 0;
   327  			if (m > n) return 1;
   328  			return -1;
   329  		};
   330  		return r;
   331  	}
   332  	
   333  	/**
   334  	 * Returns the comparator that compares the results by their invocation count.
   335  	 * 
   336  	 * @return the comparator that compares the results by their invocation count
   337  	 */
   338  	private function getMethodInvocationCountComparator(Void):Function {
   339  		// returning function directly is not flex compatible
   340  		// flex compiler would not recognize return statement
   341  		// seems to be a flex compiler bug
   342  		var r:Function = function(a:TestResult, b:TestResult):Number {
   343  			if (a instanceof TestSuiteResult
   344  					&& b instanceof TestSuiteResult) {
   345  				var m:Number = TestSuiteResult(a).getMethodInvocationCount();
   346  				var n:Number = TestSuiteResult(b).getMethodInvocationCount();
   347  				if (m == n) return 0;
   348  				if (m > n) return 1;
   349  				return -1;
   350  			}
   351  			return 0;
   352  		};
   353  		return r;
   354  	}
   355  	
   356  	/**
   357  	 * Returns the comparator that compares the results by their method invocation
   358  	 * count in percentage.
   359  	 * 
   360  	 * @return the comparator that compares the results by their method invocation
   361  	 * count in percentage
   362  	 */
   363  	private function getMethodInvocationPercentageComparator(Void):Function {
   364  		var scope:TestSuiteResult = getThis();
   365  		// returning function directly is not flex compatible
   366  		// flex compiler would not recognize return statement
   367  		// seems to be a flex compiler bug
   368  		var r:Function = function(a:TestResult, b:TestResult):Number {
   369  			if (a instanceof TestSuiteResult
   370  					&& b instanceof TestSuiteResult) {
   371  				var m:Number = TestSuiteResult(a).getMethodInvocationPercentage(scope.getMethodInvocationCount());
   372  				var n:Number = TestSuiteResult(b).getMethodInvocationPercentage(scope.getMethodInvocationCount());
   373  				if (m == n) return 0;
   374  				if (m > n) return 1;
   375  				return -1;
   376  			}
   377  			return 0;
   378  		};
   379  		return r;
   380  	}
   381  	
   382  	/**
   383  	 * Returns the method invocation succession comparator.
   384  	 * 
   385  	 * @return the method invocation succession comparator
   386  	 */
   387  	private function getMethodInvocationSuccessionComparator(Void):Function {
   388  		// returning function directly is not flex compatible
   389  		// flex compiler would not recognize return statement
   390  		// seems to be a flex compiler bug
   391  		var r:Function = function(a:TestResult, b:TestResult):Number {
   392  			var m:MethodInvocation;
   393  			var n:MethodInvocation;
   394  			if (a instanceof MethodInvocation) m = MethodInvocation(a);
   395  			else if (a instanceof MethodInvocationHolder) m = MethodInvocationHolder(a).getMethodInvocation();
   396  			if (b instanceof MethodInvocation) n = MethodInvocation(b);
   397  			else if (b instanceof MethodInvocationHolder) n = MethodInvocationHolder(b).getMethodInvocation();
   398  			if (a && b) {
   399  				if (m.isPreviousMethodInvocation(n)) return -1;
   400  				if (n.isPreviousMethodInvocation(m)) return 1;
   401  			}
   402  			return 0;
   403  		};
   404  		return r;
   405  	}
   406  	
   407  }