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.env.except.IllegalArgumentException;
    18  import org.as2lib.env.overload.Overload;
    19  import org.as2lib.env.reflect.MethodInfo;
    20  import org.as2lib.env.reflect.ConstructorInfo;
    21  import org.as2lib.env.reflect.ClassInfo;
    22  import org.as2lib.test.speed.AbstractTest;
    23  import org.as2lib.test.speed.Test;
    24  import org.as2lib.test.speed.SimpleTestSuiteResult;
    25  import org.as2lib.test.speed.MethodInvocation;
    26  
    27  /**
    28   * {@code MethodTestCase} is a test case for a method. It tests a method and profiles
    29   * its method invocations.
    30   *
    31   * @author Simon Wacker
    32   * @author Martin Heidegger
    33   */
    34  class org.as2lib.test.speed.MethodTestCase extends AbstractTest implements Test {
    35  	
    36  	/** Makes the static variables of the super-class accessible through this class. */
    37  	private static var __proto__:Function = AbstractTest;
    38  	
    39  	/** The previous method invocation. */
    40  	private static var p:MethodInvocation;
    41  	
    42  	/** The profiled method. */
    43  	private var method:MethodInfo;
    44  	
    45  	/** Scope of profiled method. */
    46  	private var s;
    47  	
    48  	/** Name of profiled method. */
    49  	private var n:String;
    50  	
    51  	/**
    52  	 * @overload #MethodTestCaseByVoid
    53  	 * @overload #MethodTestCaseByMethod
    54  	 * @overload #MethodTestCaseByObjectAndMethod
    55  	 * @overload #MethodTestCaseByObjectAndName
    56  	 */
    57  	public function MethodTestCase() {
    58  		var o:Overload = new Overload(this);
    59  		o.addHandler([], MethodTestCaseByVoid);
    60  		o.addHandler([MethodInfo], MethodTestCaseByMethod);
    61  		o.addHandler([MethodInfo, Object, String], MethodTestCaseByMethod);
    62  		o.addHandler([Object, Function], MethodTestCaseByObjectAndMethod);
    63  		o.addHandler([Object, String], MethodTestCaseByObjectAndName);
    64  		o.forward(arguments);
    65  	}
    66  	
    67  	/**
    68  	 * Constructs a new {@code MethodTestCase} instance for the default method. This is
    69  	 * the method named {@code "doRun"} that must be declared on this instance.
    70  	 */
    71  	private function MethodTestCaseByVoid(Void):Void {
    72  		MethodTestCaseByObjectAndName(this, "doRun");
    73  	}
    74  	
    75  	/**
    76  	 * Constructs a new {@code MethodTestCase} instance by method.
    77  	 * 
    78  	 * <p>If you want to profile a method, referenced from a different scope and with a
    79  	 * different name you can specify thse with the last two arguments. Note that if
    80  	 * specified the method declared on the class will not be profiled but its
    81  	 * reference.
    82  	 * 
    83  	 * @param method the method to profile
    84  	 * @param referenceScope (optional) the scope of the method reference to profile
    85  	 * @param referenceName (optional) the name of the method reference to profile
    86  	 * @throws IllegalArgumentException if the passed-in {@code method} is {@code null}
    87  	 * or {@code undefined}
    88  	 */
    89  	private function MethodTestCaseByMethod(method:MethodInfo, referenceScope, referenceName:String):Void {
    90  		if (!method) {
    91  			throw new IllegalArgumentException("Argument 'method' [" + method + "] must not be 'null' nor 'undefined' or this instance must declare a method named 'doRun'.", this, arguments);
    92  		}
    93  		this.method = method.snapshot();
    94  		if (referenceScope) {
    95  			this.s = referenceScope;
    96  		} else {
    97  			if (method instanceof ConstructorInfo) {
    98  				this.s = method.getDeclaringType().getPackage().getPackage();
    99  			} else if (method.isStatic()) {
   100  				this.s = method.getDeclaringType().getType();
   101  			} else {
   102  				this.s = method.getDeclaringType().getType().prototype;
   103  			}
   104  		}
   105  		if (referenceName) {
   106  			this.n = referenceName;
   107  		} else {
   108  			if (method instanceof ConstructorInfo) {
   109  				this.n = method.getDeclaringType().getName();
   110  			} else {
   111  				this.n = method.getName();
   112  			}
   113  		}
   114  		setResult(new SimpleTestSuiteResult(method.getFullName()));
   115  	}
   116  	
   117  	/**
   118  	 * Constructs a new {@code MethodTestCase} instance by object and method.
   119  	 * 
   120  	 * @param object the object that declares the method to profile
   121  	 * @param method the method to profile
   122  	 * @throws IllegalArgumentException if {@code object} or {@code method} is
   123  	 * {@code null} or {@code undefined}
   124  	 */
   125  	private function MethodTestCaseByObjectAndMethod(object, method:Function):Void {
   126  		if (object == null || !method) {
   127  			throw new IllegalArgumentException("Neither argument 'object' [" + object + "] nor 'method' [" + method + "] is allowed to be 'null' or 'undefined'.");
   128  		}
   129  		var c:ClassInfo = ClassInfo.forObject(object);
   130  		MethodTestCaseByMethod(c.getMethodByMethod(method));	
   131  	}
   132  	
   133  	/**
   134  	 * Constructs a new {@code MethodTestCase} instance by object and method name.
   135  	 * 
   136  	 * @param object the object that declares the method to profile
   137  	 * @param methodName the name of the method to profile
   138  	 * @throws IllegalArgumentException if a method with the given {@code methodName}
   139  	 * does not exist on the given {@code object} or is not of type {@code "function"}
   140  	 */
   141  	private function MethodTestCaseByObjectAndName(object, methodName:String):Void {
   142  		if (!object[methodName]) {
   143  			throw new IllegalArgumentException("Method [" + object[methodName] + "] with name '" + methodName + "' on object [" + object + "] must not be 'null' nor 'undefined'.");
   144  		}
   145  		if (typeof(object[methodName]) != "function") {
   146  			throw new IllegalArgumentException("Method [" + object[methodName] + "] with name '" + methodName + "' on object [" + object + "] must be of type 'function'.");
   147  		}
   148  		var c:ClassInfo = ClassInfo.forObject(object);
   149  		if (c.hasMethod(methodName)) {
   150  			MethodTestCaseByMethod(c.getMethodByName(methodName));
   151  		} else {
   152  			var m:MethodInfo = new MethodInfo(methodName, c, false, object[methodName]);
   153  			MethodTestCaseByMethod(m, object, methodName);
   154  		}
   155  	}
   156  	
   157  	/**
   158  	 * Returns the profiled method.
   159  	 * 
   160  	 * @return the profiled method
   161  	 */
   162  	public function getMethod(Void):MethodInfo {
   163  		return this.method;
   164  	}
   165  	
   166  	/**
   167  	 * Runs this performance test case.
   168  	 */
   169  	public function run(Void):Void {
   170  		this.s[this.n] = createClosure();
   171  	}
   172  	
   173  	/**
   174  	 * Creates a closure, that is a wrapper method, for the method to profile.
   175  	 * 
   176  	 * @return the created closure
   177  	 */
   178  	private function createClosure(Void):Function {
   179  		var t:MethodTestCase = this;
   180  		var mi:MethodInfo = this.method;
   181  		var m:Function = this.method.getMethod();
   182  		var closure:Function = function() {
   183  			var i:MethodInvocation = t["c"]();
   184  			i.setPreviousMethodInvocation(MethodTestCase["p"]);
   185  			i.setArguments(arguments);
   186  			i.setCaller(arguments.caller.__as2lib__i);
   187  			m.__as2lib__i = i;
   188  			var b:Number = getTimer();
   189  			try {
   190  				var r = mi.invoke(this, arguments);
   191  				i.setTime(getTimer() - b);
   192  				i.setReturnValue(r);
   193  				return r;
   194  			} catch (e) {
   195  				i.setTime(getTimer() - b);
   196  				i.setException(e);
   197  				throw e;
   198  			} finally {
   199  				t["a"](i);
   200  				MethodTestCase["p"] = i;
   201  				delete m.__as2lib__i;
   202  			}
   203  		};
   204  		closure.valueOf = function():Object {
   205  			return m;
   206  		};
   207  		// sets class specific variables needed for closures of classes
   208  		closure.__proto__ = m.__proto__;
   209  		closure.prototype = m.prototype;
   210  		closure.__constructor__ = m.__constructor__;
   211  		closure.constructor = m.constructor;
   212  		return closure;
   213  	}
   214  	
   215  	/**
   216  	 * Creates a new method invocation configured for this test case.
   217  	 * 
   218  	 * @return a new configured method invocation
   219  	 */
   220  	private function c(Void):MethodInvocation {
   221  		return new MethodInvocation(this.method);
   222  	}
   223  	
   224  	/**
   225  	 * Adds a new method invocation profile result.
   226  	 * 
   227  	 * @param m the new method invocation profile result
   228  	 */
   229  	private function a(m:MethodInvocation):Void {
   230  		this.result.addTestResult(m);
   231  	}
   232  	
   233  }